Showing posts with label Liferay. Show all posts
Showing posts with label Liferay. Show all posts

Saturday, November 19, 2011

10 things a programmer should know about Liferay Kaleo Workflow Engine

Liferay developers decided to create their own workflow engine called Kaleo 2 years ago. It's a flexible lightweight workflow engine similar to other open-source engines out there. It is a plugin built on Liferay's ServiceBuilder and it needs to be deployed before you can use it (it is not a part of portal).

The principle consists in defining workflow for resources as an xml definition containing states, tasks, conditions, forks & joins and timer. Apart from timer, all of them contains transition nodes (where to go next). And apart from condition, fork&join all of them may contain actions (what to do) and hereby orchestrate a sequence of events and actions. It is especially useful for content reviewing, validation, approving and quality evaluation.


It is quite easy to use and design workflows in xml directly. But there are definitely use cases that makes you investigate its internal working and modify it or add something that you need.

Following summary lists a few things you should be aware of as a developer :
  • Liferay has it's own simple messaging implementation that is meant only for internal usage, it doesn't allow remote messaging. Kaleo uses it from two reasons :
    • Because it is an external plugin, portal context needs to communicate with it via messaging
    • Because messaging is a suitable choice for workflow engine implementation
  • A workflow definition is always associated with a corresponding resource. There are 2 key entities. For instance, in case of document library :  KaleoDefinition (definition itself) and WorkflowDefinitionLink (association between the definition and folder and file types that will be "workflow aware").
  • You cannot undeploy a definition if such an association exists. So that you first delete the link ( this corresponds to removing workflow from a resource in administration) and then you can deactivate it and undeploy it.
  • When you deploy a workflow definition, it is parsed in XMLWorkflowModelParser into an object model and for each node type (mentioned in 2. paragraph) there is a NodeExecutor type with methods enter, execute and exit
  • Everytime you add a resource, that has a workflow definition link, a workflow instance is created and its execution context is associated with it.
  • Consequently KaleoSignaler ( class that sends messages about entering, executing and exiting ) either call a corresponding NodeExecutor in case of timer node or sends a message to liferay/kaleo_graph_walker destination about entering or leaving a node (transitions). The destination is associated with DefaultGraphWalker, that steps into target nodes and call corresponding NodeExecutors. The methods are implemented differently based on type of the node.
  • Execution context contains so called Instance Token that is carrying information about current position within the workflow node model and and its state. It also holds a reference to workflow and service contexts.
  • Task nodes contain assignment element that determines recipients (content reviewers for instance) based on Role, User or just an email address. You can even dynamically determine this in runtime in scripted-assignment, which is a matter of setting a user or a role that you want in the scripting context. I wrote a blog post about improving scripting experience.
  • Actions are either notifications or whatever you need to be done. For a java developer the obvious choice would be writing groovy scripts.
  • KaleoLog entity contains information like duration, user comments etc. 

I hope this post helped you with getting grasp of Liferay Kaleo Engine's internal working. Cheers

Wednesday, November 9, 2011

Liferay Workflow Kaleo Engine tuning

It's been a pleasure to work with Liferay Workflow Kaleo Engine. I'm not going to introduce it here, you can read about it in the documentation reference. I just wanted to mention one thing that I needed to improve.
    When I was designing complex workflow definitions that were containing a lot of groovy scripts I just didn't want to write them directly in workflow xml definition. Instead, I created a many groovy script files and put ${references} into workflow definitions. During deployment I just expand them.
    With this setup I can profit from IDE support and I don't have to maintain hundreds of lines of code in a workflow definition. I just keep them basic scripts that has up to 15-20 lines. Imagine all those java import declarations, you wouldn't even know that you have that stuff on classpath.

This code takes care of it. I didn't use Liferay DOM API because it is missing a few key methods from classes that extend org.dom4j.Node and I didn't want to work that around. You just change those IllegalStateExceptions to your System ones or something.

  
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.jaxen.JaxenException;
import org.jaxen.SimpleNamespaceContext;
import org.jaxen.XPath;
import org.jaxen.dom4j.Dom4jXPath;

