An Alfresco Developer’s Challenge and Journey

The requirements posed were clear, so to come up with a technical implementation of the solution, we first scetched the new invitation page :
Screeshot of customized Alfresco Site Invitation Screen

Easy so far, so lets continue tracing the process through the code. The Share part should boil down to some basic customizations of the Invitee list webscript, ending up with a JSON-/REST style call to the repo.

So far, so good. Continuing with the repository part, examining the data webscript implementation 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 additional header (Disposition-Notification-To) and the optional custom mail text ?
  • Where is the freemarker template and how to I make the mail HTML instead of the default plain text ?
  • How deep is the rabbit-hole ?

A stacktrace down to the Javamail 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()
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 webscript implementation calls a method org.alfresco.repo.site.script.Site.inviteNominated
  • A BPM task-transition triggers the execution of a custom (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 Javamail API sending out the email

Somewhere in between, the TemplateService (Freemarker) is used to generate the mail body.

Solution

Disclaimer: The solution introduced here surely does not qualify as Alfresco Best Practice from an academical perspective. Speaking from experience, this is a real exception and not the common case. Its contrary to my engineering desire creating elegant, long-lived solutions with high educational value. I really tried hard to do it in a more clean fashion. But in the end – I think given boundary conditions justify the approach.

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

The first thing implemented was a small repository script extension scriptableUtils basically wrapping org.springframework.extensions.webscripts.ScriptableUtils to get URL-encoding functionality 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 routing multiple (logical) strings through allInOne (formerly known as acceptUrl) to get the data down the hole (The alternative I considered was using a ThreadLocal).

The abused string comes back into play in InviteSender.buildArgs where the freemarker model is set up. To handle it accordingly, I had to provide custom implementations of InviteSender and SendInviteAction. The main purpose of the latter being to invoke the former. The significant changes to InviteSender where


private Map buildArgs(Map 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 adjustment of the path to the template (to address the HTML template requirement), and the introduction of a hint about the disposition-notification header:

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

Thanks to Alfresco being Open Source and 4.0 shipping with an HTML version of the invitation, I had some code to peek at.

Further down, the email had to be made aware of the new text/html content-type and Javamail had somehow had to apply the new SMTP Header. My Implementation went into a custom MailActionExecutor derived from the one provided by Alfresco.

public void sendMail(Map properties)
{
// unimportant stuff not shown
boolean isHTML = false;
String htmlPrefix = "= 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 puzzle where minor customizations to Spring-Context XML and the jBPM process definition, to get the custom code executed:


   
   
      
   




  
  jbpm
  alfresco/module/de_cu_alfresco/workflow/cu-invitation-nominated_processdefinition.xml
  text/xml
  true



The last hurdle was figuring out how to override the default MailActionExecutor, as this Spring bean lives in a child-application context. In this case (the email subsystem), the classpath location had to match classpath*:alfresco/extension/subsystems/email/OutboundSMTP/outbound/*-context.xml

Thats it :

Trust Me, I’m an “Engineer”

Thanks for reading – I hope you had a pleasant flight !

PS : Flames or alternative ideas are welcome.

References:

Seiten: 1 2

Andreas Steffan
Pragmatic ? Scientist and DevOps Mind @ Contentreich. Believes in Open Source, the Open Web and Linux. Freelancing in DevOps-, Cloud-, Kubernetes, JVM- and Contentland and speaks Clojure, Kotlin, Groovy, Go, Python, JavaScript, Java, Alfresco and WordPress. Built infrastructure before it was cool. ❤️ Emacs.

8 thoughts on “An Alfresco Developer’s Challenge and Journey”

  1. Hi Andreas,
    mmmh – really dirty, isn’t it?
    modifying the processsdefinition is OK, but why didn’t you add a tiny Javascript extension to replace org.alfresco.repo.site.script.Site.inviteNominated?

  2. Hi Jan,

    I tried to make clear that the solution introduced may not be the most beautiful one in the world and I’m not proud of it.

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

    public NominatedInvitation inviteNominated(
    String inviteeUserName,
    Invitation.ResourceType resourceType,
    String resourceName,
    String inviteeRole,
    String serverPath,
    String acceptUrl,
    String rejectUrl) ;

    defined by InvitationService (interface) implemented in InvitationServiceImpl. Parameters semantics look fairly strict to me and I don’t see a clean way to get the additional information through here.

    Or are you suggesting to bypass InvitationService ?

  3. Hi,
    no, not bypassing:
    1.) custom JS extension with a method appropriate to your parameters & calls CustomSiteServiceImpl (= bean siteService)
    2) CustomSiteService extentends SiteServiceImpl & declares custom inviteNom­i­nated method
    3.) CustomSiteServiceImpl extends SiteServiceImpl, implements CustomSiteService with implemented custom invitation logic…

  4. Ok, but providing a custom inviteNom­i­nated method effectively bypasses declarations of the interface, right ?

    That would have been fine with me in this case – Javascript does not care about interfaces anyways. I even considered doing this in the beginning.

    Abondoned the idea because I felt bypassing an interface is ugly and confusing. :) Besides the method doing the heavy-lifting in InvitationServiceImpl (as shipped) is

    private NominatedInvitation startNominatedInvite(…)

    It has almost the same parameters as inviteNominated and rougly 250 LOC, so this class does not really make a good candidate for subclassing.

    That was the time I concluded it would be overall easier and safer to abuse a String parameter. :) I chose acceptUrl because I was fairly sure, its only purpose is to get rendered 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, render the mail text, and set headers and content-type.

    Thanks for commenting.

  5. Nice exposé about the drawbacks of a pretty hard-set/-coded interface and workflow. This reminds me of the efforts that we once had to put into adding custom permissions to an Alfresco system, restricting what kind of site roles could invite other users using role-based restricted lists of invitee roles to choose from.

    Considering the project parameters you explained I find it an acceptable solution. In terms of using the acceptUrl as your “transport medium” I would tend towards using the transaction context instead (AlfrescoTransactionSupport instead of the mentioned ThreadLocal).

    I personally do not like custom extensions of Alfresco services as I have seen too many examples that violate internal service contracts. Facading in combination with transaction context data or implementing a contributable patch to the OOTB service are my preferred options. In terms of the InvitationService I could see a simple API extension which would allow passing of a custom data / property map, provide a “map-to-workflow mapper strategy” and a “mail / notification action” extension point (delegating based on site type / state evaluators).

  6. Thanks for your feedback, Axel.

    Funny, it reminds you of security related efforts you had. I am right now facing requirements to extend the Share Site security model. :)

    For the particular case of the InvitationService, it might indeed make sense to widen the tight interface we have today and work out a longer term solution. But an approach like that is of course not feasible in most real world projects.

    Honestly – the more I think about it, the more I realize that “hacks” in general (i.e. not only in Alfresco environments) are actually fairly common. Truth is, that I am now exercising all kinds of nasty Javascript while implementing the new security requirements.

    In the end, I guess a big share of these problems boils down to the nature of the tools we are given – the Java type system, accessibility modifiers (private, package, final) and so on. For me, these tend to get in the way quite often. That is why I like “tools” such as dynamically typed languages in customization projects.

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

Schreibe einen Kommentar

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