Count number of queries executed by NHibernate in a unit test - nhibernate

In some unit/integration tests of the code we wish to check that correct usage of the second level cache is being employed by our code.
Based on the code presented by Ayende here:
http://ayende.com/Blog/archive/2006/09/07/MeasuringNHibernatesQueriesPerPage.aspx
I wrote a simple class for doing just that:
public class QueryCounter : IDisposable
{
CountToContextItemsAppender _appender;
public int QueryCount
{
get { return _appender.Count; }
}
public void Dispose()
{
var logger = (Logger) LogManager.GetLogger("NHibernate.SQL").Logger;
logger.RemoveAppender(_appender);
}
public static QueryCounter Start()
{
var logger = (Logger) LogManager.GetLogger("NHibernate.SQL").Logger;
lock (logger)
{
foreach (IAppender existingAppender in logger.Appenders)
{
if (existingAppender is CountToContextItemsAppender)
{
var countAppender = (CountToContextItemsAppender) existingAppender;
countAppender.Reset();
return new QueryCounter {_appender = (CountToContextItemsAppender) existingAppender};
}
}
var newAppender = new CountToContextItemsAppender();
logger.AddAppender(newAppender);
logger.Level = Level.Debug;
logger.Additivity = false;
return new QueryCounter {_appender = newAppender};
}
}
public class CountToContextItemsAppender : IAppender
{
int _count;
public int Count
{
get { return _count; }
}
public void Close()
{
}
public void DoAppend(LoggingEvent loggingEvent)
{
if (string.Empty.Equals(loggingEvent.MessageObject)) return;
_count++;
}
public string Name { get; set; }
public void Reset()
{
_count = 0;
}
}
}
With intended usage:
using (var counter = QueryCounter.Start())
{
// ... do something
Assert.Equal(1, counter.QueryCount); // check the query count matches our expectations
}
But it always returns 0 for Query count. No sql statements are being logged.
However if I make use of Nhibernate Profiler and invoke this in my test case:
NHibernateProfiler.Intialize()
Where NHProf uses a similar approach to capture logging output from NHibernate for analysis via log4net etc. then my QueryCounter starts working.
It looks like I'm missing something in my code to get log4net configured correctly for logging nhibernate sql ... does anyone have any pointers on what else I need to do to get sql logging output from Nhibernate?
Additional info:
Logging.config:
<log4net>
<appender name="trace" type="log4net.Appender.TraceAppender, log4net">
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%P{user}&gt; - %m%n" />
</layout>
</appender>
<appender name="console" type="log4net.Appender.ConsoleAppender, log4net">
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%P{user}&gt; - %m%n" />
</layout>
</appender>
<appender name="debug" type="log4net.Appender.DebugAppender, log4net">
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%P{user}&gt; - %m%n" />
</layout>
</appender>
<logger name="NHibernate.SQL" additivity="false">
<level value="DEBUG" />
<appender-ref ref="ConsoleAppender" />
</logger>
<root>
<priority value="DEBUG" />
<appender-ref ref="trace" />
<appender-ref ref="console" />
<appender-ref ref="debug" />
</root>
</log4net>
show_sql: true
Based on jfneis response, I wrote a far simpler class which just uses NHibernate's factory statistics:
public class QueryCounter
{
long _startCount;
QueryCounter()
{
}
public int QueryCount
{
get { return (int) (UnitOfWork.CurrentSession.SessionFactory.Statistics.QueryExecutionCount - _startCount); }
}
public static QueryCounter Start()
{
return new QueryCounter {_startCount = UnitOfWork.CurrentSession.SessionFactory.Statistics.QueryExecutionCount};
}
}
Which works just fine once statistics is enabled.

