Posted on Leave a comment

Add Connection Pool with auto connection cleanup functionality for Tomcat 7 and MySQL

Tomcat Logo

Since we don’t want to use external classes or libraries to handle database connections for our web applications we should stick to what comes built-in with the container server – in my case Apache Tomcat 7 and MySQL database server.

To keep the database connection pooling and cleanup functionality on the server-side you can use the following approach:

1.) In your web-app’s META-INF/context.xml add

<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/some-project">

    <Resource name="jdbc/your-db-resource" 
              auth="Container" 
              type="javax.sql.DataSource"
              factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
              testWhileIdle="true"
              testOnBorrow="true"
              testOnReturn="false"
              validationQuery="SELECT 1"
              validationInterval="30000"
              timeBetweenEvictionRunsMillis="30000"
              maxActive="100" 
              maxIdle="30" 
              maxWait="10000"
              initialSize="10"
              removeAbandonedTimeout="60"
              removeAbandoned="true"
              logAbandoned="true"
              minEvictableIdleTimeMillis="30000"
              jmxEnabled="true"
              jdbcInterceptors="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;
            org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
              username="root" 
              password="" 
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/your_db"/>
</Context>

2.) In your WEB-INF/web.xml add reference to JDNI resource from above:

<resource-ref>
        <res-ref-name>jdbc/your-db-resource</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>

Also make sure to put the corresponding mysql connector library jar into tomcat/lib folder so that you don’t you an exception like so:

WARNING: Unexpected exception resolving reference
java.sql.SQLException: com.mysql.jdbc.Driver
at org.apache.tomcat.jdbc.pool.PooledConnection.connectUsingDriver(PooledConnection.java:254)
at org.apache.tomcat.jdbc.pool.PooledConnection.connect(PooledConnection.java:182)
at org.apache.tomcat.jdbc.pool.ConnectionPool.createConnection(ConnectionPool.java:699)
at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:631)
at org.apache.tomcat.jdbc.pool.ConnectionPool.init(ConnectionPool.java:485)
at org.apache.tomcat.jdbc.pool.ConnectionPool.(ConnectionPool.java:143)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.pCreatePool(DataSourceProxy.java:116)
at org.apache.tomcat.jdbc.pool.DataSourceProxy.createPool(DataSourceProxy.java:103)
at org.apache.tomcat.jdbc.pool.DataSourceFactory.createDataSource(DataSourceFactory.java:539)
at org.apache.tomcat.jdbc.pool.DataSourceFactory.getObjectInstance(DataSourceFactory.java:237)
at org.apache.naming.factory.ResourceFactory.getObjectInstance(ResourceFactory.java:143)
at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:304)
at org.apache.naming.NamingContext.lookup(NamingContext.java:843)
at org.apache.naming.NamingContext.lookup(NamingContext.java:154)
at org.apache.naming.NamingContext.lookup(NamingContext.java:831)
at org.apache.naming.NamingContext.lookup(NamingContext.java:168)
at org.apache.catalina.core.NamingContextListener.addResource(NamingContextListener.java:1061)
at org.apache.catalina.core.NamingContextListener.createNamingContext(NamingContextListener.java:671)
at org.apache.catalina.core.NamingContextListener.lifecycleEvent(NamingContextListener.java:270)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5173)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:680)
Caused by: java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:247)
at org.apache.tomcat.jdbc.pool.PooledConnection.connectUsingDriver(PooledConnection.java:246)
… 29 more
Oct 31, 2012 11:23:25 AM org.apache.catalina.core.NamingContextListener addResource
WARNING: Failed to register in JMX: javax.naming.NamingException: com.mysql.jdbc.Driver
Oct 31, 2012 11:23:25 AM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler [“http-bio-8086”]
Oct 31, 2012 11:23:25 AM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler [“ajp-bio-8009”]
Oct 31, 2012 11:23:25 AM org.apache.catalina.startup.Catalina start
INFO: Server startup in 794 ms

That’s it!

