cancel
Showing results for 
Search instead for 
Did you mean: 

Count downloads

maxmil
Champ in-the-making
Champ in-the-making
I am evaluating Alfresco for our company. One of our requirements is to be able to count the bytes of downloaded content on a per user basis.

From what i have understood this would require creating a custom aspect.

As far as documentation goes i haven't found a "definitive guide" on creating custom aspects, if one exists please let me know where i can find it.

From what i've seen in the "hit count" custom aspect example in the SDK this can be done by binding a class behaviour to some sort of service policy.

So i have two questions:

1) Am i on the right path?
2) Is there some service policy that represents the downloading of content that i can bind my custom behaviour to?

Thanks
9 REPLIES 9

cheffilet
Champ in-the-making
Champ in-the-making
Yes you could, but i dont know what happens as context informations left. I guess, the most secure(and easiest) way is to overwrite the BaseDownloadContentServlet.processDownloadRequest to store the bytes as an property upon a note.

maxmil
Champ in-the-making
Champ in-the-making
I did as you mentioned and it was pretty straight forward to do.

I would like to extend my counting of downloads to the ftp client. Any idea as to which class i would have to modify?

I don't like having had to overwrite alfresco classes but i couldn't see any other way to do this.

What i needed was some way to bind to the AbstractContentReader.getReader method, however as far as i can see alfresco only defines policies that you can bind to in public service classes.

Is it possible to define new policies and bind to existing classes?

I am a bit bemused by the documentation that i've found on the wiki, its helpful but seems to leave big gaps for you to fill in yourself by reading through the code.

Is there any other source of documentation on line or in print that you could recommend to me?

cheffilet
Champ in-the-making
Champ in-the-making
Hm well well. Okay, i am struggling with myself how can i answearing you in the right way. At the end its up to you on how can this behavoiour be implemented.

To fall back to your previous question regarding policies:

Yes a policy to obtain a content reader exists(so you have your binding):


ContentServicePolicies.OnContentReadPolicy

This interface is the right choice to count access to the content reader:

import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.ContentServicePolicies;
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.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;


public class ContentReadAccessEvent implements ContentServicePolicies.OnContentReadPolicy{

   private PolicyComponent policyComponent;
   
   public void setPolicyComponent(PolicyComponent policyComponent)
   {
      this.policyComponent = policyComponent;
   }
   
   public void init(){
      this.policyComponent.bindClassBehaviour(
            QName.createQName(NamespaceService.ALFRESCO_URI, "onContentRead"),
            ContentModel.TYPE_CONTENT,
                new JavaBehaviour(this, "onContentRead", Behaviour.NotificationFrequency.EVERY_EVENT));
   }
   
   @Override
   public void onContentRead(NodeRef nodeRef) {
      //do some things here
   }
}

But please reconsider: You are not secure with this solution whether a download will be performed as only the contentreader will be requested from a particular component!

Your asking about custom policies (my personal mention): The problem on defining custom policies is, that you must have access to the implementations of the Alfresco-API itself to bind event firings on particular places. This is not the right way.


But i guess you can make a more secured way to obtain the downloadcounts. This can be made via a proxy marshalled over the ContentService. An around aspect checks for accessing the method getContentInputStream method where you can make (morely) sure that content will be downloaded.

maxmil
Champ in-the-making
Champ in-the-making
Yep, i understand why i can't use the ContentServicePolicies.OnContentReadPolicy.

What i was wanting to bind to was the FileContentReader (which extends AbstractContentReader). This is the component that creates the input stream.

However i think i understand that this is not possible since this is a component, "a black box" as the wiki says and the only place you can plug behaviours into is in the public server layer.