There's another (simpler, IMO) way to assert if cache is being hit or if queries are being executed: using Statistics.
First of all, you have to enable statistics in your NH config file:
<property name="generate_statistics">true</property>
After that, you can ask your session factory whenever you want how things are going. You've talked about L2 cache testing, so you could have something like that:
// act
MappedEntity retrievedEntity = session.FindById(entity.Id);
long preCacheCount = sessionFactory.Statistics.SecondLevelCacheHitCount;
retrievedEntity = session.FindById(entity.Id);
long postCacheCount = sessionFactory.Statistics.SecondLevelCacheHitCount;
// assert
Assert.AreEqual(preCacheCount + 1, postCacheCount);
But, if what you really want is the query count, there are plenty other options in the Statistics interface:
sessionFactory.Statistics.QueryExecutionCount;
sessionFactory.Statistics.TransactionCount;
Well, that's it. Hope this helps you as helped me.
Regards,
Filipe

Related

How to retrieve NServiceBus message headers in version 5?

I have the following line to obtain the message header within a IHandleMessages<> class
IDictionary<string, string> headers = _bus.CurrentMessageContext.Headers;
I get the error message
bus does not implement IManageMessageHeaders
I would have thought that this was configured as default. Does anyone know how to implement IManageMessageHeaders? Do I need to change how the endpoint is configured?
public class EndpointConfig : IConfigureThisEndpoint
{
public void Customize(BusConfiguration configuration)
{
configuration.UsePersistence<InMemoryPersistence>();
ConfigureLog4Net();
}
private void ConfigureLog4Net()
{
log4net.Config.XmlConfigurator.Configure();
var layout = new PatternLayout
{
ConversionPattern = "%d [%t] %-5p %c [%x] - %m%n"
};
layout.ActivateOptions();
var consoleAppender = new ColoredConsoleAppender
{
Threshold = Level.Debug,
Layout = layout
};
consoleAppender.ActivateOptions();
BasicConfigurator.Configure(consoleAppender);
LogManager.Use<Log4NetFactory>();
}
}
I'm using NServiceBus Host 6.0.0, NServiceBus 5.1.2.

Have users of activeMQ own a queue which name is the user's name