A sample connection wrapper class would be:

package at.kerstner;

import java.sql.Connection;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

abstract class DBConnection {

    public static Connection getConnection() {
        Connection conn = null;
        try {
            Context initContext = new InitialContext();
            Context envContext = (Context) initContext.lookup("java:/comp/env");
            DataSource ds = (DataSource) envContext.lookup("jdbc/your-db-resource");
            conn = ds.getConnection();
        } catch (Exception e) {
            System.err.println("Failed to get connection: " + e.getMessage());
        }
        return conn;
    }
}

Also make sure to have a look at my other post concerning problems when loading the resource factory class.

Posted on 2 Comments

Tomcat 7 Fails to load Resource Factory class

Tomcat Logo

In case you get the exception Could not load resource factory class when trying to retrieve the Connection object from the JDBC connection resource pool based on the factory class org.apache.tomcat.jdbc.pool.DataSourceFactory make sure to check if tomcat-jdbc.jar is installed in the tomcat/lib directory. Please note that this file is not installed by default on most *nix systems. You can download tomcat-jdbc.jar from Maven or other sources.

Posted on 3 Comments

Serving Files using Jersey Web Service (JAX-RS)

Jersey Logo

There are a lot of tutorials out there explaining how to send binary data (i.e. application/octet-stream) as response for Jersey web services (Java JAX-RS). Two possible solutions are based on either returning a Response or StreamingObject containing the appropriate binary data stream.

Below you find a simple example for both scenarios:

//either inject response via context
@Context
private HttpServletResponse response;

//or return Response as shown below

/**
 *
 * @param content
 * @return
 */
@GET
@Path("/attachment")
@Consumes("text/plain; charset=UTF-8")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response getAttachment(
  @QueryParam("file") String fileName) {
  try {
    if (fileName == null) {
      System.err.println("No such item");
      return Response.status(Response.Status.BAD_REQUEST).build();
    }

    // either set response injected above
    //response.setContentType("image/png");
    //response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
    //TODO: write file content to response.getOutputStream();
    //response.getOutputStream().close();
    //return response;

    // OR: use a custom StreamingOutput and set to Response
    StreamingOutput stream = new StreamingOutput() {
      @Override
      public void write(OutputStream output) throws IOException {
        try {
          // TODO: write file content to output;
        } catch (Exception e) {
           e.printStackTrace();
        }
      }
    };

    return Response.ok(stream, "image/png") //TODO: set content-type of your file
            .header("content-disposition", "attachment; filename = "+ fileName)
            .build();
    }
  }

  System.err.println("No such attachment");

  return Response.status(Response.Status.BAD_REQUEST).build();

  } catch (Exception e) {
     System.err.println(e.getMessage());
     return Response.status(Response.Status.BAD_REQUEST).build();
  }
}

Additional checks have been ommitted for better readability. You should definitely check parameter fileName and not use it directly to serve files 😉

Pretty easy, right?

Posted on 9 Comments

Convert GPS Coordinates to Address using Google Geocoding API using Java

Google Logo

Google Maps makes it easy to convert addresses to their corresponding GPS coordinates using the Geocoding API. But what if you want to do the reverse, i.e. convert GPS coordinates to the corresponding address? Simple, just use Google’s ReverseGeocoding functionality.

Below you find a simple Java example of how to use the API to convert GPS coordinates to addresses:

package at.kerstner.geocoding;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

/**
 *
 * @param lng
 * @param lat
 * @return
 */
    private String getAddressByGpsCoordinates(String lng, String lat)
            throws MalformedURLException, IOException, org.json.simple.parser.ParseException {
        
        URL url = new URL("http://maps.googleapis.com/maps/api/geocode/json?latlng="
                + lat + "," + lng + "&sensor=true");
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        String formattedAddress = "";

        try {
            InputStream in = url.openStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            String result, line = reader.readLine();
            result = line;
            while ((line = reader.readLine()) != null) {
                result += line;
            }

            JSONParser parser = new JSONParser();
            JSONObject rsp = (JSONObject) parser.parse(result);

            if (rsp.containsKey("results")) {
                JSONArray matches = (JSONArray) rsp.get("results");
                JSONObject data = (JSONObject) matches.get(0); //TODO: check if idx=0 exists
                formattedAddress = (String) data.get("formatted_address");
            }

            return "";
        } finally {
            urlConnection.disconnect();
            return formattedAddress;
        }
    }

