new Aspect, mimic of auditable: init fired not other methods

cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
jbaton
Member II

new Aspect, mimic of auditable: init fired not other methods

Jump to solution
Hi all,

I'm facing a problem on aspect creation (1.1 OS jboss bundle … rebuilded)

I've created a new aspect, it contains a sole date.
I need it to be initialised.
I made the corresponding class on the example of the AuditableAspect class.
The aspect appears in the UI, the init method is fired but not the onCreate/onUpdate/onAdd methods.

I've binded those methods in the same way that in the 'auditable' aspect.

I believe the problem is that the methods are 'badly' registered.
Looking at AuditableAspect.init() , I can not figure out what are the parameters of policyComponent.bindClassBehaviour().

The first QName is based on "http://www.alfresco.org" which does not appear in contentModel.xml where the auditable aspect is defined.

The second parameter, seems to use the namespace uri defined in the custom model (xpath like path == model/namespaces/namespace/uri)
The third parameter is a Behaviour instance.

How should one select the value of the first QName parameter ?

Thanks for your time.


Jerome


Here is the java code (excerp)

public void init() {
// Create behaviours
onCreateObsoDate    = new JavaBehaviour(this, "onCreateObsoDate");
onAddObsoDate       = new JavaBehaviour(this, "onAddObsoDate");
onUpdateObsoDate    = new JavaBehaviour(this, "onUpdateObsoDate");
       
QName ASPECT_OBSO = QName.createQName("http://www.toto.com/ged/obso", "obsolescence");
       
policyComponent.bindClassBehaviour(QName.createQName("http://www.toto.com/ged/obso", "onCreateNode"),ASPECT_OBSO, onCreateObsoDate);
      
      policyComponent.bindClassBehaviour(QName.createQName("http://www.toto.com/ged/obso", "onAddAspect"), ASPECT_OBSO, onAddObsoDate);
       
policyComponent.bindClassBehaviour(QName.createQName("http://www.toto.com/ged/obso", "onUpdateNode"), ASPECT_OBSO, onUpdateObsoDate);


Here is the custom model


<model name="gcSmiley Surprisedbso_model" xmlns="http://www.alfresco.org/model/dictionary/1.0">
   <description>custom Model for obsolescence date (GED-2)</description>
   <author></author>
   <version>1.0</version>

   <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"/>
   </imports>

   <namespaces>
      <!– Define a Namespace for my new definitions –>
      <namespace uri="http://www.toto.com/ged/obso" prefix="gc"/>
   </namespaces>

   <!– Type and Aspect definitions go here –>
   <types/>
   <aspects>
      <aspect name="gcSmiley Surprisedbsolescence">
         <title>Date d'obsolescence</title>
         <properties>
            <property name="gcSmiley Surprisedbso_date">
               <type>d:date</type>
            </property>
         </properties>
      </aspect>
   </aspects>
</model>
1 Solution

Accepted Solutions
rwetherall
Active Member II

Re: new Aspect, mimic of auditable: init fired not other methods

Jump to solution
Hi Ryan,

The behaviour implementations are bound to the policy using the method bindClassBehaviour.

Eg …



policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"),ASPECT_OBSO, onCreateObsoDate);       


The first parameter is the QName of the policy that you want to bind your behaviour to (or in other terms register interest in).  These policies are well defined elsewhere in the repository although we don't have a central registry of them all.  Something we should do perhaps.

The second parameter is the type restriction.  When the policy is fired the type and aspects of the node in question are looked at and if they match the type restriction we have specified then the policy behaviour will be executed otherwise it will not.  In this way we can add behaviour for specific aspects and types.  For example the auto-version behaviour is only executed for nodes that have the Versionable aspect applied.

The last parameter is the behaviour object, in this case it is a Java Behaviour pointing to a method in this class.  The behaviour can refer to any method in any class as long as it has the correct set of parameters that the policy is expecting.  There is no special prefix naming required.

Eg …



onCreateObsoDate =  new JavaBehaviour(this, "onCreateObsoDate");


In the future you will be able to specify a non Java behaviour so that behaviours can be implemented in other languages, for example Ruby.

Hope this clears things up a little,
Thanks,
Roy
11 Replies
rbramley
Member II

Re: new Aspect, mimic of auditable: init fired not other methods

Jump to solution
It looks as though your behaviours are created incorrectly.
I guess the JavaBehaviour method argument should probably match the NodeServicePolicies i.e. "onCreateNode", "onAddAspect", "onUpdateNode".

Does your class implement all 3 of those interfaces: OnCreateNodePolicy, OnAddAspectPolicy and OnUpdateNodePolicy?

Hope this helps - if not post all your code (or check out the Versionable Aspect).
rwetherall
Active Member II

Re: new Aspect, mimic of auditable: init fired not other methods

Jump to solution
Hi,

I think its the first parameter that you have incorrect.  This is the QName of the policy you are registering interest in.  In nearly all cases the repository policies are in the standard Alfresco namesspace.

So try this out instead …


public void init() {
// Create behaviours
onCreateObsoDate    = new JavaBehaviour(this, "onCreateObsoDate");
onAddObsoDate       = new JavaBehaviour(this, "onAddObsoDate");
onUpdateObsoDate    = new JavaBehaviour(this, "onUpdateObsoDate");
      
QName ASPECT_OBSO = QName.createQName("http://www.toto.com/ged/obso", "obsolescence");
      
policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"),ASPECT_OBSO, onCreateObsoDate);
     
      policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), ASPECT_OBSO, onAddObsoDate);
      
policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"), ASPECT_OBSO, onUpdateObsoDate);


For future reference the second parameter refers to the type or aspect that you want to bind this behaviour to.  In practise this means that the policy behaviour that you add will only be called for nodes that are of the specified type or have the specified aspect applied.

The last parameter is the behaviour to execute when the policy is triggered.  At the moment there is only a Java behaviour implementation, but in the future (when we get time!) there will be scripting behaviour implementations allowing behaviour to be implemented in other languages.

Since the behaviours and policies are loosely coupled you will not need to implement any of the policy interfaces in the class that contains the behaviour methods.

Hope this helps to clear things up.  If you look through the code for *Aspect.java files you should find some more examples of how we attach behaviour to various aspects.

Cheers,
Roy
jbaton
Member II

Re: new Aspect, mimic of auditable: init fired not other methods

Jump to solution
Hello Roy,

Thanks a lot (once again).

I'm glad to say it rolls, Roy.    (sorry, I'm an office clown :lol: )

And it's neat : model in its own file in extension dir.
(I had made it work previously with my aspect defined in the default model by Ctrl-C/Ctrl-V'ing even more AuditableAspect).

At start, I infered I had to replace all alfresco.org (and like) references to avoid repository confusion with other aspects that might come out of the box later.

I'll definetely have a look at the other aspect classes.

BTW, well done, as for what I've seen, the source is well commented


In order to help other users that might have the same problem , here comes some code that works. (sorry, schedule is too tight to add it to the wiki at the right place right now).

What it does is add a date to store when will the document be obsolete.
It is to be initialized to current day + 2 years.

the model

<model name="gcSmiley Surprisedbso_model" xmlns="http://www.alfresco.org/model/dictionary/1.0">
   <description>theypayme custom Model for obsolescence date (GED-2)</description>
   <author></author>
   <version>1.0</version>
   <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"/>
   </imports>
   <namespaces>
      <!– Define a Namespace for my new definitions –>
      <namespace uri="http://www.theypayme.com/ged/obso" prefix="gc"/>
   </namespaces>
   <!– Type and Aspect definitions go here –>
   <types/>
   <aspects>
      <aspect name="gcSmiley Surprisedbsolescence">
         <title>Date d'obsolescence</title>
         <properties>
            <property name="gcSmiley Surprisedbso_date">
               <type>d:date</type>
            </property>
         </properties>
      </aspect>
   </aspects>
</model>


the class

package com.theypayme.ged.aspects;

import java.util.Calendar;
import java.util.Date;

import org.alfresco.repo.policy.Behaviour;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.policy.PolicyScope;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ObsolescenceDateAspect {

   private static final Log logger = LogFactory.getLog(ObsolescenceDateAspect.class);

   // Dependencies
   private PolicyComponent policyComponent;
   private NodeService nodeService;
   private AuthenticationService authenticationService;

   private Behaviour onCreateObsoDate;
   private Behaviour onAddObsoDate;
    private Behaviour onUpdateObsoDate;

    public ObsolescenceDateAspect(){
       logger.error("In ObsolescenceDateAspect constructor !") ;
       // put a breakpoint here to check your aspect is registered at server start
    }
   
    public void init()
    {
        // Create behaviours
        onCreateObsoDate =  new JavaBehaviour(this, "onCreateObsoDate");
        onAddObsoDate    = new JavaBehaviour(this, "onAddObsoDate");
        onUpdateObsoDate = new JavaBehaviour(this, "onUpdateObsoDate");
      
        QName ASPECT_OBSO = QName.createQName("http://www.theypayme.com/ged/obso", "obsolescence");
       
        policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"),ASPECT_OBSO, onCreateObsoDate);
        policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), ASPECT_OBSO, onAddObsoDate);
        policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"), ASPECT_OBSO, onUpdateObsoDate);
    }
   
    /**
     * Maintain audit properties on creation of Node
     *
     * @param childAssocRef  the association to the child created
     */
    public void onCreateObsoDate(ChildAssociationRef childAssocRef)
    {
        NodeRef nodeRef = childAssocRef.getChildRef();
        onAddObsoDate(nodeRef, null);
    }
   
    /**
     * Maintain audit properties on addition of audit aspect to a node
     *
     * @param nodeRef  the node to which auditing has been added
     * @param aspect  the aspect added
     */
    public void onAddObsoDate(NodeRef nodeRef, QName aspect)
    {
        try
        {
            onUpdateObsoDate.disable();
           
            // Set created / updated date
            Date now = createDate();
            nodeService.setProperty(
                  nodeRef,
                  QName.createQName("http://www.theypayme.com/ged/obso", "obso_date"),
                  now
                  );
        }
        finally
        {
            onUpdateObsoDate.enable();
        }
    }

   
    private Date createDate(){
       Calendar cal = Calendar.getInstance() ;
       cal.add( Calendar.YEAR, 2 );
       return cal.getTime() ;
    }
   
    /**
     * Maintain audit properties on update of node
     *
     * @param nodeRef  the updated node
     */
    public void onUpdateObsoDate(NodeRef nodeRef)
    {
        // Set updated date
        Date now = createDate();

        nodeService.setProperty(
              nodeRef,
              QName.createQName("http://www.theypayme.com/ged/obso", "obso_date"),
              now
              );
    }

   public AuthenticationService getAuthenticationService() {
      return authenticationService;
   }

   public void setAuthenticationService(AuthenticationService authenticationService) {
      this.authenticationService = authenticationService;
   }

   public NodeService getNodeService() {
      return nodeService;
   }

   public void setNodeService(NodeService nodeService) {
      this.nodeService = nodeService;
   }

   public PolicyComponent getPolicyComponent() {
      return policyComponent;
   }

   public void setPolicyComponent(PolicyComponent policyComponent) {
      this.policyComponent = policyComponent;
   }


}