public class WhatEver {

 public byte[] expandScripts(String workFlowPath) {
  ClassLoader cl = Thread.currentThread().getContextClassLoader();

  Document document;
  try {
   document = new SAXReader().read(cl.getResource(workFlowPath));

   List<Element> scripts = findAllElements("script", document.getRootElement());

   expandScripts(cl, scripts);
  } catch (DocumentException e) {
   throw new IllegalStateException("Writing dom4j document failed", e);
  }

  return toByteArray(document);
 }

 private List<Element> findAllElements(String name, Element rootElement) {
  String ns = rootElement.getQName().getNamespaceURI();

  Map<String, String> map = new HashMap<String, String>();
  map.put("x", ns);

  List<Element> scripts;
  try {
   XPath xpath = new Dom4jXPath("//x:" + name);
   xpath.setNamespaceContext(new SimpleNamespaceContext(map));

   scripts = xpath.selectNodes(rootElement);
  } catch (JaxenException e) {
   throw new IllegalStateException("Xpath search for: '" + name + "' failed", e);
  }

  return scripts;
 }

 private void expandScripts(ClassLoader cl, List<Element> scripts) {
  DocumentFactory docFactory = DocumentFactory.getInstance();

  for (Element oldScriptElement : scripts) {
   String text = oldScriptElement.getText();

   if (!isCDATA(oldScriptElement) && text != null && text.startsWith("$")) {
    String scriptPath = text.substring(2, text.length() - 1);

    Element result = docFactory.createElement("script", oldScriptElement.getParent().getQName().getNamespaceURI());

    String script;

    try {
     script = getString(cl.getResourceAsStream(scriptPath));
    } catch (IOException e) {
     throw new IllegalStateException("Reading script: '" + scriptPath + "' failed", e);
    }
    result.addCDATA(script);

    replaceNode(oldScriptElement, result);
   }
  }
 }

 private byte[] toByteArray(Document document) {
  ByteArrayOutputStream baos = new ByteArrayOutputStream();

  XMLWriter writer;
  try {
   writer = new XMLWriter(baos, OutputFormat.createPrettyPrint());

   writer.write(document);
   writer.close();
  } catch (IOException e) {
   throw new IllegalStateException("Writing dom4j document failed", e);
  }

  return baos.toByteArray();
 }

 private boolean isCDATA(Element node) {
  for (Node n : (List<Node>) node.content()) {
   if (Node.CDATA_SECTION_NODE == n.getNodeType()) {
    return true;
   }
  }
  return false;
 }

 private void replaceNode(Element oldNode, Element newNode) {
  List parentContent = oldNode.getParent().content();

  int index = parentContent.indexOf(oldNode);

  oldNode.detach();

  parentContent.set(index, newNode);
 }

 private String getString(InputStream is) throws IOException {
  final char[] buffer = new char[0x10000];
  StringBuilder out = new StringBuilder();
  Reader in = new InputStreamReader(is, "UTF-8");
  int read;
  do {
   read = in.read(buffer, 0, buffer.length);
   if (read > 0) {
    out.append(buffer, 0, read);
   }
  } while (read >= 0);

  return out.toString();
 }
}

Gist

Tuesday, November 8, 2011

How to use Liferay third-party libraries as Maven dependencies ?

