Fork me on GitHub

Introduction

This document describes the basic steps needed to migrate an application written for Turbine 4.0 to Turbine 5.0.

Migrating from Turbine 4.0 to Turbine 5.0 is mostly a task of updating any references to commons-config and insuring that you are using Parts for file upload rather than the old FileItem object.

Updating configuration

Log4j changes

TurbineResources.properties have changed making it less verbose to point to the log4j config file. Secondly, we have upgraded to Log4j2. We still keep the file as is (i.e. outside classpath), as this is what Turbine did all the way. Old config line:



        # -------------------------------------------------------------------
        #
        #  L O G 4 J - L O G G I N G
        #
        # -------------------------------------------------------------------
        
        log4j.file = WEB-INF/conf/log4j.properties
    

			
New config line:

        # -------------------------------------------------------------------
        #
        #  L O G 4 J 2 - L O G G I N G
        #
        # -------------------------------------------------------------------
        
        log4j.file = log4j2.xml

Setting character encoding

The default character encoding typically should be set by your servlet container. For example, in Tomcat you can update the server.xml in the following way to force URI connections to be encoded in UTF-8.


    <!-- Define a non-SSL HTTP/1.1 Connector on port 8080 -->
       <Connector port="8080" maxHttpHeaderSize="8192"
                   maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
                   enableLookups="false" redirectPort="8443" acceptCount="100"
                   connectionTimeout="20000" disableUploadTimeout="true"
                   URIEncoding="UTF-8"
       />

However, earlier versions of Apache Turbine allowed you to override this value by setting the input.encoding property in your TurbineResources.properties file.

With Turbine 5.0, you can still accomplish this same behavior, but you need to make sure that you have added the encoding valve to the turbine-classic-pipeline.xml to enable it.

In TurbineResources.properties, you can set the following parameter:


        input.encoding = UTF-8

But it will not be picked up unless you edit the pipeline valves to read as follows. Note that ORDER does matter if you are not seeing the behavior you expected.


    <pipeline name="default">
        <valves>

            <!-- Use this valve to enable use of the input.encoding
                    parameter defined in your TurbineResources.properties file -->
            <valve>org.apache.turbine.pipeline.DefaultSetEncodingValve</valve>

            <valve>org.apache.turbine.pipeline.DetermineActionValve</valve>
            <valve>org.apache.turbine.pipeline.DetermineTargetValve</valve>
            <valve>org.apache.turbine.pipeline.DefaultSessionTimeoutValve</valve>
            <valve>org.apache.turbine.pipeline.DefaultLoginValve</valve>
            <valve>org.apache.turbine.pipeline.DefaultSessionValidationValve</valve>
            <valve>org.apache.turbine.pipeline.DefaultACLCreationValve</valve>
            <valve>org.apache.turbine.pipeline.ExecutePageValve</valve>
            <valve>org.apache.turbine.pipeline.CleanUpValve</valve>
            <valve>org.apache.turbine.pipeline.DetermineRedirectRequestedValve</valve>
      </valves>
    </pipeline>

Migrating to Functional Interfaces and Rundata

Functional interfaces are now used instead of abstract classes. Rundata should be removed and instead of PipelineData used, but we keep it for now. As a result AbstractValve was removed and with it the method getRunData(pipelineData) is gone. You may retrieve the Rundata casted object now with pipelineData.getRunData().


        // old
        // RunData rundata = getRunData(pipelineData)
        RunData rundata = pipelineData.getRunData();


	

Assembler derived classes in package org.apache.turbine.modules (Action, LayoutScreen, Navigation, Page, ..) are now declared as java functional interfaces instead of abstract classes. Using them in child classes might be as easy as replacing extends with implements keyword in class declaration. Remark: Method signature containing checked exceptions were not changed. To use Java 8 functional lamda functions you may catch and rethrow them wrapped into a RuntimeException.



  // old
  // class MyAction extends Action

  class MyAction implements Action
  

Migrating File Upload to Parts

In turbine-4.0.1 and prior, file uploads were processed through the data.getParameters().getFileItem("file_field_name") method and returned a FileItem object.

With Turbine-5.0, the framework is now using Java servlet 3.1.0. As such, you will need to migrate this code using the new Part object from the servlet spec. This actually saves you some time since you don't have to convert the FileItem to a byte array and then into an InputStream for processing.. you auto-magically get an getInputStream() method on your javax.servlet.http.Part object to then do as you please...



        // all file items are now parts
        Part fileItem = data.getParameters().getPart("file");
        if (fileItem != null) {

            InputStream is = fileItem.getInputStream();
            BufferedReader bfReader = null;
            try {
                bfReader = new BufferedReader(new InputStreamReader(is));
                String line = null;
                while ((line = bfReader.readLine()) != null) {

                    // do something with the input here ...

                }
            } catch (IOException e) {
                // handle exception here	
                e.printStackTrace();
            } finally {
                try {
                    if (is != null)
                        is.close();
                } catch (Exception ex) {
                    // handle exception here	
                }
            }
        }


	

And if you really do need a byte array (for example to store the contents as a binary object in the database), you can do this using the following method calls.



  InputStream is = fileItem.getInputStream();
  byte[] byteArray = IOUtils.toByteArray(is);
  

Migrating to Quartz for Job Scheduling

Quartz was introduced as a replacement for job scheduling starting with Turbine 4.0. It provides Unix cron like abilities to manage the automatic execution of tasks (or jobs) within your Turbine application.

An example of a job might be periodic data or log cleanup that you want to automate within the application itself.

Creating a job

