Archive for the ‘dm’ Category

Disabling Auditable Properties from Being Set Automatically

Sunday, May 3rd, 2009

When performing content ingestion, it is often needed to set some system properties that are governed by the Auditable Aspect. These properties are cm:created, cm:creator, cm:modified, cm:modifier. In 3.0 and 3.1, there is no explicit option to turn these off. If this issue affects you, please vote for this JIRA ticket, but in the meantime, here is a way to get around this when you need to disable automatic setting of these properties temporarily.

Note: we are using some trickery here, and doing things that aren’t typically recommended, so please be very careful and test things out in your environment. This worked for me on Alfresco 3.0SP1.

The auditing properties are being set pretty deep inside the infrastructure code (class HibernateNodeDaoServiceImpl). The code checks whether Auditable aspect is applied, and if so, it sets these properties explicitly. One way to disable this is by modifying the code. The way to do it more cleanly is by temporarily changing contentModel.xml. Auditable aspect applied is a mandatory aspect on cm:object, which is parent of cm:content.

Step 1 – Disabling Automatic Setting of Auditable Properties

So my solution is to remove Auditable as a mandatory aspect and to add those properties to cm:object directly. This is the new definition of cm:object.

<type name=”cm:cmobject”>

<title>Object</title>

<parent>sys:base</parent>

<properties>

<property name=”cm:name”>

<title>Name</title>

<type>d:text</type>

<mandatory enforced=”true”>true</mandatory>

<constraints>

<constraint ref=”cm:filename” />

</constraints>

</property>

<!–temporary added properties to go around auditable –>

<property name=”cm:created”>

<title>Created</title>

<type>d:datetime</type>

</property>

<property name=”cm:creator”>

<title>Creator</title>

<type>d:text</type>

</property>

<property name=”cm:modified”>

<title>Modified</title>

<type>d:datetime</type>

</property>

<property name=”cm:modifier”>

<title>Modifier</title>

<type>d:text</type>

</property>

<property name=”cm:accessed”>

<title>Accessed</title>

<type>d:datetime</type>

</property>

</properties>

<!—- Disable Auditable Aspect

<mandatory-aspects>

<aspect>cm:auditable</aspect>

</mandatory-aspects>

–>

</type>

Additionally, for convenience, it’s a good idea to not make these protected, or enforce them as being mandatory, as they are on the original Auditable aspect. As you can see, we commented out cm:auditable as mandatory aspect on this type, which means the code that sets these properties automatically will not get triggered.

Step 2 – Show Properties in the User Interface

The next step is cosmetic – we need to make these properties visible in our UI so it’s easier to test. For that, you will need to edit CONFIGROOT web-client-config-properties.xml. Add the auditable properties to “content” evaluator:

<config evaluator=”node-type” condition=”content”>

<property-sheet>

<show-property name=”creator” />

<show-property name=”created” />

<show-property name=”modifier” />

<show-property name=”modified” />

This will allow you to see these in the UI.

Step 3 – Set Properties, Migrate Content, Perform Bulk Load, etc.

After a server restart, you have now disabled the automatic setting of properties. You can either set them programmatically (and your changes will no longer get intercepted), or manually through the UI.

Edit Auditable properties

Edit Auditable properties

Note: This is an all or nothing operation – you can’t have it automatically set some properties but not others (to do it you’d have to do metadata extraction or some kind of event handler). This is why this is usually a temporary step as you are loading or migrating content.

After you are done with the content load, the next step is re-enable original model to ensure consistency. You also have a bunch of nodes that don’t have Auditable Aspect applied, so we need to reapply it.

Step 4 – Apply Auditable Aspect to Migrated Content

First, you need to revert to the original content model. To apply auditable aspect to many nodes, you typically write a JavaScript script that walks through the nodes and adds Auditable Aspect.

For applying Auditable aspect manually, you’ll have to add some code to CONFIGROOTweb-client-config.xml so it shows u in Add Aspect action.