Almost all day worth of work but finally I finished DependencyInstallerMojo. Simply put, you provide it with destination of Liferay's lib/versions.xml and inclusion regexp patterns that determine which libraries are to be installed into either local maven repository or a custom location and it installs them as Maven artifacts and generates a pom definition that contains corresponding dependencies, so that you can use itself as dependency and thus transitively include all those libraries on your classpath.

  
 <plugin>
    <groupId>com.liferay.maven.plugins</groupId>
    <artifactId>liferay-maven-plugin</artifactId>
    <version>6.1.0-SNAPSHOT</version>
    <configuration>
      <generatedPomLocation>${project.basedir}/target/generated-pom.xml</generatedPomLocation>
      <generatedPomVersion>${project.version}</generatedPomVersion>
      <generatedPomName>Liferay dependencies</generatedPomName>
      <localRepositoryId>liferay-third-party-deps</localRepositoryId>
      <projArtifactId>${project.artifactId}</projArtifactId>
      <projGroupId>${project.groupId}</projGroupId>
      <dependencyScope>test</dependencyScope>
      <libDirPath>/opt/liferay/portal/lib</libDirPath>
      <localRepositoryPath>/home/old-lisak/fake-repo</localRepositoryPath>
      <include>
        <development>jsf-.*,derby,catalina,ant-.*,jalopy</development>
        <global>portlet.jar</global>
        <portal>chemistry-.*,commons-.*,jackrabbit-.*,spring-.*</portal>
      </include>
    </configuration>
 </plugin>

Most of those properties are set by default, you actually need to set at least :
 libDirPath - to the source xml file (versions.xml in case of Liferay)
 localRepositoryPath - to a custom local maven repository, unless you want to use the default one
 include - this element is a map of sub-directories of libDirPath that contains jar packages

Names of the sub-directories become part of groupId (com.example.development) and regular expressions adhere to java.util.regex split up by colons.


Why Liferay developers might need this ?


Because if you wanted your code to be covered by infrastructure tests, you would have to mock Liferay services quite frequently, unless you have Liferay third party libraries on classpath and you can boot up the spring based infrastructure and  hundreds of its services.
   Mocking might seem to be a suitable alternative until you are refactoring or upgrading Liferay version and all those tests turn into a maintenance hell. I personally use rather infrastructure testing than tons of "functional" unit tests, so that I'm trying to deal with this situation.

A year ago I manually listed maven dependencies corresponding to those listed in lib/versions.xml, just those that I needed for using particular Liferay services, but it is a really hideous thing to do because of all those version conflicts and transitive dependencies.

I wrote this Maven Mojo after I got really repelled by Maven Surefire Plugin's way of dealing with additional classpath resources, classloading hell.

A last possible option how to do this is systemPath property of Maven Dependency of system scope, but it is deprecated and it will be probably not supported in next releases.

If you want to try that out, follow these instructions :

$git clone git@github.com:l15k4/liferay-maven-support.git
$cd liferay-maven-support
$mvn install

Then go into an existing maven project and add this plugin into its build section.

  
 <plugin>
   <groupId>com.liferay.maven.plugins</groupId>
   <artifactId>liferay-maven-plugin</artifactId>
   <version>6.1.0-SNAPSHOT</version>
   <configuration>
     <libDirPath>/path/to/portal/lib</libDirPath>
     <localRepositoryPath>/home/user/test-maven-repository</localRepositoryPath>
     <include>
       <development>jsf-.*,derby,catalina,ant-.*,jalopy</development>
       <global>portlet.jar</global>
       <portal>chemistry-.*,commons-.*,jackrabbit-.*,spring-.*</portal>
     </include>
   </configuration>
 </plugin>

$mkdir /home/user/test-maven-repository
$mvn com.liferay.maven.plugins:liferay-maven-plugin:6.1.0-SNAPSHOT:install-dependencies


If everything goes well, the repository will contain the artifacts and there will be target/generated-pom.xml file in your project.

If you found this useful and you wanted to see this as part of liferay-maven-plugin, you could vote up here: LSP-22947

Sunday, November 6, 2011

Liferay database load balancing and sharding

If you are considering optimizations at the database level, you should be aware of 2 things :

Sharding