In my application, a user may create an account freely, and it needs to own a queue (or topic) to communicate 2 backend processes between them. I don't want to have to modify activemq's configuration every time that someone creates an account. I have already created a jaasAuthenticationPlugin and it works fine. Here is the relevant part of my activemq.xml file:
<plugins>
<!-- 'activemq-domain' defined in conf/login.conf -->
<jaasAuthenticationPlugin configuration="activemq-domain" />
<authorizationPlugin>
<map>
<authorizationMap>
<authorizationEntries>
<authorizationEntry queue="foobarQueue"
write="foobarGroup"
read="foobarGroup"
admin="foobarGroup"
/>
</authorizationEntries>
</authorizationMap>
</map>
</authorizationPlugin>
</plugins>
As you may deduct, the authentication plugin is authenticating a user (foobar in this example) and putting the user in the foobarGroup group. The AuthorizationEntry is granting read, write and admin privileges to the foobarQueue to this foobarGroup. This is working well, but now if I create a new user, I must come to this file and add a new AuthorizationEntry. Is it possible with a simple configuration line in the activemq.xml to do something like:
<authorizationEntry
queue="<% Username %>"
write="<% Username %>"
read="<% Username %>"
admin="<% Username %>"
/>
or should I write some JAAS authorization class to do that?
Finally I have written a class to handle the Authorization part. It was a bit difficult because documentation is difficult to find and I couldn't find any good example. Digging in the source code of the default LDAPAuthorizationMap was key. Anyway, the source for anyone interested:
package com.example.activemq;
import org.apache.activemq.advisory.AdvisorySupport;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.jaas.GroupPrincipal;
import org.apache.activemq.security.AuthorizationMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.HashSet;
import java.util.Set;
public class OwnedUserQueueAuthorizator implements AuthorizationMap {
private static final Log log =
LogFactory.getLog(OwnedUserQueueAuthorizator.class);
private boolean debug = false;
// the Destination will be the name of the user, and we should return that
// the group with user name has read,write and admin privileges to the
// topic/queue named like the username
// for temporary destinations, if null is returned, then everybody has
// permission.
public Set<GroupPrincipal> getTempDestinationAdminACLs() {
return null;
}
public Set<GroupPrincipal> getTempDestinationReadACLs() {
return null;
}
public Set<GroupPrincipal> getTempDestinationWriteACLs() {
return null;
}
// for persistent destinations
public Set<GroupPrincipal> getAdminACLs(ActiveMQDestination destination) {
if (debug) {
log.debug("getAdminACLs: " + destination.getPhysicalName());
}
return getACLs(destination);
}
public Set<GroupPrincipal> getReadACLs(ActiveMQDestination destination) {
if (debug) {
log.debug("getReadACLs: " + destination.getPhysicalName());
}
return getACLs(destination);
}
public Set<GroupPrincipal> getWriteACLs(ActiveMQDestination destination) {
if (debug) {
log.debug("getwriteACLs: " + destination.getPhysicalName());
}
return getACLs(destination);
}
private Set<GroupPrincipal> getACLs(ActiveMQDestination destination) {
Set<GroupPrincipal> result;
if (AdvisorySupport.isAdvisoryTopic(destination)) {
result = getACLsForAdvisory();
} else {
result = new HashSet<GroupPrincipal>();
// Destination should be something like UUID or UUID.whatever...,
// so we must add only the first component as the group principal
result.add(new GroupPrincipal(
destination.getDestinationPaths()[0])
);
}
if (debug) {
String s = "";
for (GroupPrincipal gp : result) {
s += ", " + gp.getName();
}
log.debug("groupPrincipals: " + "[" + s.substring(2) + "]");
}
return result;
}
private Set<GroupPrincipal> getACLsForAdvisory() {
Set<GroupPrincipal> result = new HashSet<GroupPrincipal>();
GroupPrincipal g = new GroupPrincipal("advisories");
result.add(g);
return result;
}
// Properties
// -------------------------------------------------------------------------
// if the <bean> definition in the activemq.xml has some
// <property name="foo" value="..." />
// defined, they will call this.setFoo($value), so, even if these get/set
// methods aren't called from here, they are really needed.
public void setDebug(String debug) {
this.debug = "true".equalsIgnoreCase(debug);
}
public String getDebug() {
return String.valueOf(debug);
}
}
The conf/activemq.xml file:
<beans ...>
...
<broker ...>
...
<plugins>
<!-- 'activemq-domain' defined in conf/login.conf -->
<jaasAuthenticationPlugin configuration="activemq-domain" />
<authorizationPlugin>
<map>
<bean id="OwnedUserQueueAuthorizationMap"
class="com.example.activemq.OwnedUserQueueAuthorizator"
xmlns="http://www.springframework.org/schema/beans">
<property name="debug" value="false"/>
</bean>
</map>
</authorizationPlugin>
</plugins>
...
</broker>
...
</beans>

NHibernate, joined subclass hierarchy, PreUpdate event data changes on an entity which is only modified in the PreUpdate event is not persisted