The example above retrieves the formatted address of the first result found. Additional checks have been omitted for better readability.

The API response has the following format:

{"results":[
{"address_components":[
{"long_name":"1","types":["street_number"],"short_name":"1"},
{"long_name":"Entenplatz","types":["route"],"short_name":"Entenpl."},
{"long_name":"Gries","types":["sublocality","political"],"short_name":"Gries"},
{"long_name":"Graz","types":["locality","political"],"short_name":"Graz"},
{"long_name":"Graz","types":["administrative_area_level_2","political"],"short_name":"Graz"},
{"long_name":"Styria","types":["administrative_area_level_1","political"],"short_name":"Stmk."},
{"long_name":"Austria","types":["country","political"],"short_name":"AT"},
{"long_name":"8020","types":["postal_code"],"short_name":"8020"}],
"formatted_address":"Entenplatz 1, 8020 Graz, Austria",
"types":["street_address"],"geometry":{"viewport":{"southwest":{"lng":15.4327164197085,"lat":47.0663661197085},"northeast":{"lng":15.4354143802915,"lat":47.0690640802915}},"location_type":"ROOFTOP","location":{"lng":15.4340654,"lat":47.0677151}}},

{"address_components":[{...}]}

]}

You also might want to checkout http://www.mobilefish.com/services/coordinate_converter/coordinate_converter.php for an easy way to handle and convert coordinates and preview them on a map.

Posted on 2 Comments

Setting Custom Portlet Titles in Liferay

Liferay Logo

Setting custom portlet titles in Liferay can be a little tricky. Changing the title via the portlet configuration in the frontend is easy but when it comes to reading the custom title property programmatically things seem to get a little messy.

Setting the Title

So, first of set your portlet title as usual via the portlet configuration in the frontend. That’s the easy part.

Determining Portle Title Programmatically

Then, in order to determine custom portlet titles use the following code:

ThemeDisplay themeDisplay = ((ThemeDisplay) request
  .getAttribute(WebKeys.THEME_DISPLAY));
String portletId = themeDisplay.getPortletDisplay().getId();
javax.portlet.PortletPreferences portletSetup = PortletPreferencesFactoryUtil
  .getLayoutPortletSetup(themeDisplay.getLayout(), portletId);
String portletCustomTitle = themeDisplay.getPortletDisplay()
  .getTitle();
portletCustomTitle = portletSetup.getValue("portletSetupTitle_"
  + themeDisplay.getLanguageId(), portletCustomTitle);

This code will determine the current portlet’s ID, get its preferences, determine the current namespace and finally retrieve the custom portlet title.

In case you need to read other custom portlet properties simply look up their keys in the database and use the portletSetup.getValue(“yourKey_”, …) option as shown previously.

Posted on Leave a comment

Bugfix for urbanairship-java SDK Connection Still Allocated

Apart from another bug reported in https://www.kerstner.at/en/2012/12/bugfix-for-urbanairship-java-sdk-jsonparseexception/ there also exists a bug related to the internal use of BasicClientConnManager for sending HTTP requests.

The Cause

When trying to send multiple HTTP requests using sullis urbanairship-java SDK (occasionally) an IllegalStateException is thrown. This is caused due to the fact that the version does not release open connections properly. You will get an exception similar to the one shown below:

java.lang.IllegalStateException: Invalid use of BasicClientConnManager: connection still allocated.
Make sure to release the connection before allocating another one.
        at org.apache.http.impl.conn.BasicClientConnectionManager.getConnection(BasicClientConnectionManager.java:162)
        at org.apache.http.impl.conn.BasicClientConnectionManager$1.getConnection(BasicClientConnectionManager.java:139)
        at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:455)
        at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
        at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805)
        at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:784)
        at com.urbanairship.UrbanAirshipClient.execute(UrbanAirshipClient.java:403)
        at com.urbanairship.UrbanAirshipClient.get(UrbanAirshipClient.java:505)
        at com.urbanairship.UrbanAirshipClient.getDevice(UrbanAirshipClient.java:250)
        ... (and many more)

The Fix

The fix is pretty easy. First and foremost make sure to use an apache-commons-httpclient version newer or equals than 4.2, since from this version onwards it offers a releaseConnection() that makes our life much easier (link: ). Then, in UrbanAirshipClient.java add method.releaseConnection(), as shown below:

protected HttpResponse execute(HttpRequestBase method) {
  try {
    method.setHeader(new BasicHeader("Accept", "application/json"));
    HttpResponse rsp = getHttpClient().execute(method);
    checkResponse(method, rsp);
			
    method.releaseConnection();
			
    return rsp;
  } catch (RuntimeException rtex) {
    throw rtex;
  } catch (Exception ex) {
    throw new RuntimeException(ex);
  }
}

That’s it! Enjoy 🙂

Posted on Leave a comment

Bugfix für urbanairship-java SDK JsonParseExceptionBugfix for urbanairship-java SDK JsonParseException

There (still) exists a bug in urbanairship-java SDK related to parsing valid ISO8601 dates. The original bug report can be found here: GSon date parsing error.

The Cause

ISO8601 states that for date-time strings the separator “T” can be left out. Unfortunately the urbanairship-java SDK expects only fully qualified ISO8601 strings. Thus, when it receives a (perfectly valid) date-time string such as the one shown below it throws a JsonParseException:

{
    "device_token": "XXX",
    "last_registration": "2012-12-27 08:24:33",
    "tz": null,
    "tags": [],
    "alias": null,
    "quiettime": {
        "start": null,
        "end": null
    },
    "active": true,
    "badge": 0
}

The stacktrace caused by the JsonParseException thrown looks similiar like the one shown below:

com.google.gson.JsonParseException: 2012-12-27 08:24:33
	at com.urbanairship.GsonFactory$CalendarAdapter.deserialize(GsonFactory.java:54)
	at com.urbanairship.GsonFactory$CalendarAdapter.deserialize(GsonFactory.java:1)
	at com.google.gson.TreeTypeAdapter.read(TreeTypeAdapter.java:58)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93)
	at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172)
	at com.google.gson.Gson.fromJson(Gson.java:795)
	at com.google.gson.Gson.fromJson(Gson.java:761)
	at com.google.gson.Gson.fromJson(Gson.java:710)
	at com.google.gson.Gson.fromJson(Gson.java:682)
	at com.urbanairship.UrbanAirshipClient.fromJson(UrbanAirshipClient.java:527)
	at com.urbanairship.UrbanAirshipClient.fromJson(UrbanAirshipClient.java:385)
	at com.urbanairship.UrbanAirshipClient.get(UrbanAirshipClient.java:507)
	at com.urbanairship.UrbanAirshipClient.getDevice(UrbanAirshipClient.java:250)
	... many more

The problem is caused in the fromJSON function in Urbanairshipclient.java:

protected <T> T fromJson(final String json, final Class<T> clazz) {
  Gson gson = GsonFactory.create();
  return gson.fromJson(json, clazz); // PROBLEM HERE
}

The Fix

Luckily the fix is pretty easy. Open ISO8601.java and update the PATTERN variable as follows:

public class ISO8601
{
	//public static final String PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ"; //BUG HERE
	public static final String PATTERN = "yyyy-MM-dd HH:mm:ss"; //BUG FIXED    
        ...
}

That’s it. I’ve updated the bug report accordingly.

Posted on 1 Comment

Setting Custom Timezones in Liferay

Liferay Logo