Technique that scales your database along portal instances : Database Sharding. Not that I have used it already, I haven't found a use case for this yet, so I'm setting up liferay-maven-plugin to call ServiceBuilder with these properties, so that it doesn't generate file shard-data-source-spring.xml

    
 <configuration>
   <springDynamicDataSourceFileName>
     null
   </springDynamicDataSourceFileName>
   <springShardDataSourceFileName>
     null
   </springShardDataSourceFileName>
 </configuration>

   I just wanted to mention it for those that are wondering what the shard-data-source-spring.xml is about. So that they don't think of it as "shared" type but that database shard is a known technique of horizontal partitioning of database design.

   I am also turning generation of dynamic-data-source-spring.xml off, because the solutions I've been developing didn't expect any massive load, so that  Read-write split technique is not needed either .

Read-write Split

Read-write split targets database replication. Not in the terms of a bulk file transfer, but rather a proxy mechanism that sends update/delete statements to multiple database servers, that is to say, having a database update against one server reflected on another server(s) in a real-time fashion. Databases use so called master-slave model where the master keeps a log of all transactions that it has performed and slave connects to it and performs the updates as it reads the log entries. The slave updates its position in the master's transaction log and waits for another one.
    For load balancing the best setup would be having two servers that are slaved to each other, which results in a slave-slave or master-master configuration. Transactions are reflected and there is no difference in the role of those servers.
    For instance MySQL database can be configured for this scenario quite easily and Liferay is quite ready for various requirements. Dynamic data sourcing for instance lets you have 2 data sources, one for READs and the second for WRITEs.
 
I have't needed this yet, as I said, so I'm turning it off as well in liferay-maven-plugin. The less files in my project, the better.


Anyway, some years of Liferay experience always come handy when you need to deal with stuff like this :-)

Liferay caching

Liferay is caching on a few places. Good one to start at is taking a look at PortalContextLoaderListener where all the key classes that take care of pooling, caching or buffering get revealed.

Entity caching and Finder caching :

These are just java collections that are mapping an instance of an Object representing an Entity to a primary key. Unless you access database by other means than via Persistence layer generated by Service Builder, you can rely on these types of cache. The finder cache is using FinderPaths, you can take a look at EntityPersistenceImpl.java how it is done :

fetchByContactId(long contactId, boolean retrieveFromCache) {
  Object[] finderArgs = new Object[] { contactId };

  Object result = null;

  if(retrieveFromCache) {
   result = FinderCacheUtil.getResult(FINDER_PATH_FETCH_BY, finderArgs, this);
  }
}

To disable entity and finder caching of User entity for instance, you set these properties to false in portal-ext.properties :

value.object.entity.cache.enabled.com.liferay.portal.model.User=false
value.object.finder.cache.enabled.com.liferay.portal.model.User=false

Hibernate caching :

Liferay is using Ehcache as a caching provider for Hibernate. If you are using Service Builder, you can choose whether to cache entity or not by attribute cache-enabled in portlet-hbm.xml

    
   <entity name="Order" remote-service="false" cache-enabled="false">

and hibernate entity mapping will be generated accordingly :

    
   <cache usage="read-write" />

Permission caching :

Liferay has improved permissioning system and its performance so much two years ago, that it doesn't seem to be an issue for now. Take a look at PermissionCacheUtil if you need to know more.

ThreadLocal caching :

Liferay is using ThreadLocalCache via AOP. Take a look at ThreadLocalCacheAdvice and corresponding @ThreadLocalCachable annotation.

