An Alfresco Developer’s Challenge and Journey

The require­ments posed were clear, so to come up with a tech­ni­cal imple­men­ta­tion of the solu­tion, we first scetched the new invi­ta­tion page :
Screeshot of customized Alfresco Site Invitation Screen

Easy so far, so lets con­tinue trac­ing the process through the code. The Share part should boil down to some basic cus­tomiza­tions of the Invitee list web­script, end­ing up with a JSON-/REST style call to the repo.

So far, so good. Con­tin­u­ing with the repos­i­tory part, exam­in­ing the data web­script imple­men­ta­tion org/alfresco/repository/site/invitation/invitation.post.json.js, I found it reads:


invitation = site.inviteNominated(inviteeFirstName,
 inviteeLastName, inviteeEmail, inviteeRoleName,
 serverPath, acceptUrl, rejectUrl);

Ok, now :

  • Where do I go with the addi­tional header (Disposition-Notification-To) and the optional cus­tom mail text ?
  • Where is the freemarker tem­plate and how to I make the mail HTML instead of the default plain text ?
  • How deep is the rabbit-hole ?

A stack­trace down to the Java­mail API should give us some insight:


at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at com.sun.mail.util.TraceInputStream.read(TraceInputStream.java:97)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
at java.io.BufferedInputStream.read(BufferedInputStream.java:237)
- locked <0x00000000f44e8690> (a java.io.BufferedInputStream)
at com.sun.mail.util.LineInputStream.readLine(LineInputStream.java:75)
at com.sun.mail.smtp.SMTPTransport.readServerResponse(SMTPTransport.java:1440)
at com.sun.mail.smtp.SMTPTransport.issueSendCommand(SMTPTransport.java:1376)
at com.sun.mail.smtp.SMTPTransport.mailFrom(SMTPTransport.java:959)
at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:583)
- locked <0x00000000f44e5540> (a com.sun.mail.smtp.SMTPTransport)
at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:402)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:341)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:356)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:345)
at org.alfresco.repo.action.executer.MailActionExecuter.executeImpl(MailActionExecutor.java:439)
at org.alfresco.repo.action.executer.ActionExecuterAbstractBase.execute(ActionExecuterAbstractBase.java:133)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.alfresco.repo.management.subsystems.SubsystemProxyFactory$1.invoke(SubsystemProxyFactory.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy209.execute(Unknown Source)
at org.alfresco.repo.action.ActionServiceImpl.directActionExecution(ActionServiceImpl.java:749)
at org.alfresco.repo.action.ActionServiceImpl.executeActionImpl(ActionServiceImpl.java:675)
at org.alfresco.repo.action.ActionServiceImpl.executeAction(ActionServiceImpl.java:540)
at org.alfresco.repo.action.ActionServiceImpl.executeAction(ActionServiceImpl.java:526)
at org.alfresco.repo.action.ActionServiceImpl.executeAction(ActionServiceImpl.java:758)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.alfresco.repo.security.permissions.impl.AlwaysProceedMethodInterceptor.invoke(AlwaysProceedMethodInterceptor.java:34)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.alfresco.repo.security.permissions.impl.ExceptionTranslatorMethodInterceptor.invoke(ExceptionTranslatorMethodInterceptor.java:44)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.alfresco.repo.audit.AuditMethodInterceptor.proceed(AuditMethodInterceptor.java:160)
at org.alfresco.repo.audit.AuditMethodInterceptor.invoke(AuditMethodInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:107)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy34.executeAction(Unknown Source)
at org.alfresco.repo.invitation.site.InviteSender.sendMail(InviteSender.java:117)
at org.alfresco.repo.invitation.site.SendInviteAction.execute(SendInviteAction.java:82)
at org.jbpm.graph.def.Action.execute(Action.java:129)
at org.jbpm.graph.def.GraphElement.executeAction(GraphElement.java:284)
at org.jbpm.graph.def.GraphElement.executeActions(GraphElement.java:241)
at org.jbpm.graph.def.GraphElement.fireAndPropagateEvent(GraphElement.java:213)
at org.jbpm.graph.def.GraphElement.fireEvent(GraphElement.java:196)
at org.jbpm.graph.def.Transition.take(Transition.java:152)
at org.jbpm.graph.def.Node.leave(Node.java:479)
at org.jbpm.graph.node.StartState.leave(StartState.java:82)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitializer.java:157)
at org.jbpm.graph.def.Node$$EnhancerByCGLIB$$d588864a.leave(<generated>)
at org.jbpm.graph.exe.Token.signal(Token.java:223)
at org.jbpm.graph.exe.Token.signal(Token.java:188)
at org.jbpm.taskmgmt.exe.TaskInstance.end(TaskInstance.java:495)
at org.alfresco.repo.workflow.jbpm.WorkflowTaskInstance.end(WorkflowTaskInstance.java:135)
at org.jbpm.taskmgmt.exe.TaskInstance.end(TaskInstance.java:436)
at org.alfresco.repo.workflow.jbpm.JBPMEngine$26.doInJbpm(JBPMEngine.java:1815)
at org.springmodules.workflow.jbpm31.JbpmTemplate$1.doInHibernate(JbpmTemplate.java:87)
at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:406)
at org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:339)
at org.springmodules.workflow.jbpm31.JbpmTemplate.execute(JbpmTemplate.java:80)
at org.alfresco.repo.workflow.jbpm.JBPMEngine.endTask(JBPMEngine.java:1780)
at org.alfresco.repo.workflow.WorkflowServiceImpl.endTask(WorkflowServiceImpl.java:648)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.alfresco.repo.security.permissions.impl.AlwaysProceedMethodInterceptor.invoke(AlwaysProceedMethodInterceptor.java:34)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.alfresco.repo.security.permissions.impl.ExceptionTranslatorMethodInterceptor.invoke(ExceptionTranslatorMethodInterceptor.java:44)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.alfresco.repo.audit.AuditMethodInterceptor.proceed(AuditMethodInterceptor.java:160)
at org.alfresco.repo.audit.AuditMethodInterceptor.invoke(AuditMethodInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:107)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy68.endTask(Unknown Source)
at org.alfresco.repo.invitation.InvitationServiceImpl.startNominatedInvite(InvitationServiceImpl.java:1345)
at org.alfresco.repo.invitation.InvitationServiceImpl.inviteNominated(InvitationServiceImpl.java:228)
at org.alfresco.repo.invitation.InvitationServiceImpl.inviteNominated(InvitationServiceImpl.java:213)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.alfresco.repo.security.permissions.impl.AlwaysProceedMethodInterceptor.invoke(AlwaysProceedMethodInterceptor.java:34)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.alfresco.repo.security.permissions.impl.ExceptionTranslatorMethodInterceptor.invoke(ExceptionTranslatorMethodInterceptor.java:44)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.alfresco.repo.audit.AuditMethodInterceptor.proceedWithAudit(AuditMethodInterceptor.java:217)
at org.alfresco.repo.audit.AuditMethodInterceptor.proceed(AuditMethodInterceptor.java:184)
at org.alfresco.repo.audit.AuditMethodInterceptor.invoke(AuditMethodInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:107)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at $Proxy96.inviteNominated(Unknown Source)
at org.alfresco.repo.site.script.Site.inviteNominated(Site.java:643)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.mozilla.javascript.MemberBox.invoke(MemberBox.java:155)
at org.mozilla.javascript.NativeJavaMethod.call(NativeJavaMethod.java:243)
at org.mozilla.javascript.optimizer.OptRuntime.callN(OptRuntime.java:86)
at org.mozilla.javascript.gen.c6._c1(file:.../webscripts/org/alfresco/repository/site/invitation/invitation.post.json.js:130)
at org.mozilla.javascript.gen.c6.call(file:.../webscripts/org/alfresco/repository/site/invitation/invitation.post.json.js)
at org.mozilla.javascript.optimizer.OptRuntime.callName0(OptRuntime.java:108)
at org.mozilla.javascript.gen.c6._c0(file:.../webscripts/org/alfresco/repository/site/invitation/invitation.post.json.js:141)
at org.mozilla.javascript.gen.c6.call(file:.../webscripts/org/alfresco/repository/site/invitation/invitation.post.json.js)
at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:393)
at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:2834)
at org.mozilla.javascript.gen.c6.call(file:.../webscripts/org/alfresco/repository/site/invitation/invitation.post.json.js)
at org.mozilla.javascript.gen.c6.exec(file:.../webscripts/org/alfresco/repository/site/invitation/invitation.post.json.js)
at org.alfresco.repo.jscript.RhinoScriptProcessor.executeScriptImpl(RhinoScriptProcessor.java:472)
at org.alfresco.repo.jscript.RhinoScriptProcessor.execute(RhinoScriptProcessor.java:190)
at org.alfresco.repo.processor.ScriptServiceImpl.executeScript(ScriptServiceImpl.java:282)
at org.alfresco.repo.web.scripts.RepositoryScriptProcessor.executeScript(RepositoryScriptProcessor.java:102)
at org.springframework.extensions.webscripts.AbstractWebScript.executeScript(AbstractWebScript.java:981)
at org.springframework.extensions.webscripts.DeclarativeWebScript.execute(DeclarativeWebScript.java:86)
at org.alfresco.repo.web.scripts.RepositoryContainer$2.execute(RepositoryContainer.java:383)
at org.alfresco.repo.transaction.RetryingTransactionHelper.doInTransaction(RetryingTransactionHelper.java:381)
at org.alfresco.repo.web.scripts.RepositoryContainer.transactionedExecute(RepositoryContainer.java:436)
at org.alfresco.repo.web.scripts.RepositoryContainer.transactionedExecuteAs(RepositoryContainer.java:466)
at org.alfresco.repo.web.scripts.RepositoryContainer.executeScript(RepositoryContainer.java:304)
at org.springframework.extensions.webscripts.AbstractRuntime.executeScript(AbstractRuntime.java:333)
at org.springframework.extensions.webscripts.AbstractRuntime.executeScript(AbstractRuntime.java:189)
at org.springframework.extensions.webscripts.servlet.WebScriptServlet.service(WebScriptServlet.java:118)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.alfresco.web.app.servlet.GlobalLocalizationFilter.doFilter(GlobalLocalizationFilter.java:58)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:555)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:852)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:662)