By default Liferay uses UTC as timezone. In order to set custom timezones you may choose from the following options:

  1. Create an EXT-Plugin
  2. Specify timezone in Tomcat’s setenv script

EXT-Plugin

Create an EXT-plugin (or use your existing one) and put system-ext.properties in extyour-extdocrootWEB-INFext-implsrc using the following setting:

user.timezone=Europe/Vienna

Be sure the deploy and restart Tomcat afterwards. Also, check via the Control Panel in Server Administration / System properies that the timezone attribute has been applied correctly.

Setenv Script

The other option is to set your desired timezone in Tomcat’s setenv script located in tomcat/bin by using the following command:

-Duser.timezone=Europe/Vienna

Depending on your operating system you will need to configure setenv.bat (Windows) or setenv.sh.

Liferay Developer Studio

If you happen to use Liferay Developer Studio you can easily set your desired timezone using the server properties, as shown below:

Posted on Leave a comment

Custom Response Types in Jersey SecurityContext

Jersey Logo

Sometimes you might need to return custom response types in your SecurityContext implementation when using Jersey RESTFul web services. Basically, there are two options available to do so:

  1. Use javax.ws.rs.core.Response and throw a WebApplicationException
  2. or write your own ExceptionMapper

Throw WebApplicationException

import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import java.security.Principal;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.SecurityContext;

public class MySecurityContext implements SecurityContext {

	private final User user;
	private final Session session;

	public MySecurityContext(User user, Session session) {
		this.user = user;
		this.session = session;
	}

	public String getAuthenticationScheme() {
		return SecurityContext.BASIC_AUTH;
	}

	public Principal getUserPrincipal() {
		return user;
	}

	public boolean isSecure() {
		return false;
	}

	public boolean isUserInRole(String role) {

		JSONObject jsonObj = JSONFactoryUtil.createJSONObject();

		if (null == session || !session.isActive()) {
			jsonObj.put("status", "failed");
			jsonObj.put("message", "Authentication failed");

			javax.ws.rs.core.Response denied = javax.ws.rs.core.Response
					.status(javax.ws.rs.core.Response.Status.FORBIDDEN)
					.type("application/json; charset=utf-8")
					.entity(jsonObj.toString()).build();

			throw new WebApplicationException(denied);
		}

		try {
			return user.getRoles().contains(role);
		} catch (Exception e) {
			e.printStackTrace();
		}

		return false;
	}
}

Custom ExceptionMapper

The other (even nicer option) is to write your own ExceptionMapper:

@Provider
public MyMapper implements ExceptionMapper<WebApplicationException> {
   public Response toResponse(WebApplicationException ex) {
     Response r = ex.getResponse();
     if (r.getStatus() == 403 && r.getEntity() == null) {
      return Response.fromResponse(r).entity(
        new Viewable("/403response", null)).build();
     } else {
       return r;
     }
   }
} 
Posted on 1 Comment

Setting Custom User Model Attributes in Liferay

Liferay Logo

Oftentimes you will need to extend Liferay’s built-in User model by adding custom attributes, such as a list of preferred settings. Fortunately, Liferay provides developers with an ExpandoBridge implementation to do so.

Thus, in order to add custom attributes simply use the Custom Fields option via the Control Panel (or programmatically). Search for the User model and add your custom field(s). In order to programmatically access these properties you now have two options. First, you may iterate over all custom fields set, or access custom fields through their key.

Access List of Custom Attributes

The following code snippet shows how to access the list of custom attributes set:

Map<String, Serializable> customAttributes = userLiferay
  .getExpandoBridge().getAttributes();
Iterator<String> it = customAttributes.keySet().iterator();

while (it.hasNext()) {
  String attribute = it.next();
  Serializable attributeValue = customAttributes.get(attribute);
  System.out.println("user custom attribute " + attribute + "="
    + attributeValue);
}

NOTE:
When getting NULL instead of your desired value when using getAttribute be sure to check for corrects permissions.

Access Specific Custom Attribute