Liferay servlet filtering

    The first thing that I have noticed when I started using Liferay was quite a significant amount of filters. Some of them relates to performance, caching, bandwidth, garbage collection of thread locals, security or providing you with various ways of authentication. But you should really know them and turn those that you don't need off. You can do that in portal-ext.properties.

    # Audit Service
    com.liferay.portal.servlet.filters.audit.AuditFilter=false

    # in case you are using one of AutoLogin implementations specified in auto.login.hooks property
    com.liferay.portal.servlet.filters.autologin.AutoLoginFilter=false

    # CAS Liferay Integration
    com.liferay.portal.servlet.filters.sso.cas.CASFilter=false

    # compressing content if client supports it, OFF for dev, ON for production
    com.liferay.portal.servlet.filters.gzip.GZipFilter=false

    # very important, see second paragraph, you better have these activated
    com.liferay.portal.servlet.filters.cache.CacheFilter=true
    com.liferay.portal.servlet.filters.header.HeaderFilter=true

    # monitors portal request performance.
    com.liferay.portal.servlet.filters.monitoring.MonitoringFilter=false

    # Integration with NTLM and ADS
    com.liferay.portal.servlet.filters.sso.ntlm.NtlmFilter=false
    com.liferay.portal.servlet.filters.sso.ntlm.NtlmPostFilter=false

    # Liferay OpenSSO Integration
    # Integrating OpenSSO & openAM with Liferay
    com.liferay.portal.servlet.filters.sso.opensso.OpenSSOFilter=false

    # Access via Sharepoint protocol.
    com.liferay.portal.sharepoint.SharepointFilter=false

Thursday, October 20, 2011

Liferay Modeshape Hook

Liferay has a really incredible ability to support various implementations and technology vendors. I mean, search through portal-properties for something you'd need to use and you most probably find that Liferay supports it.

As far as ECM is concerned, Liferay Portal disposes of Document Library that has Repository implementations underneath. The default repository, LiferayRepository, is a fullfledged one with versioning, indexing, Liferay specific ACL / permissioning, asset management, workflows, etc. All metadata is persisted in database and only the file and necessary versioning related information are stored either directly into filesystem, jcr repository, as a database blob or amazon S3. Which is for scaling purposes mainly, it allows you to choose the way of document persistence.
     If you wanted to make your own repository implementation that would extensively use JCR specification for instance, because you would need to write/read it to/from by other means than via Document Library or persist various types of metadata that you have extracted from documents, you better consider twice before using Doclib with it - we would be talking about serious duplication, considering that if you want to use Doclib, each document must have its FileEntry entry in database. You would have to be very precise if you wanted to supply Doclib with a repository implementation that would be accessed like that.
    In fact Liferay team mentioned they were going to make differences. I can already see commits like LPS-21374, that seem to be part of the refactoring.

   This Liferay Modeshape Hook is just another scaling alternative to the aforementioned ones. That is to say, it is doing the resulting CRUD of files, which is the last bit in a series of events that precede. JCRStore is executing the CRUD operations. It is JCR 2.0 compliant so that you can swap JCR repository implementations underneath.
   I prefer Modeshape over Jackrabbit due to many reasons. You can take a look by yourself what distinguishes Modeshape. This blog post would be too long if I started to list them. All necessary steps to make it run are described in README.

Wednesday, June 1, 2011

Liferay - Train of thoughts

It is ca 2 years ago when I was playing with sample portlets wondering what is the difference between Ext and Hook. I wanted to make a summary of the most important facts that it is good to be aware of.

Newcomers usually expect that they will be able to deliver an Intranet solution or at least a nice website with a few handy plugins very shortly. But suddenly they realize that even though Liferay has so much to offer, they really need at least a few weeks of LR experience to be able to customize or even use them. Just setting everything up and configure plugins and portal requires decent knowledge of Liferay.

You can easily add Wiki, Message Board or Blog into your site and set up roles and permissions for its users. Add some web content, social portlet, calendar or even Document Library (there are more of them). What I just named is great, I appreciate it a lot, but the problem is, that these portlets itself don't constitute a production ready solution most of the time. Make a list of those plugins and imagine what market product you would be able to put together using them.

  • Full-fledged website for 
    •  a common interest that connects some social group together
    •  a product that needs a proper representation and that has its user / social group  
    •  a corporate extranet and basic intranet with Document Library that is capable of document workflow and connection to other repositories or accessible from your desktop
    •  etc.