<config evaluator=”string-compare” condition=”Action Wizards”>


<!– and the has-aspect condition –>

<aspects>
<aspect name=”generalclassifiable”/>

<aspect name=”complianceable”/>

<aspect name=”dublincore”/>

<aspect name=”auditable”/>

<aspect name=”effectivity”/>

<aspect name=”summarizable”/>

<aspect name=”versionable”/>

<aspect name=”templatable”/>

<aspect name=”emailed”/>

<aspect name=”emailserver:aliasable”/>

<aspect name=”taggable”/>

</aspects>

Another possible solution that might preclude this is to have a rule that automatically adds the Auditable aspect as you create nodes, but I haven’t tested that yet.

Step 5 – Restore original state of contentModel.xml and web-client-config-properties.xml

To get everything back to a consistent state, you now have to restore your original contentModel and UI properties. Because all nodes now have auditable aspect applied, as well as the properties that need to exist do in fact exist, Alfresco will now be able to pick it up from there and manage these properties the way it always does.

Dynamic Data-Driven Drop Downs for List Properties

Friday, August 8th, 2008

Dynamic Dropdowns

A customer request that comes up often is a need to create a property that is dynamically populated through drop down list. Alfresco has ability to set a pre-set list of drop-downs (i.e. apple, orange) through the LIST constraint, but there is no way to have list change dynamically.

For more on constraints, see this wiki page: http://wiki.alfresco.com/wiki/Constraints. Also, this post assumes some understanding of Component Generators – they are UI components that get dynamically rendered in the Document / Space details Property Sheet. See http://wiki.alfresco.com/wiki/Component_Generator_Framework.The code accompanying this article can be found on Community Developer Toolbox here.

There are several approaches to doing this, the first one is using the existing infrastructure and extend the List Constraints, and the second is to create your own component generator from scratch that generates the drop down the any way you like.
In this post, I’ll talk about reusing the existing list infrastructure. I built a custom constraint that receives a Lucene query and renders the drop down based on results of that query.

This allows you to model a node hierarchy somewhere and point to that hierarchy through the constraint. You can then maintain that hierarchy, adding or deleting nodes, which in turn will modify your drop down appropriately. For example, you can put nodes as a set of subspaces in the data dictionary, or use categories. A potentially useful artifact of this implementation is security filtering – if you choose to secure your nodes, you can have the drop downs render different for different people, based on what they have access to.

Creating SearchBasedListConstraint

The way list drop down works is that the TextFieldGenerator component generator has explicit code to detect the LIST constraint, and renders itself appropriately. It calls getAllowedValues method on the constraint to get the values the drop down should be populated with. I extended this ListConstraint, since if I implement the getAllowedValues to be dynamically, then the drop down should be dynamic.

In my implementation, I chose to separate the generic dynamic list capability from the Lucene specific capability. I have a class called SearchBasedListConstraint that extends ListOfValuesConstraint, and has an abstract method getSearchResult, which returns the list that will be passed on as AllowedValues. Implement this method in a subclass to be backed by anything you want, a file, a database, etc. Another thing i added
here is a reference to ServiceRegistry, so that Alfresco Repository Services could be called, such as SearchService, or NodeService, by subclases. There are a few other methods, which I will discuss in a later post.