Job creation has not really changed from the way jobs were created in older versions of Turbine. You can refer to the older documentation on how to create a job. For ease of following the notes below however, we will add one simple job.

Create the job class in com.myapp.modules.scheduledjobs.MyTurbineJob.java



package com.myapp.modules.scheduledjobs;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.turbine.modules.ScheduledJob;
import org.apache.turbine.services.schedule.JobEntry;
import org.apache.turbine.services.security.SecurityService;

public class MyTurbineJob extends ScheduledJob {

	/** Logging */
	private static Log log = LogFactory.getLog(MyTurbineJob.class);

    // Track the number of times the job has been run
	private static int taskcount = 0;

	/**
	 * Constructor
	 */
	public MyTurbineJob() {
	}

	@Override
	public void run(JobEntry job) throws Exception {

		try {

            log.info("Job run: " + taskcount);
            // do some interesting work to be scheduled here...

		} catch (Exception e) {
			log.error("An error occurred running MyTurbineJob: " + e.toString());
		}

		taskcount++;
	}
}

Quartz service configuration

The Quartz scheduler is implemented as a Fulcrum component and needs to be defined in the roleConfiguration.xml and componentConfiguration.xml files in the WEB-INF/conf directory.

Modify the WEB-INF/conf/roleConfiguration.xml




<role-list>

    ...

    <!-- Service required for the QuartzSchedulerService -->
    <role
        name="org.apache.fulcrum.quartz.QuartzScheduler"
        shorthand="quartz"
        default-class="org.apache.fulcrum.quartz.impl.QuartzSchedulerImpl" />
        
</role-list>

Modify the WEB-INF/conf/componentConfiguration.xml



<componentConfig>
    ...

     <quartz>
        <configuration>
            <properties>
                <parameter name="org.quartz.scheduler.instanceName" value="TestScheduler"/>
                <parameter name="org.quartz.scheduler.instanceId " value="AUTO"/>
                <parameter name="org.quartz.scheduler.skipUpdateCheck" value="true"/>
                <parameter name="org.quartz.threadPool.class" value="org.quartz.simpl.SimpleThreadPool"/>
                <parameter name="org.quartz.threadPool.threadCount" value="3"/>
                <parameter name="org.quartz.threadPool.threadPriority" value="5"/>
                <parameter name="org.quartz.jobStore.misfireThreshold" value="60000"/>
                <parameter name="org.quartz.jobStore.class" value="org.quartz.simpl.RAMJobStore"/>
                <parameter name="org.quartz.plugin.jobInitializer.class" value="org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin"/>
                <parameter name="org.quartz.plugin.jobInitializer.fileNames" value="../conf/quartz.xml"/>
                <parameter name="org.quartz.plugin.jobInitializer.failOnFileNotFound" value="true"/>
                <parameter name="org.quartz.plugin.jobInitializer.scanInterval" value="120"/>
                <parameter name="org.quartz.plugin.jobInitializer.wrapInUserTransaction" value="false"/>
            </properties>
        </configuration>
    </quartz>  

</componentConfig>


Next, enable the Quartz scheduler service in the TurbineResources.properties file.


services.SchedulerService.classname=org.apache.turbine.services.schedule.QuartzSchedulerService

Finally, we need the configuration file for Quartz itself to run the job. You can do this by creating a quartz.xml file in your WEB-INF/conf directory.

If you want to change the name, check the component configuration above which defines the org.quartz.plugin.jobInitializer.fileNames value.

This example quartz configuration is set to run our test job every 15 minutes.


<?xml version="1.0" encoding="utf-8"?>
<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
	license agreements. See the NOTICE file distributed with this work for additional 
	information regarding copyright ownership. The ASF licenses this file to 
	you under the Apache License, Version 2.0 (the "License"); you may not use 
	this file except in compliance with the License. You may obtain a copy of 
	the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required 
	by applicable law or agreed to in writing, software distributed under the 
	License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
	OF ANY KIND, either express or implied. See the License for the specific 
	language governing permissions and limitations under the License. -->
<job-scheduling-data
	xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_2_0.xsd"
	version="2.0">
	<pre-processing-commands>
		<delete-jobs-in-group>*</delete-jobs-in-group>
		<!-- clear all jobs in scheduler -->
		<delete-triggers-in-group>*</delete-triggers-in-group>
		<!-- clear all triggers in scheduler -->
	</pre-processing-commands>
	<processing-directives>
		<!-- if there are any jobs/trigger in scheduler of same name (as in this 
			file), overwrite them -->
		<overwrite-existing-data>true</overwrite-existing-data>
		<!-- if there are any jobs/trigger in scheduler of same name (as in this 
			file), and over-write is false, ignore them rather then generating an error -->
		<ignore-duplicates>false</ignore-duplicates>
	</processing-directives>
	<schedule>
		<job>
			<name>MyTurbineJob</name>
			<group>TURBINE</group>
			<description>Perform some task at a specified time</description>
			<job-class>org.apache.turbine.services.schedule.JobEntryQuartz</job-class>
		</job>
		<trigger>
			<simple>
				<name>MyTurbineJobTrigger</name>
				<group>TURBINE</group>
				<job-name>MyTurbineJob</job-name>
				<job-group>TURBINE</job-group>
				<start-time>2018-06-01T00:00:00</start-time>
				<misfire-instruction>MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT</misfire-instruction>
				<repeat-count>-1</repeat-count>
        		<!--  check every 15 minutes -->
				<repeat-interval>900000</repeat-interval>
			</simple>
		</trigger>
	</schedule>
</job-scheduling-data>