If the input stream were obtained in the ContentService then i think, with your recommendations, a custom policy could be implemented. But as far as i can see the ContentService only gives you access to the reader not directly to the content stream (i can't see the method getContentInputStream in ContentService).

If i understand you correctly then your idea would be to create my own ContentService with the same methods as the current content service and add a method to return an input stream to the conent.

I could then, via the spring configuration, use my ContentService instead of the alfresco one and marshall all calls to the alfresco content service.

However i would still have to change all the code that uses the ContentService to use call my new getInputStream method wouldn't i?

Anyway, with the dirty hack on the download servlet i have satisfied my basic needs for the moment and this all sounds a bit more daunting than what i had expected so i think that for the moment i'll just stick with the dirty hack.

Thanks for your help, i've learnt quite a bit with this problem.

cheffilet
Champ in-the-making
Champ in-the-making
No i meant something like this(it works with Alfresco 2.2):

At first the beandefinition:


<bean id="contentServiceInterceptor" class="de.dmc.alfresco.configuration.ContentServiceInterceptor"/>
<bean id="readerInterceptor" class="de.dmc.alfresco.configuration.ContentReaderInterceptor"/>

<bean id="ContentService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>org.alfresco.service.cmr.repository.ContentService</value>
        </property>
        <property name="target">
            <ref bean="contentService"/>
        </property>
        <property name="interceptorNames">
            <list>
                <idref bean="ContentService_transaction"/>
                <idref bean="AuditMethodInterceptor"/>
                <idref bean="exceptionTranslator"/>
                <idref bean="mlContentInterceptor"/>
                <idref bean="ContentService_security"/>
                <idref bean="ContentService_security"/>
                <idref bean="contentServiceInterceptor"/>
            </list>
        </property>
    </bean>

Then the interceptor class (with a new proxy within):


package de.dmc.alfresco.configuration;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.alfresco.service.cmr.repository.ContentReader;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class ContentServiceInterceptor implements MethodInterceptor{

   class ContentReaderHandler implements InvocationHandler {
      
      private ContentReader cdr;
      
      public ContentReaderHandler(ContentReader cdr){
         this.cdr = cdr;
      }
      
      public Object invoke(Object proxy, Method method, Object[] args) {
         try {
            if(method.getName().startsWith("getContent")){
               //here we begin to count
            }
            System.out.println(method.getName());
            return method.invoke(cdr, args);
         } catch (Exception e) {
            throw new RuntimeException(e);
         }
      }
   }
   
   @SuppressWarnings("unchecked")
   @Override
   public Object invoke(MethodInvocation invocation) throws Throwable {
      if(invocation.getMethod().getName().equals("getReader")){
         Class cdrClass = Class.forName("org.alfresco.service.cmr.repository.ContentReader");
         return Proxy.newProxyInstance(cdrClass.getClassLoader(),
               new Class[] { cdrClass },
               new ContentReaderHandler((ContentReader)invocation.proceed()));
         
      }
      return invocation.proceed();
   }

}

Feel free to make comments or to ask for guidance Smiley Happy

It works, no codechange must be made (as you descriped) but please reconsider about caching documents.

maxmil
Champ in-the-making
Champ in-the-making
WOW, very clean.

I need to do some reading up on AOP but i can follow your example. Pretty potent stuff AOP when used with a proxy like this!

I'll have a go at implementing it next week and let you know the outcome.

You mentioned that this works in alfresco 2.2. I am using Labs 3. Is there any reason why it shouldn't work in this version?

What exactly is your warning about caching?

cheffilet
Champ in-the-making
Champ in-the-making
I had never the pleasure of using Alfresco 3 from implementors site as our customers uses Alfresco up to 2.2.2. I hope this will be changed in the near future as we have made some cool things with this "new" alfresco.

But i guess, it should work without greater problems when the api and AOP model have been changed in such a detail. Please looking for the ContentService bean definition as interceptors could be changed or added to have the recent version of it.

Caching means, that your browser could cache some already processed downloads (like small html/txt files stored backhands). Test it, i guess you should add the method "   getContentInputStream" to the second proxy.

maxmil
Champ in-the-making
Champ in-the-making
I have just tried integrating it with Alfresco 3 and it works fine. The interceptor definition of the Content service does not seem to have changed.

I am very happy with this solution, thanks for all your help.

In my case the browser cache won't be a problem. Our company is thinking of using a pricing model based on the bandwidth its clients use downloading content. I'm going to have a go at auditing the downloads and possibly integrating it with the auditing architecture used by alfresco.

cheffilet
Champ in-the-making
Champ in-the-making
Hm just like an content provider? Sounds good for me Smiley Happy