The essence here is :

  • The Javascript based web­script imple­men­ta­tion calls a method org.alfresco.repo.site.script.Site.inviteNominated
  • A BPM task-transition trig­gers the exe­cu­tion of a cus­tom (BPM) Action org.alfresco.repo.invitation.site.SendInviteAction
  • The ActionService then invokes an (Alfresco) Action org.alfresco.repo.action.executer.MailActionExecuter which finally gets down to the Java­mail API send­ing out the email

Some­where in between, the TemplateService (Freemarker) is used to gen­er­ate the mail body.

Solu­tion

Dis­claimer: The solu­tion intro­duced here surely does not qual­ify as Alfresco Best Prac­tice from an aca­d­e­m­i­cal per­spec­tive. Speak­ing from expe­ri­ence, this is a real excep­tion and not the com­mon case. Its con­trary to my engi­neer­ing desire cre­at­ing ele­gant, long-lived solu­tions with high edu­ca­tional value. I really tried hard to do it in a more clean fash­ion. But in the end – I think given bound­ary con­di­tions jus­tify the approach.

That said, the Alfresco Share part indeed proved to be as sim­ple as expected, so I’ll skip it here and start right away with the repository.

The first thing imple­mented was a small repos­i­tory script exten­sion scriptableUtils basi­cally wrap­ping org.springframework.extensions.webscripts.ScriptableUtils to get URL-encoding func­tion­al­ity in Javascript, and some slight tweaks to invitation.post.json.js shipped:

var inviteeEmail = json.get("inviteeEmail");
var allInOne;
if (json.has("inviteeMsg")) {
  allInOne = acceptUrl + "\n" +
    scriptableUtils.urlEncode(json.get("inviteeMsg"));
} else {
  allInOne = acceptUrl;
}
invitation = site.inviteNominated(inviteeFirstName,
 inviteeLastName, inviteeEmail, inviteeRoleName,
 serverPath, allInOne, rejectUrl);

Yes, I’m rout­ing mul­ti­ple (log­i­cal) strings through allInOne (for­merly known as acceptUrl) to get the data down the hole (The alter­na­tive I con­sid­ered was using a ThreadLocal).

The abused string comes back into play in InviteSender.buildArgs where the freemarker model is set up. To han­dle it accord­ingly, I had to pro­vide cus­tom imple­men­ta­tions of InviteSender and SendInviteAction. The main pur­pose of the lat­ter being to invoke the for­mer. The sig­nif­i­cant changes to InviteSender where


private Map<String, String> buildArgs(Map<String,
 String> properties, NodeRef inviter, NodeRef invitee)
{
    // unchanged before
    String[] hack = properties.get(wfVarAcceptUrl).split("\n");

    String acceptLink = serverPath + hack[0] + params;
    String inviterMsg = null;
    if (hack.length > 1 && hack[1].trim().length() > 0) {
    	inviterMsg = URLDecoder.decode(hack[1]);
    }
    // unimportant stuff not shown
    args.put("acceptLink", acceptLink);
    if (inviterMsg != null) {
    	args.put("inviterMsg", inviterMsg);
    }
    // unimportant stuff not shown
    return args;
}