bean definition in application-context.xml

<bean
        id="obsolescenceAspect"
        class="com.globecast.ged.aspects.ObsolescenceDateAspect"
        init-method="init"
    >
        <property name="nodeService">
            <ref bean="nodeService" />
        </property>
        <property name="policyComponent">
            <ref bean="policyComponent" />
        </property>
        <property name="authenticationService">
            <ref bean="authenticationService" />
        </property>
    </bean>


One should not forget to modify
web-client-config.xml and web-client-config-edit.xml
too (as shown in the wiki)

RBramley, thank you for trying to solve my problem.


Jérôme
rberg
Member II

Re: new Aspect, mimic of auditable: init fired not other methods

Jump to solution
How does the repository know when and what methods to call on your custom aspect? I am assuming that onCreateObsoDate, onAddObsoDate, and onUpdateObsoDate aren't standard with Alfresco out of the box. 

Does it use the method prefix to make this determination? (e.g. onAdd…, onUpdate…, onCreate…). If this is the case, is there a discrete list of the available prefixes documented somewhere?

Thank you!
-Ryan
rwetherall
Active Member II

Re: new Aspect, mimic of auditable: init fired not other methods

Jump to solution
Hi Ryan,

The behaviour implementations are bound to the policy using the method bindClassBehaviour.

Eg …



policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"),ASPECT_OBSO, onCreateObsoDate);       