public abstract class SearchBasedListConstraint extends ListOfValuesConstraint
{

@Override
public List<String> getAllowedValues()
{
List<String> allowedValues = getSearchResult();
super.setAllowedValues(allowedValues);
return allowedValues;
}

protected abstract List<String> getSearchResult() ;

There is a good forum post that talks a bit about implementing this as well.

http://forums.alfresco.com/en/viewtopic.php?f=4&t=11687

Dynamic DropDowns based on Lucene Searches

As far as an example implementation, I thought that allowing getSearchResult to be backed by a Lucene Query would be useful, since then you can store your data inside Alfresco. It also means that you can have dynamic querying based on content already in Alfresco, another potentially useful feature. So I subclass SearchBasedListconstraint with LuceneSearchBasedListConstraint. I’d like this to be parametrized and set from the content model (this is where the constraints are set), so I expose setQuery public method. Because the SearchService API requires a StoreRef to be passed, i introduce another setter called setStoreRef. I already have a reference to ServiceRegistry from superclass, so I can just start using it.

We are now ready to perform the search, the code currently is hardcoded to return the Name property.

protected List<String> getSearchResult()
{
if (logger.isDebugEnabled())
logger.debug(“Original Query ” + query);

StoreRef storeRef = new StoreRef(strStoreRef);
ResultSet resultSet = getServiceRegistry().getSearchService().query(storeRef, SearchService.LANGUAGE_LUCENE, finalQuery);
NodeService nodeSvc = getServiceRegistry().getNodeService();

List<String> allowedValues = new ArrayList<String>();
for (ResultSetRow row : resultSet)
{
allowedValues.add((String)nodeSvc.getProperty(row.getNodeRef(), ContentModel.PROP_NAME));
}

return allowedValues;
}

That’s all it takes. in the sample, there is some additional code to manage dependencies between dropdowns, which once again I will explain later.

Configuring Constraints

I am using the exampleModel that gets supplied with Alfresco in the extension directory, and added a couple of new fields – country and city. Here is the definition of constraint, note the parameter type query being set.

<constraint name=”my:customConstraint”
type=”org.alfresco.sample.constraints.LuceneSearchBasedListConstraint” >
<parameter name=”query”>
<value> TYPE:”{http://www.alfresco.org/model/content/1.0}content”
</value>
</parameter>
</constraint>

And now introduce this constraint to a property.

<property name=”my:country”>
<title>Country</title>
<type>d:text</type>
<index enabled=”true”>
<atomic>true</atomic>
<stored>false</stored>
<tokenised>false</tokenised>
</index>
<constraints>
<constraint ref=”my:customConstraint” />
</constraints>
</property>

Bootstrapping Alfresco Node Services

Here we have a bit of an issue. The constraints code does not by default get injected through Spring framework, it in fact gets dynamically instantiated through reflection. However, we need to use Alfresco’s foundation services in it, such as NodeService and SearchService, so we need a reference either to them, or to the ServiceRegistry. The solution is to initialize the Constraint from Spring through a use of static internal variables. Then you can use spring to inject ServiceRegistry to the setter, which in turn sets the internal static variable. From then any instances of the class will have access to the static variable.

Here is the code:

private static ServiceRegistry registry;

public ServiceRegistry getServiceRegistry()
{
return registry;
}

public void setServiceRegistry(ServiceRegistry registry)
{
SearchBasedListConstraint.registry = registry;
}

And now we can configure this, so that the Spring configuratoin takes care of setting the static variable.

<bean id=”LuceneSearchBasedListConstraintInitializer”
class=”org.alfresco.sample.constraints.LuceneSearchBasedListConstraint”>

<property name=”serviceRegistry”>
<ref bean=”ServiceRegistry”/>
</property>
</bean>

And the corresponding configuration:

<bean id=”LuceneSearchBasedListConstraintInitializer”
class=”org.alfresco.sample.constraints.LuceneSearchBasedListConstraint”>

<property name=”serviceRegistry”>
<ref bean=”ServiceRegistry”/>
</property>
</bean>

This should do it.

An even more interesting question is how to create dependencies between properties, i.e. so you can choose a country, and then cities of that country get populated automatically. This is part of the sample I provided, but will be explained in a future post.

Update 05/03/2009 – The Source code is now on the new share site (share.alfresco.com), in Developer Toolbox here.

Blogged with the Flock Browser


Alfresco Home | Legal | Privacy | Accessibility | Site Map | RSS  RSS

© 2012 Alfresco Software, Inc. All Rights Reserved.