along with an adjust­ment of the path to the tem­plate (to address the HTML tem­plate require­ment), and the intro­duc­tion of a hint about the disposition-notification header:

public void sendMail(Map<String, String> properties)
{
    // unimportant stuff not shown
    mail.setParameterValue(MailActionExecutor.PARAM_DISP_TO,
                           getEmail(inviter));

Thanks to Alfresco being Open Source and 4.0 ship­ping with an HTML ver­sion of the invi­ta­tion, I had some code to peek at.

Fur­ther down, the email had to be made aware of the new text/html content-type and Java­mail had some­how had to apply the new SMTP Header. My Imple­men­ta­tion went into a cus­tom MailActionExecutor derived from the one pro­vided by Alfresco.

public void sendMail(Map<String, String> properties)
{
// unimportant stuff not shown
boolean isHTML = false;
String htmlPrefix = "<html";
if (text.length() >= htmlPrefix.length() &&
text.substring(0, htmlPrefix.length()).equalsIgnoreCase(htmlPrefix))
{
    isHTML = true;
}
// unimportant stuff not shown
message.setText(text, isHTML);
// unimportant stuff not shown
String dispTo = (String) ruleAction.getParameterValue(PARAM_DISP_TO);
if (dispTo != null) {
    mimeMessage.setHeader("Disposition-Notification-To"
                , dispTo);
}

The final pieces of the puz­zle where minor cus­tomiza­tions to Spring-Context XML and the jBPM process def­i­n­i­tion, to get the cus­tom code executed:

<start-state name="start">
   <task name="inwf:inviteToSiteTask" swimlane="initiator" />
   <transition name="sendInvite" to="invitePending">
      <action class="de.customer.alfresco.repo.invitation.site.SendInviteAction" />
   </transition>
</start-state>
<bean id="de_customer_alfresco_module_workflowBootstrap" parent="workflowDeployer">
<property name="workflowDefinitions">
<list>
  <props>
  <prop key="engineId">jbpm</prop>
  <prop key="location">alfresco/module/de_cu_alfresco/workflow/cu-invitation-nominated_processdefinition.xml</prop>
  <prop key="mimetype">text/xml</prop>
  <prop key="redeploy">true</prop>
</props>
</list>
</property>

The last hur­dle was fig­ur­ing out how to over­ride the default MailActionExecutor, as this Spring bean lives in a child-application con­text. In this case (the email sub­sys­tem), the class­path loca­tion had to match classpath*:alfresco/extension/subsystems/email/OutboundSMTP/outbound/*-context.xml

Thats it :

Trust Me, I’m an “Engineer”

Thanks for read­ing – I hope you had a pleas­ant flight !

PS : Flames or alter­na­tive ideas are welcome.

Ref­er­ences:

 
Dieser Eintrag wurde veröffentlicht in Alfresco, Spring und verschlagwortet mit , von Andreas Steffan. Permanenter Link zum Eintrag.

Über Andreas Steffan

Freelance Java-Platform and Content-Management Architect / Web-Geek / Code-Mixer / Alfresco-, Grails and Linux Evangelist / Groovy- and Clojure-Fanboy / Javascript-and Wordpress-Cherrypicker / Scala-Sceptic / Emacs-Veteran / Content-Gourmet and -Cook / Agilo / Conference-Tourist / Physicist / Father / Japanese Kitchen Explorer / BBQ-Chef / Wine-Drinker / Photographer-Wannabe / Elektronica- and Frisbee-Friend / Hammock- and Backyard-Chiller / Asia-, Outdoor and Scuba-Diving-Traveller

7 Gedanken zu “An Alfresco Developer’s Challenge and Journey

  1. Hi Andreas,
    mmmh — really dirty, isn’t it?
    mod­i­fy­ing the processs­de­f­i­n­i­tion is OK, but why didn’t you add a tiny Javascript exten­sion to replace org.alfresco.repo.site.script.Site.inviteNominated?

  2. Hi Jan,

    I tried to make clear that the solu­tion intro­duced may not be the most beau­ti­ful one in the world and I’m not proud of it.

    org.alfresco.repo.site.script.Site.inviteNominated calls

    pub­lic Nom­i­nate­d­In­vi­ta­tion inviteNom­i­nated(
    String invi­teeUser­Name,
    Invitation.ResourceType resource­Type,
    String resource­Name,
    String invi­tee­Role,
    String server­Path,
    String accep­tUrl,
    String rejectUrl) ;

    defined by Invi­ta­tion­Ser­vice (inter­face) imple­mented in Invi­ta­tion­Ser­vi­ceImpl. Para­me­ters seman­tics look fairly strict to me and I don’t see a clean way to get the addi­tional infor­ma­tion through here.

    Or are you sug­gest­ing to bypass InvitationService ?

  3. Hi,
    no, not bypass­ing:
    1.) cus­tom JS exten­sion with a method appro­pri­ate to your para­me­ters & calls Cus­tom­Site­Ser­vi­ceImpl (= bean site­Ser­vice)
    2) Cus­tom­Site­Ser­vice exten­tends Site­Ser­vi­ceImpl & declares cus­tom inviteNom­i­nated method
    3.) Cus­tom­Site­Ser­vi­ceImpl extends Site­Ser­vi­ceImpl, imple­ments Cus­tom­Site­Ser­vice with imple­mented cus­tom invi­ta­tion logic…

  4. Ok, but pro­vid­ing a cus­tom inviteNom­i­nated method effec­tively bypasses dec­la­ra­tions of the inter­face, right ?

    That would have been fine with me in this case — Javascript does not care about inter­faces any­ways. I even con­sid­ered doing this in the beginning.

    Abon­doned the idea because I felt bypass­ing an inter­face is ugly and con­fus­ing. :) Besides the method doing the heavy-lifting in Invi­ta­tion­Ser­vi­ceImpl (as shipped) is

    pri­vate Nom­i­nate­d­In­vi­ta­tion startNominatedInvite(…)

    It has almost the same para­me­ters as inviteNom­i­nated and rougly 250 LOC, so this class does not really make a good can­di­date for subclassing.

    That was the time I con­cluded it would be over­all eas­ier and safer to abuse a String para­me­ter. :) I chose accep­tUrl because I was fairly sure, its only pur­pose is to get ren­dered into the email.

    Either way, at this point you are only half way down the rabbit-hole, you still have to pass the BPM layer, ren­der the mail text, and set head­ers and content-type.

    Thanks for commenting.

  5. Nice exposé about the draw­backs of a pretty hard-set/-coded inter­face and work­flow. This reminds me of the efforts that we once had to put into adding cus­tom per­mis­sions to an Alfresco sys­tem, restrict­ing what kind of site roles could invite other users using role-based restricted lists of invi­tee roles to choose from.

    Con­sid­er­ing the project para­me­ters you explained I find it an accept­able solu­tion. In terms of using the accep­tUrl as your “trans­port medium” I would tend towards using the trans­ac­tion con­text instead (Alfres­co­Trans­ac­tion­Sup­port instead of the men­tioned ThreadLocal).

    I per­son­ally do not like cus­tom exten­sions of Alfresco ser­vices as I have seen too many exam­ples that vio­late inter­nal ser­vice con­tracts. Facad­ing in com­bi­na­tion with trans­ac­tion con­text data or imple­ment­ing a con­tributable patch to the OOTB ser­vice are my pre­ferred options. In terms of the Invi­ta­tion­Ser­vice I could see a sim­ple API exten­sion which would allow pass­ing of a cus­tom data / prop­erty map, pro­vide a “map-to-workflow map­per strat­egy” and a “mail / noti­fi­ca­tion action” exten­sion point (del­e­gat­ing based on site type / state evaluators).

  6. Thanks for your feed­back, Axel.

    Funny, it reminds you of secu­rity related efforts you had. I am right now fac­ing require­ments to extend the Share Site secu­rity model. :)

    For the par­tic­u­lar case of the Invi­ta­tion­Ser­vice, it might indeed make sense to widen the tight inter­face we have today and work out a longer term solu­tion. But an approach like that is of course not fea­si­ble in most real world projects.

    Hon­estly — the more I think about it, the more I real­ize that “hacks” in gen­eral (i.e. not only in Alfresco envi­ron­ments) are actu­ally fairly com­mon. Truth is, that I am now exer­cis­ing all kinds of nasty Javascript while imple­ment­ing the new secu­rity requirements.

    In the end, I guess a big share of these prob­lems boils down to the nature of the tools we are given — the Java type sys­tem, acces­si­bil­ity mod­i­fiers (pri­vate, pack­age, final) and so on. For me, these tend to get in the way quite often. That is why I like “tools” such as dynam­i­cally typed lan­guages in cus­tomiza­tion projects.

    Maybe I’ll start a “best hacks” or “worst prac­tice” post series. ;)

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

*

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>