The first parameter is the QName of the policy that you want to bind your behaviour to (or in other terms register interest in).  These policies are well defined elsewhere in the repository although we don't have a central registry of them all.  Something we should do perhaps.

The second parameter is the type restriction.  When the policy is fired the type and aspects of the node in question are looked at and if they match the type restriction we have specified then the policy behaviour will be executed otherwise it will not.  In this way we can add behaviour for specific aspects and types.  For example the auto-version behaviour is only executed for nodes that have the Versionable aspect applied.

The last parameter is the behaviour object, in this case it is a Java Behaviour pointing to a method in this class.  The behaviour can refer to any method in any class as long as it has the correct set of parameters that the policy is expecting.  There is no special prefix naming required.

Eg …



onCreateObsoDate =  new JavaBehaviour(this, "onCreateObsoDate");


In the future you will be able to specify a non Java behaviour so that behaviours can be implemented in other languages, for example Ruby.

Hope this clears things up a little,
Thanks,
Roy
rberg
Member II

Re: new Aspect, mimic of auditable: init fired not other methods

Jump to solution
Thanks Roy!  That answered my question. I overlooked the QName binding to the node policies.
rberg
Member II

Re: new Aspect, mimic of auditable: init fired not other methods

Jump to solution
Follow up question:

I was able to create my new aspect, and succesfully define its behaviour. However, I am now having the problem that it is working a little TOO well.

If you refer to my code below:



package com.tsgrp.alfresco.aspects;

import java.util.StringTokenizer;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.policy.Behaviour;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;

/**
* Aspect that enables a content item to be subscribed to.
* Upon update of the content item, all subscribers will be notified.
*
* @author Ryan Berg (rberg@tsgrp.com)
* @version 1.0
*/
public class SubscribeableAspect
{
    //  Logger
    private static final Log logger = LogFactory.getLog(SubscribeableAspect.class);

    // Unknown user, for when authentication has not occured
    private static final String USERNAME_UNKNOWN = "unknown";
   
    // Custom QNames
    private static final QName ASPECT_SUBSCRIBEABLE = QName.createQName("http://www.tsgrp.com/alfresco/model/1.0", "subscribeable");
    private static final QName PROP_SUBSCRIBERS = QName.createQName("http://www.tsgrp.com/alfresco/model/1.0", "subscribers");
   
   
    // Dependencies
    private NodeService nodeService;
    private AuthenticationService authenticationService;
    private PolicyComponent policyComponent;
    private JavaMailSender mailSender;

    // Behaviours
    private Behaviour onUpdateSubscribeable;
   

    /**
     * @param nodeService  the node service to use for audit property maintenance
     */   
    public void setNodeService(NodeService nodeService)
    {
        this.nodeService = nodeService;
    }

    /**
     * @param policyComponent  the policy component
     */
    public void setPolicyComponent(PolicyComponent policyComponent)
    {
        this.policyComponent = policyComponent;
    }
   
    /**
     * @param authenticationService  the authentication service
     */
    public void setAuthenticationService(AuthenticationService authenticationService)
    {
        this.authenticationService = authenticationService;
    }
   
    /**
     * @param mailSender          The JavaMailSender to set.
     */
    public void setMailSender(JavaMailSender mailSender)
    {
       this.mailSender = mailSender;
    }
   
    /**
     * Initialise the Auditable Aspect
     */
    public void init()
    {
        // Create behaviours
        onUpdateSubscribeable = new JavaBehaviour(this, "onUpdateSubscribeable");
       
       
        // Bind behaviours to node policies
        policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"), ASPECT_SUBSCRIBEABLE, onUpdateSubscribeable);
       
    }

    /**
     * Maintain audit properties on update of node
     *
     * @param nodeRef  the updated node
     */
    public void onUpdateSubscribeable(NodeRef nodeRef)
    {
        String subscribers = (String)nodeService.getProperty(nodeRef, PROP_SUBSCRIBERS);
        String modifier = getUsername();
 
        NotificationInfo info = new NotificationInfo(modifier, nodeRef);
       
        StringTokenizer st = new StringTokenizer(subscribers, ",");
       
        while(st.hasMoreTokens())
        {
            notifySubscriber(st.nextToken(), info);
        }
       
        if (logger.isDebugEnabled())
            logger.debug("Subscribeable node " + nodeRef + " updated [notified=" + subscribers + "]");
    }