The other option is to access specific custom fields by using its key through ExpandoValueLocalServiceUtil, as shown below:

String userBookmarksString = (String) ExpandoValueLocalServiceUtil
  .getData(Long.parseLong(PropsUtil.get("default-companyId")),
    userExpBridge.getClassName(), "CUSTOM_FIELDS",
    "yourCustomField", userExpBridge.getClassPK());

Updating Custom Attributes

Finally, in case you need to update a custom field use the following code:

ExpandoValue expVal = ExpandoValueLocalServiceUtil
  .getValue(Long.parseLong(PropsUtil.get("default-companyId")),
    userExpBridge.getClassName(), "CUSTOM_FIELDS",
    "yourCustomField", userExpBridge.getClassPK());
expVal.setData("newValue");
ExpandoValueLocalServiceUtil.updateExpandoValue(expVal);

That’s it!

Posted on 1 Comment

Determining Absolute Path for FileEntry in Liferay

Liferay Logo

In case you are wondering how to determine the absolute path to FileEntry objects in Liferay (i.e. Document and Media Library elements) here is how it works.

First, add the following to your portal-ext.properties:

dl.hook.file.system.root.dir=${liferay.home}/data/document_library

Then, to calculate the absolute path for a FileEntry use the following code:

private String getDLFileAbsPath(FileEntry fileEntry) 
throws PortalException, SystemException {
  return PropsUtils.get("dl.hook.file.system.root.dir") + "/" 
    + fileEntry.getCompanyId() + "/"
    + fileEntry.getFolderId() + "/"
    + ((DLFileEntry) fileEntry.getModel()).getName() + "/"
    + fileEntry.getVersion();
}

Note that in order to get the foldername where your FileEntry resides you need to cast FileEntry to DLFileEntry, as shown above.

Posted on Leave a comment

Dynamically determine Structure IDs in Liferay

Liferay Logo

When using multiple Web Content (aka JournalArticle) structures in Liferay managing their corresponding IDs via configuration files can become a tedious task. Luckily, there is an easy way to determine structure IDs dynamically based on their title, like the following code snippet demonstrates:

private static String STRUCTUREID = null;

private String getStructureId() {
  try {
    if (STRUCTUREID != null) {
      return STRUCTUREID; // already set
    }

    DynamicQuery dynamicQuery = DynamicQueryFactoryUtil.forClass(
      JournalStructure.class).add(PropertyFactoryUtil.forName("name").like(
      "%\">Your Structure Title</>%"));
			
  @SuppressWarnings("unchecked") //optional
  List<JournalStructure> structures = JournalStructureLocalServiceUtil.
    dynamicQuery(dynamicQuery, 0, 1);

  if (structures.size() < 1) {
    System.out.println("No structure found");
    return null;
  }

  STRUCTUREID = structures.get(0).getStructureId();

  System.out.println("Structure ID=" + STRUCTUREID);

  return STRUCTUREID;

} catch (SystemException e) {
    e.printStackTrace();
    return null;
  }
}
Posted on 3 Comments

Determine latest version of Web Content in Liferay

Liferay Logo

Most of the time when using Liferay’s Web Content (i.e. JournalArticle) you will want to determine the latest version to be displayed to your users. The following code snippet shows a simple solution to do so:

List<JournalArticle> articles = JournalArticleLocalServiceUtil.getStructureArticles(GROUPID, STRUCTUREID);
ListIterator<JournalArticle> it = articles.listIterator();
List<String> checkedArticleIds = new ArrayList<String>();

while (it.hasNext()) {
  JournalArticle article = it.next();

  if (checkedArticleIds.contains(article.getArticleId())) {
    continue; // previous article version already checked
  }

  JournalArticle articleLastVersion = JournalArticleLocalServiceUtil.getLatestArticle(GROUPID, article.getArticleId());

  checkedArticleIds.add(article.getArticleId());

  System.out.println("Added articleId " + article.getArticleId() + " with version " + article.getVersion());
}