Overview: With NHibernate I am experimenting with a 3 layered hierarchy using joined subclasses. There is a Category, which inherits from AuditableEntity (to add PreUpdate and PreInsert audit trail), which finally inherits from an Entity.
Problem: None of the data changes to the AuditableEntity object, which are carried out exactly as Ayende’s blog post, are being persisted to the database. The AuditableEntity objects properties are successfully updated by the PreUpdate code, but it is as if NHibernate is not seeing the AuditableEntity as dirty as no update sql statement occurs.
Hbm:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Learning"
namespace="Learning.entities">
<class name="Entity" >
<id name="Id" type="guid">
<generator class="guid.comb"></generator>
</id>
<version name="Version"/>
<joined-subclass name="AuditableEntity" >
<key column="AuditableEntity_id"></key>
<property name="CreatedOn" ></property>
<property name="CreatedBy" ></property>
<property name="LastModifiedOn" ></property>
<property name="LastModifiedBy" ></property>
<joined-subclass name="Category">
<key column="AuditableEntity_id"></key>
<property name="Name" />
</joined-subclass>
</joined-subclass>
</class>
</hibernate-mapping>
NHibernate config for listeners:
<event type="pre-insert">
<listener class="Learning.eventlisteners.AuditInsertEventListener, Learning" />
</event>
<event type="pre-update">
<listener class="Learning.eventlisteners.AuditUpdateEventListener, Learning" />
</event>
PreUpdate code:
namespace Learning.eventlisteners
{
public class AuditInsertEventListener : IPreInsertEventListener
{
public bool OnPreInsert(PreInsertEvent #event)
{
var audit = #event.Entity as IAuditable;
if (audit == null)
return false;
var createdOn = DateTime.Now;
var createdBy = loggedOnProfile;
AuditCommon.Set(#event.Persister, #event.State, "CreatedOn", createdOn);
AuditCommon.Set(#event.Persister, #event.State, "CreatedBy", createdBy);
AuditCommon.Set(#event.Persister, #event.State, "LastModifiedOn", createdOn);
AuditCommon.Set(#event.Persister, #event.State, "LastModifiedBy", createdBy);
audit.CreatedOn = createdOn;
audit.CreatedBy = createdBy;
audit.LastModifiedOn = createdOn;
audit.LastModifiedBy = createdBy;
return false;
}
}
public static class AuditCommon
{
internal static void Set(IEntityPersister persister, IList<object> state, string propertyName, object value)
{
var index = Array.IndexOf(persister.PropertyNames, propertyName);
if (index == -1)
return;
state[index] = value;
}
}
public class AuditUpdateEventListener : IPreUpdateEventListener
{
public bool OnPreUpdate(PreUpdateEvent #event)
{
var audit = #event.Entity as IAuditable;
if (audit == null)
return false;
var lastModifiedOn = DateTime.Now.AddSeconds(28);
var lastModifiedBy = loggedOnProfile;
AuditCommon.Set(#event.Persister, #event.State, "LastModifiedOn", lastModifiedOn);
AuditCommon.Set(#event.Persister, #event.State, "LastModifiedBy", lastModifiedBy);
audit.LastModifiedOn = lastModifiedOn;
audit.LastModifiedBy = lastModifiedBy;
return false;
}
}
}
Code:
using (var session = SessionFactory.OpenSession())
using (var transaction = session.BeginTransaction())
{
var category = session.Query<Category>().First();
category.Name = "Updated";
session.SaveOrUpdate(category);
transaction.Commit();
}
An observation: if I manually update just one of the AuditableEntity properties before calling SaveOrUpdate, the PreUpdate event is obviously fired and appropriate data changes are made, and then the AuditableEntity data IS persisted to the database.
using (var session = SessionFactory.OpenSession())
using (var transaction = session.BeginTransaction())
{
var category = session.Query<Category>().First();
category.Name = "Updated";
category.CreatedOn = DateTime.Now;
session.SaveOrUpdate(category);
transaction.Commit();
}
Help: I obviously don't want to have to dummy edit an AuditableEntity properties, so any ideas as to what I am doing wrong here?
To answer this I have authored an nhibernate.info WIKI article - http://nhibernate.info/doc/howto/various/changing-values-in-nhibernate-events
The abstract; Audit trails using NHibernate's event model often use the OnPreInsert and OnPreUpdate event listeners to change/ modify the state of the entity. While this does works and is widely documented as a solution, it should be noted the OnPreInsert and OnPreUpdate events are not intended to be used to change the values of the entity and instead they should be used to check values and for that reason they return "veto".
An update blog post from Fabio - http://fabiomaulo.blogspot.com/2011/05/nhibernate-bizarre-audit.html

Dynamic list constraint not updating in alfresco on a datalist

I tried to create a dynamic list constraint. The data in the drop down is not getting refreshed when an item is added to the database.
ListOfValuesQueryConstraint.java
package org.alfresco.ryden;
import java.util.ArrayList;
import java.util.List;
import java.io.Serializable;
import java.sql.*;
import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint;
import org.alfresco.web.bean.generator.BaseComponentGenerator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.faces.model.SelectItem;
public class ListOfValuesQueryConstraint extends ListOfValuesConstraint implements Serializable {
private static Log logger = LogFactory.getLog(BaseComponentGenerator.class);
private static final long serialVersionUID=1;
private List allowedLabels;
public void setAllowedValues(List allowedValues) {}
public void setCaseSensitive(boolean caseSensitive) {}
public void initialize() {
super.setCaseSensitive(false);
this.loadDB();
}
public List getAllowedValues() {
this.loadDB();
return super.getAllowedValues(); // In earlier post there is no return statement..
//return this.getAllowedValues();
}
public List getAllowedLabels() {
return this.allowedLabels;
}
public void setAllowedLabels(List allowedLabels) {
this.allowedLabels=allowedLabels;
}
public List getSelectItemList() {
List result = new ArrayList(this.getAllowedValues().size());
for(int i=0;i<this.getAllowedValues().size();i++) {
result.add(new SelectItem((Object)this.getAllowedValues().get(i),this.allowedLabels.get(i)));
}
return result;
}
protected void loadDB() {
String driverName = "com.mysql.jdbc.Driver";
String serverName = "localhost:3307";
String mydatabase = "propertyrecord";
String username = "propertyrecord";
String password = "rydenproperty";
List av = new ArrayList();
List al=new ArrayList();
try {
Connection connection = null;
Class.forName(driverName);
String url = “jdbc:mysql://” + serverName + “/” + mydatabase;
connection = DriverManager.getConnection(url, username, password);
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(“select propertyRef from propertyrecord”);
while (rs.next()) {
av.add(rs.getString(“propertyRef”));
al.add(rs.getString(“propertyRef”));
System.out.println(“value of prop pavani “+rs.getString(“propertyRef”));
logger.debug(“value of prop pavani “+rs.getString(“propertyRef”));
}
rs=null;
}
catch (Exception e) {}
super.setAllowedValues(av);
this.setAllowedLabels(al);
}
}
CustomListComponentGenerator.java
package org.alfresco.ryden;
import java.util.List;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectOne;
import javax.faces.context.FacesContext;
import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint;
import org.alfresco.service.cmr.dictionary.Constraint;
import org.alfresco.service.cmr.dictionary.ConstraintDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.web.bean.generator.TextFieldGenerator;
import org.alfresco.web.ui.repo.component.property.PropertySheetItem;
import org.alfresco.web.ui.repo.component.property.UIPropertySheet;
import org.apache.log4j.Logger;
import org.alfresco.ryden.ListOfValuesQueryConstraint;
public class CustomListComponentGenerator extends TextFieldGenerator {
private static Logger log = Logger.getLogger(CustomListComponentGenerator.class);
// private String tutorialQuery =
// “( TYPE:\”{http://www.alfresco.org/model/content/1.0}content\” AND
// (#\\{http\\://www.alfresco.org/model/content/1.0\\}name:\”tutorial\”
// TEXT:\”tutorial\”))”
// ;
private boolean autoRefresh = false;
public boolean isAutoRefresh() {
return autoRefresh;
}
/**
* This gets set from faces-config-beans.xml, and allows some drop downs to
* be automaticlaly refreshable (i.e. country), and others not (i.e. city).
*/
public void setAutoRefresh(boolean autoRefresh) {
this.autoRefresh = autoRefresh;
}
#Override
#SuppressWarnings(“unchecked”)
protected UIComponent createComponent(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem item) {
UIComponent component = super.createComponent(context, propertySheet, item);
log.info(“********************** ” + item + ” >” + component + ” >” + (component instanceof UISelectOne) + ” ” + isAutoRefresh());
if (component instanceof UISelectOne && isAutoRefresh()) {
component.getAttributes().put(“onchange”, “submit()”);
}
return component;
}
/**
* Retrieves the list of values constraint for the item, if it has one
*
* #param context
* FacesContext
* #param propertySheet
* The property sheet being generated
* #param item
* The item being generated
* #return The constraint if the item has one, null otherwise
*/
protected ListOfValuesConstraint getListOfValuesConstraint(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem item) {
ListOfValuesConstraint lovConstraint = null;
log.info(“propertySheet: ” + propertySheet.getNode() + ” item: ” + item.getName());
// get the property definition for the item
PropertyDefinition propertyDef = getPropertyDefinition(context, propertySheet.getNode(), item.getName());
if (propertyDef != null) {
// go through the constaints and see if it has the
// list of values constraint
List constraints = propertyDef.getConstraints();
for (ConstraintDefinition constraintDef : constraints) {
Constraint constraint = constraintDef.getConstraint();
//log.info(“constraint: ” + constraint);
if (constraint instanceof ListOfValuesQueryConstraint) {
//Node currentNode = (Node) propertySheet.getNode();
// This is a workaround for the fact that constraints do not
// have a reference to Node.
//((ListOfValuesQueryConstraint) constraint).setNode(currentNode);
lovConstraint = (ListOfValuesQueryConstraint) constraint;
break;
}
if (constraint instanceof ListOfValuesConstraint) {
lovConstraint = (ListOfValuesConstraint) constraint;
break;
}
}
}
return lovConstraint;
}
}
custom-model.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- Definition of Property Base Model -->
<model name="cdl:customdatalist" xmlns="http://www.alfresco.org/model/dictionary/1.0">
<!-- Optional meta-data about the model -->
<description>Custom Data Model</description>
<author>Lalitha Akella</author>
<version>1.0</version>
<!-- Imports are required to allow references to definitions in other models -->
<imports>
<!-- Import Alfresco Dictionary Definitions -->
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
<!-- Import Alfresco Content Domain Model Definitions -->
<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
<import uri="http://www.alfresco.org/model/datalist/1.0" prefix="dl"/>
</imports>
<!-- Introduction of new namespaces defined by this model -->
<namespaces>
<namespace uri="cdl.model" prefix="cdl"/>
</namespaces>
<constraints>
<constraint name="cdl:PropertyRef" type="org.alfresco.ryden.ListOfValuesQueryConstraint" >
<parameter name="allowedValues">
<list>
</list>
</parameter>
<parameter name="caseSensitive"><value>true</value></parameter>
</constraint>
</constraints>
<types>
<type name="cdl:applicationform">
<title>Custom Application Form</title>
<parent>dl:dataListItem</parent>
<properties>
<property name="cdl:applicationpropertyRef">
<title>Property Reference</title>
<type>d:text</type>
<mandatory>true</mandatory>
<constraints>
<constraint ref="cdl:PropertyRef" />
</constraints>
</property>
<property name="cdl:applicationpropAddress">
<title>Property Address</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:apcreateddate">
<title>Created Date</title>
<type>d:date</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:apcreatedby">
<title>Created By</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:applicationstatus">
<title>Application Status</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:applicationlink">
<title>Application Workflow Link</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
</properties>
<associations>
<association name="cdl:applicationassignee">
<title>Assignee</title>
<source>
<mandatory>true</mandatory>
<many>true</many>
</source>
<target>
<class>cm:person</class>
<mandatory>true</mandatory>
<many>false</many>
</target>
</association>
<association name="cdl:applicationattachments">
<title>Attachments</title>
<source>
<mandatory>true</mandatory>
<many>true</many>
</source>
<target>
<class>cm:cmobject</class>
<mandatory>true</mandatory>
<many>true</many>
</target>
</association>
</associations>
</type>
<type name="cdl:terminationform">
<title>Custom Termination Form</title>
<parent>dl:dataListItem</parent>
<properties>
<property name="cdl:terminationpropertyRef">
<title>Property Reference</title>
<type>d:text</type>
<mandatory>true</mandatory>
<constraints>
<constraint ref="cdl:PropertyRef" />
</constraints>
</property>
<property name="cdl:trcreateddate">
<title>Created Date</title>
<type>d:date</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:trcreatedby">
<title>Created By</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:terminationstatus">
<title>Termination Status</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:terminationlink">
<title>Termination Workflow Link</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
</properties>
<associations>
<association name="cdl:terminationassignee">
<title>Assignee</title>
<source>
<mandatory>true</mandatory>
<many>true</many>
</source>
<target>
<class>cm:person</class>
<mandatory>true</mandatory>
<many>false</many>
</target>
</association>
<association name="cdl:terminationattachments">
<title>Attachments</title>
<source>
<mandatory>true</mandatory>
<many>true</many>
</source>
<target>
<class>cm:cmobject</class>
<mandatory>true</mandatory>
<many>true</many>
</target>
</association>
</associations>
</type>
</types>
</model>
web-client-config-custom.xml
<config evaluator="node-type" condition="cdl:assignationform">
<property-sheet>
<show-property name="cdl:assignationpropertyRef" component-generator="CustomListComponentGenerator" />
</property-sheet>
</config>
faces-config-beans.xml
<managed-bean>
<description>
Bean that generates a custom generator component
</description>
<managed-bean-name>
CustomListComponentGenerator
</managed-bean-name>
<managed-bean-class>
org.alfresco.ryden.CustomListComponentGenerator
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>autoRefresh</property-name>
<value>true</value>
</managed-property>
</managed-bean>
I don't know whether I should be changing any other files or some thing is wrong in the code above.
I am new To alfresco. Any help is deeply appreciated.
Thanks,
Pavani
Try the following and change to as needed, as it works
ListOfCountriesQueryConstraint.java
package org.spectrum.customConstraints;
import java.util.ArrayList;
import java.util.List;
import java.sql.*;
import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint;
import org.alfresco.web.bean.generator.BaseComponentGenerator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.Serializable;
import javax.faces.model.SelectItem;
public class ListOfCountriesQueryConstraint extends ListOfValuesConstraint implements Serializable {
private static Log logger = LogFactory.getLog(BaseComponentGenerator.class);
private static final long serialVersionUID = 1;
private List<String> allowedLabels;
#Override
public void setAllowedValues(List allowedValues) {
}
#Override
public void setCaseSensitive(boolean caseSensitive) {
}
#Override
public void initialize() {
super.setCaseSensitive(false);
this.loadDB();
}
#Override
public List getAllowedValues() {
this.loadDB();
return super.getAllowedValues();
}
public List<String> getAllowedLabels() {
return this.allowedLabels;
}
public void setAllowedLabels(List<String> allowedLabels) {
this.allowedLabels = allowedLabels;
}
public List<SelectItem> getSelectItemList() {
List<SelectItem> result = new ArrayList<SelectItem>(this.getAllowedValues().size());
for (int i = 0; i < this.getAllowedValues().size(); i++) {
result.add(new SelectItem((Object) this.getAllowedValues().get(i), this.allowedLabels.get(i)));
}
return result;
}
protected void loadDB() {
String driverName = "org.gjt.mm.mysql.Driver";
String serverName = "alfrescotest";
String mydatabase = "alfresco_custom";
String username = "root";
String password = "support";
List<String> av = new ArrayList<String>();
List<String> al = new ArrayList<String>();
try {
Connection connection = null;
Class.forName(driverName);
String url = "jdbc:mysql://" + serverName + "/" + mydatabase;
connection = DriverManager.getConnection(url, username, password);
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("select country from countries");
while (rs.next()) {
av.add(rs.getString("country"));
al.add(rs.getString("country"));
}
} catch (Exception e) {
}
super.setAllowedValues(av);
this.setAllowedLabels(al);
}
}
custom-model.xml
<constraint name="sp:country" type="org.spectrum.customConstraints.ListOfCountriesQueryConstraint">
<parameter name="allowedValues">
<list>
</list>
</parameter>
<parameter name="caseSensitive"><value>true</value></parameter>
</constraint>
Make sure to copy the compile java to tomcat/webapps/alfresco/WEB-INF/classes/org/xxx/

How do I implement ChangeTime and ChangeUser columns using NHibernate?

I'm trying to use NHibernate with an existing database. In the data-model there is columns in each table that contains the time and username of the last update made to a row. How do I do this using NHibernate?
I tried to implement a interceptor that sets ChangeTime and ChangeUser in the entities before it gets saved using the IInterceptor.OnSave method. This didn't work because setting these properties triggers an update to the row even if no other properties has been modified.
It could have worked if there was any way to tell NHibernate to exclude the ChangeTime and ChangeUser properties then it does it's dirty-checking. But i haven't found any way to accomplish this.
Thanks for any help.
You should register a listener to the pre insert and pre update events. You can do it through your configuration like so:
<hibernate-configuration>
...
<event type="pre-update">
<listener class="MyListener, MyAssembly"/>
</event>
<event type="pre-insert">
<listener class="MyListener, MyAssembly"/>
</event>
</hibernate-configuration>
and then implement a listener - something like this (might not be entirely accurate - written off my memory):
public class MyListener : IPreUpdateEventListener, IPreInsertEventListener
{
public bool OnPreUpdate(PreUpdateEvent evt)
{
if (evt.Entity is IHasLastModified)
UpdateLastModified(evt.State, evt.Persister.PropertyNames);
return false;
}
public bool OnPreInsert(PreInsertEvent evt)
{
if (evt.Entity is IHasLastModified)
UpdateLastModified(evt.State, evt.Persister.PropertyNames);
return false;
}
void UpdateLastModified(object[] state, string[] names)
{
var index = Array.FindIndex(names, n => n == "LastModified");
state[index] = DateTime.Now;
}
}
and do the same thing with the pre update event.
EDIT: This one takes care of insert as well as update and it seems to work.
Hey I just had to solve this on a project I am working on, here is my answer
public interface IDateModified
{
DateTime Created { get; set; }
DateTime Modified { get; set; }
}
public class CustomDefaultSaveOrUpdateEventListener
: DefaultSaveOrUpdateEventListener
{
protected override object EntityIsPersistent(SaveOrUpdateEvent evt)
{
var entity = evt.Entity as IDateModified;
if (entity != null)
{
entity.Modified = DateTime.Now;
}
return base.EntityIsPersistent(evt);
}
protected override object EntityIsTransient(SaveOrUpdateEvent evt)
{
var entity = evt.Entity as IDateModified;
if (entity != null)
{
entity.Created = entity.Modified = DateTime.Now;
}
return base.EntityIsTransient(evt);
}
}
Then in my configuration (I am using Fluent NHibernate to configure my unit tests in code)
configuration.EventListeners.SaveOrUpdateEventListeners
= new ISaveOrUpdateEventListener[]
{
new CustomDefaultSaveOrUpdateEventListener()
};
AWESOMENESSSSSSSS!
Mookid's answer is correct although I would like to point out that if one is using S#arp Architecture, the NHib configuration should be set up as follows:
<hibernate-configuration>
<session-factory>
...
<event type="pre-update">
<listener class="MyListener, MyAssembly"/>
</event>
<event type="pre-insert">
<listener class="MyListener, MyAssembly"/>
</event>
</session-factory>
</hibernate-configuration>
The event elements go into the session-factory element.
Instead of using LIsteners, you can also use Interceptors:
Audit changes using interceptor