    /**
     * @return  the current username (or unknown, if unknown)
     */
    private String getUsername()
    {
        String currentUserName = authenticationService.getCurrentUserName();
        if (currentUserName != null)
        {
           return currentUserName;
        }
        return USERNAME_UNKNOWN;
    }
   
    /**
     * Send an e-mail to a subscriber
     * @param subscriber - email address of subscriber
     * @param info - notification information
     */
    private void notifySubscriber(String subscriber, NotificationInfo info)
    {
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        simpleMailMessage.setTo(subscriber);
        simpleMailMessage.setSubject("*** Alfresco Subscription Alert: " + info.getName());
       
        StringBuilder buf = new StringBuilder(256);
       
        buf.append("An item you are subscribed to has been modified.\r\n\r\n");
        buf.append("PATH:        " + info.getPath() + "/" + info.getName() + "\r\n");
        buf.append("MODIFIED BY: " + info.getModifier() + "\r\n");
       
        simpleMailMessage.setText(buf.toString());
        simpleMailMessage.setFrom("rberg@tsgrp.com");
       
        try
        {
           // Send the message
           this.mailSender.send(simpleMailMessage);
        }
        catch (Throwable e)
        {
           // don't stop the action but let admins know email is not getting sent
           logger.error("Failed to send subscription alert email to " + subscriber, e);
        }
    }
   
   
    /**
     * Simple class which contains the notification information.
     * Data member values are initialized by passing the node reference
     * into the constructor.
     */
    private class NotificationInfo
    {
        private String modifier;
        private String name;
        private String path;
       
        public NotificationInfo(String modifier, NodeRef nodeRef)
        {
            setModifier(modifier);
            setName((String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME));
            setPath((String)nodeService.getPath(nodeRef).toDisplayPath(nodeService));   
        }

        private void setModifier(String modifier)
        {
            this.modifier = modifier;
        }

        public String getModifier()
        {
            return modifier;
        }

        private void setName(String name)
        {
            this.name = name;
        }

        public String getName()
        {
            return name;
        }

        private void setPath(String path)
        {
            this.path = path;
        }

        public String getPath()
        {
            return path;
        }
    }
}


You can see that I would like an e-mail sent to each subscriber everytime content that has this aspect is updated.  

When I test this functionality, by either updating a property, or editing the content, several e-mails actually get sent to each subscriber, instead of just one.

Do you know what could be causing this behaviour?  Perhaps listening for onNodeUpdate is too fine-grained because actually several nodes (or children nodes of the content node) are updated per my one conceptual update?

Thanks in advance.
-Ryan
davidc
Active Member II

Re: new Aspect, mimic of auditable: init fired not other methods

Jump to solution
Ryan,

Yes, it's working too well, but it's the default behaviour for now.  What's happening is that every node update during the transaction is triggering your onUpdateSubscribeable method.

For v1.2, we're adding the notion of a transaction-level policy, which means you'll only get the call-back once in the transaction.

However, in the mean-time you can simulate transaction level policies with the following work-around.

You can bind any resource (object) to the current transaction using AlfrescoTransactionSupport.bindResource().  So, for your aspect, you could bind a resource that holds a list of notifications that need to be sent.  In onUpdateSubscribeable you can write something like…

List notifications = AlfrescoTransactionSupport.getResource("SubscribeableAspect.notifications");
if (notifications == null)
{
   notifications = new List();
AlfrescoTransactionSupport.bindResource("SubscribeableAspect.notification",  notifications);
}

Then, you can record your notifications in the list.  The next step is to be notified that the transaction has completed, so you can actually send the e-mails.  This is performed as follows in OnUpdateSubscribeable…

AlfrescoTransactionSupport.bindListener(this);

Your SubscribeableAspect supports the interface TransactionListener which has a call-back called afterCommit().  In this method you can use AlfrescoTransactionSupport.getResource() to retrieve your notification list and send-emails.  The list is automatically unbound from the transaction by Alfresco at the end of transaction processing.

As I say, v1.2 will provide this support in a much simpler way!
sjeek
Member II

Re: new Aspect, mimic of auditable: init fired not other methods

Jump to solution
would this function (subscription) be available in the next version of alfresco?