Beware, that there is not much you can do if you just going to create some web content and install Liferay's plugins. And even if you become a good administrator and you make a good looking social website via Control Panel and other configurations, you will most probably see many requirements that need to be done and many unused plugins, because there wasn't reasonable usage for them or it would be redundant. At that time you need a Liferay developer and UI designer.

As Liferay has many features and plugins, rather than framework it is a collection of applications, running on a robust software infrastructure (permissioning, roles, grouping, portlets lifecycle management, unified UI framework, event system, scheduling, very solid abstraction over data persistence, etc.). The features are there, they work, but they are made for universal use and as a developer you can either use them, improve them or use them as a model to create your own, which is mostly not an option. The architecture is very maintainable and you are mostly able to use services that the plugins are built on, to do what you need. There is a difference between portlets, for instance Message Board is most probably not going to be modified whereas Document Library is something that might be accessed more programmatically than via GUI, because if you are building an intranet for instance, custom document workflow and processing could be necessary and you're gonna have to make a good plan how to integrate with Document Library and benefit from the advantages that results from using. And this is something why you need to know Liferay a little longer. An important skill of a developer is the ability to learn from past mistakes and the ability to perceive what makes sense or not.

In Liferay you just has to accept the fact, that they try to satisfy existing customers, have as many features, that they can present to business world, as possible and that Liferay supports wide technology stack and vendors. This is kinda good for business and community growth, but it is not so beneficial for the software itself. It leads to defensive programming and not so much of refactoring or radical changes. There are quite a lot of programmers working on new features, producing bugs and skilled developers that are able to do radical infrastructure changes often end up fixing them and wasting a lot of precious time.
      Document library is a good example of this (notice that I don't criticize that, I mentioned before that it has valid reasons). DL came through a lot of changes recently, a lot of new features actually (CMIS, Document types and their processing, metadata, workflows, etc.). It's design is quite alright, but it doesn't adhere to any specification because LR needs it to be scalable (filesystem sore, JCR store, Amazon S3) and it utilizes the same permissioning system for it that is used for every other resource in the portal. Documents are also part of Asset management and it has it's own way of indexing and versioning. That's why it is built on repository data model (repository, file, folder, metadata, type, version, etc.). All this is quite complex and it is very hard to use a variety of interfaces where you all the time have to think of "and what if this..., wtf if that..." and you have to dive down into the implementation to answer many questions and you are infinitely not sure of consequences of what you're doing, then you finally become Document Library proficient to be able to work with it. This doesn't happen so much with software based on specification, like JCR spec. Wouldn't it be better if instead of all those new features a major refactoring was performed and all those necessary attributes of common document store were handled by JCR repository like Modeshape ? (ACLs - authentication/authorization, scalability (Modeshape datasources/connectors), versioning, indexing, CMIS ... I know that it is almost impossible at this stage, because files in Document Library are common resources and assets as well as blog post, wiki entry etc. It's part of the asset management that other portlets work with. Similarly the indexing and searching through the entire portal. On the other hand, standardization and specification is here because it doesn't change and once a developer learns it, nobody can take it from him and when a newcomer comes to a software based on specification, he knows how it works instantly.

Developing Liferay portlets is very straightforward, but only after you learn from all what you have done wrong in past. Quick example : I was so out of luck that I built one of my most important portlets on Liferay Service Builder (code and spring infrastructure generator - service/dao/domain layer). Everything looks fantastic except for one thing. Unit testing. You can easily load up portal spring context to use its services when testing, but you cannot load up your portlet Service Builder context (which depends on the portal one), because the bean IDs are the same in portlet context as of those in portal context. You just have to do heavy infrastructure coding to put such a basic thing like unit testing of a portlet together ... Actually I had 3 different variations of loading up SB portlet context together with Portal context ...

Anyway, I got really tired, gotta pack for a Nederland boat trip :-) I don't have enough energy to read it after myself so please excuse mistakes, my English or the exaggerated idea about Document Library refactoring :-)