Windows update breaks Output service on JBoss

JBossWe saw an interesting issue arise the other day where a Windows Server update caused a Production server instance to fail during a render operation. Both the Development and Test instances were patched successfully and no issues were seen in those environments.

Server was AEM Forms 6.1 SP1 on JBoss

The errors looked like this (extra lines removed for brevity):

16:57:35,336 WARN [com.adobe.formServer.docservice.LifeCycleImpl] (IDPSchedulerService_Worker-1) ALC-OUT-002-701: ServiceUtil already inititalized with service: OutputService. Aborting cache re-initialization.
16:57:35,575 INFO [com.adobe.output.config.OutputConfigImpl] (IDPSchedulerService_Worker-1) FSC008: Using the database to access and persist configuration properties.
16:57:36,057 INFO [com.adobe.service.ProcessResource] (IDPSchedulerService_Worker-1) ALC-BMC-001-505: Service XMLFormService: Starting native process with command line "D:\\Adobe\\Adobe_Experience_Manager_forms\\jboss\\standalone\\svcnative\\XMLFormService\\bin\\XMLForm.exe" -MyPath D:\Adobe\Adobe_Experience_Manager_forms\jboss\standalone\svcnative\XMLFormService -IOR IOR:000000000000002249444C3A636F6D2F61646F62652F736572766963652F4D616E616765723A312E300000000000000100000000000000BC000102000000000F31302E3133312E3131322E31393200000DC8000000000018383136363634303033362F42522D312F0100000000000000000000040000000000000008000000004A4143000000000100000020000000000501000100000001000100010001010900000002050100010001010000000014000000080000001A00000DC90000002100000030000000000000000100000000000000220000000000000000000000000000000000000000000000000000000000000000 -AppServer jboss
16:57:37,767 INFO [com.adobe.service.ProcessResource] (RequestProcessor-5) ALC-BMC-001-508: Service XMLFormService: Native process PID = 4476
16:57:37,915 WARN [com.adobe.service.J2EEConnectionFactoryManagerPeerImpl] (IDPSchedulerService_Worker-1) Service: XMLFormService resource: ProcessResource@6fb137ba(name=XMLForm.exe,pid=4476) could not schedule an interrupt for transaction: TransactionImple < ac, BasicAction: 0:ffff0a8370c0:-2618bc79:57ff2163:30dd status: ActionStatus.RUNNING > because excessive time was spent waiting in queue. Supplied timeout: 0s, Wait adjustment: 1s, Effective timeout: -1s.
16:57:37,956 INFO [com.adobe.document.XMLFormService] (RequestProcessor-5) ALC-XTG-101-000: UseProximity=false
16:57:38,380 ERROR [com.adobe.formServer.PA.XMLFormAgentWrapper] (IDPSchedulerService_Worker-1) ALC-OUT-002-013: XMLFormFactory, PAexecute failure: "java.lang.NullPointerException"
16:57:38,386 ERROR [stderr] (IDPSchedulerService_Worker-1) com.adobe.printSubmitter.PrintException: java.lang.NullPointerException in com.adobe.livecycle.formsservice.exception.RenderFormException, cause: java.lang.NullPointerException in com.adobe.livecycle.formsservice.exception.FormServerException
16:57:38,387 ERROR [stderr] (IDPSchedulerService_Worker-1) at com.adobe.printSubmitter.util.FormSubmitter.submit(FormSubmitter.java:211)
...
16:57:38,488 ERROR [stderr] (IDPSchedulerService_Worker-1) ... 300 more
16:57:38,490 ERROR [stderr] (IDPSchedulerService_Worker-1) Caused by: com.adobe.livecycle.formsservice.exception.FormServerException: java.lang.NullPointerException
...
16:57:38,492 ERROR [stderr] (IDPSchedulerService_Worker-1) ... 303 more
16:57:38,494 ERROR [stderr] (IDPSchedulerService_Worker-1) Caused by: java.lang.NullPointerException
16:57:38,494 ERROR [stderr] (IDPSchedulerService_Worker-1) at com.adobe.formServer.PA.XMLFormAgentWrapper.doPAExecute(XMLFormAgentWrapper.java:429)
16:57:38,495 ERROR [stderr] (IDPSchedulerService_Worker-1) ... 308 more
16:57:38,507 ERROR [com.adobe.workflow.AWS] (IDPSchedulerService_Worker-1) An exception was thrown with name com.adobe.livecycle.output.exception.OutputException message:com.adobe.printSubmitter.PrintException: java.lang.NullPointerException in com.adobe.livecycle.formsservice.exception.RenderFormException, cause: java.lang.NullPointerException in com.adobe.livecycle.formsservice.exception.FormServerException while invoking service OutputService and operation generatePDFOutput2 and no fault routes were found to be configured.

Solution

Delete the following JBoss temporary directories:

  • [aem_forms_install_dir]\jboss\standalone\svccommon
  • [aem_forms_install_dir]\jboss\standalone\svcnative
  • [aem_forms_install_dir]\jboss\standalone\tmp
  • [aem_forms_TEMP_dir] (C:\Windows\Temp by default on Windows)

Email Batch Size

Simple tip today: Always make sure you change the default email Batch Size when you install your AEM Forms JEE server.

By default the Batch Size on sending emails is set to ‘2’. This means that if you are testing single emails or Forms Workflows notifications from your new server, you wont see an email until there are two of them waiting to be sent.

I would like to say this setting is fine for a production server or some other use-case, but in reality I can’t think of one. If, for some reason, one email for a particular Task notification is all that is processed for the last hour, then the recipient will never see that email until something else happens in the system to generate another email. Definitely not something that should happen.

The fix

  1. Open AdminUI (http://localhost:8080/adminui) and login
  2. Browse to Services > Applications and Services > Service Management
  3.  Enter email into the Name: filter field and click Filter
    email_1.png
  4. Click the Email provider service (1st in the list)
    email_2.png
  5. Change Batch Size: to 1
    email_3.png
  6. Save

A warning about changing the default Super Administrator password

aem_warningOne thing you should always do in your production instance is change the default passwords. Everyone does that right? riiiight

Well when you decide to change the default Super Administrator (administrator) password in AEM Forms Workflows 6.1 or 6.2 there is an unexpected side effect. After changing this password you would notice that it works fine when you test it so you go about your other tasks. Then you will notice that after a period of time you are automatically locked out!

Under the hood, there is an OSGi service that links the OSGi CRX instance to the JEE instance. This is called the Adobe LiveCycle Client SDK Configuration. This service constantly attempts to log into the JEE instance and do stuff. If you change the default administrator password, and don’t change this password, this service will lock your Super Administrator account out.

Great. How do I stop this?

It’s easy. Just open the OSGi system console and change the password in the configuration screen. You can open the config item directly from a browser. e.g.

http://localhost:8080/lc/system/console/configMgr/com.adobe.livecycle.dsc.clientsdk.internal.DSCConfigService

client_sdk_config.png

Ok…I’m already locked out. Now what?

The default unlock time is set to 30 minutes after 20 incorrect attempts, so you can change the OSGi password and wait 30 minutes…or you are super impatient like me and can bust out some SQL-fu.

Remember the password you set on the database when you installed the instance? You’re going to need that now. Don’t remember it? Sorry – you now have to wait. Go browse Reddit for a bit and come back…

First, start by installing a SQL editor for your particular database (I’m going to show MySQL since that is in the turnkey edition)

  1. First log in to your MySQL instance using the root account
    Manage Server Connections_mysql.png

2. Then enter the password by clicking Store in Vault… (this is the password that you used when you installed the instance). Click OK

database_password.png

3. Click Test Connection

 database_success.png

4. You should see a couple of adobe database instances (these will be the database names you specified during setup) I usually call mine ‘adobe’ or ‘adobe_wf’

database_schemas.png

5. Now locate the Super Admin account using the following SQL:

SELECT * FROM adobe.edcprincipalentity where canonicalname = 'SuperAdmin';

6. You should see the column countauthfailure has a number > 20 and islocked is set to 1 (locked)

database_unlock.png

7. Now you can either use the GUI to change the value or run the following SQL:

UPDATE adobe.edcprincipalentity 
SET islocked = 0 
WHERE canonicalname = 'SuperAdmin';

8. You should be able to log in to your Super Administrator account now

Its probably a good idea to create a second Super Administrator account just in case this happens again.

Removing HPROF dump files

LC_bugRevisiting the topic of Livecycle consuming everything in its path, I found another issue that features in Livecycle ES4 SP1. The symptoms are the usual symptoms; lack of disk space, server crashing, etc. After hunting down all of the extra large files in your system you might find a number of very large files in the following path:

[livecycle_install_dir]/crx-repository/launchpad/felix/bundle33/dumps

HPROF Files

Heap dump off

To turn this feature off, do the following:

  1. Browse to the AEM system console: http://host:port/lc/system/console
  2. Locate the bundle
    org.apache.felix.webconsole.plugins.memoryusage
  3. Stop the bundle

You can then stop Livecycle, delete the HPROF files and restart Livecycle. The bundle should not restart, but its worth checking anyway.

Configuring secure email endpoints in Livecycle

LC LogoA client of ours recently had issues configuring their Livecycle ES4 server to use SSL for an IMAP email endpoint. Turns out it wasn’t as straightforward as the Adobe documentation makes out. In particular, it doesn’t specify where the cacerts file was in their distribution. So I thought I would run through the process in a bit more detail here.

 cacerts?

cacerts is a truststore file that is found in the Java distribution that is used by the Livecycle application server. It contains certificate references for well-known certifying authorities such as VeriSign™.  A trust store is used by the Java Virtual Machine (JVM) to authenticate peers during secure communications over a network connection.

Usually finding the cacerts file is not a problem, unless you install the Turnkey edition of Livecycle. And especially if you have upgraded from ES4 to SP1. I just searched my local Livecycle ES4 SP1 distribution and it contained 5 different cacerts files! So how do you find which one you need?

In Windows, just open the System Properties > Advanced Tab > Environment Variables and look for Adobe_JAVA_HOME variable under the System Variables list. If you double-click to open it up, it will point to a JDK version (e.g. C:\Adobe LiveCycle ES4\Java\jdk1.7.0_25). From there, just look under /jre/lib/security and you should see the caerts file.

System Properties (Click for bigger image)

Mail server certificate file

Once you locate the cacerts file, you then need your client certificate to import into the trust store file. If it is a local certificate (for example, from your internal corporate network), you can use the following method to extract the CER file required. (Note: most corporate email servers will have a Web Mail client that you can browse to over HTTPS)

1. Connect to your Corporate Web Mail portal and view the certificate details (I’m using Gmail as an example here)

Cert_1 (Click for bigger image)

2. Click View certificates > Details Tab > Copy To file

Cert_2 (Click for bigger image)

3. Now Save the file somewhere as a Base-64 encoded X.509 CER file

Cert_3 (Click for bigger image)

Cert_4 (Click for bigger image)

Cert_5 (Click for bigger image)

Cert_6 (Click for bigger image)

Finish off

Now just follow the steps as outlined in the Adobe help page: http://help.adobe.com/en_US/livecycle/11.0/AdminHelp/WS92d06802c76abadb-5145d5d12905ce07e7-7edc.2.html

However, for step #3 use something similar to the following example for Livecycle ES4 SP1 Turnkey (assumes ES4 is installed in C:\Adobe) and once it is imported, you should be good to go (i.e. no restart required)

C:\Adobe\Adobe LiveCycle ES4\Java\jdk1.7.0_25\bin\keytool –import –file C:\Adobe\MyCert.cer -keystore C:\Adobe\Adobe LiveCycle ES4\Java\jdk1.7.0_25\jre\lib\security\cacerts

Calculating Task Reminder, Escalation or Deadline days

Process Manager IconI recently had to set some reminders based on an actual time of the day (e.g. Midnight tonight and 12pm again tomorrow). This sounded easy until you realise that the Task Date object (the object used by Reminder, Escalation and Deadline) is not a Calendar or Date object, but a number of Days, Hours and Minutes until the Reminder was due. Awesome.

Below is an adaptation of a nice date calculation script from TechnoJeeves that allows you to calculate the number of days, hours and minutes between a certain date and time and transpose that into a Task Reminder object that Livecycle Process Manager can understand. You can also feed in a particular date string from a process variable and parse that if you want.

My example shows how to calculate the Hours and Minutes until midnight tonight (or tomorrow to be exact). There will never be any days involved as midnight is always less than 24 hours away.

Execute that script

To do this, you will need to create a Task Reminder variable called taskReminder.

This is the BeanShell script from my executeScript activity:

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

/**
 * Calculate the absolute difference between two Date without
 * regard for time offsets
 *
 * @param d1 Date one
 * @param d2 Date two
 * @return The fields day, hour, minute, second and millisecond
 */
long[] getTimeDifference(Date d1, Date d2) {
        long[] result = new long[5];
        Calendar cal = Calendar.getInstance();
        cal.setTimeZone(TimeZone.getTimeZone("UTC"));
        cal.setTime(d1);

        long t1 = cal.getTimeInMillis();
        cal.setTime(d2);

        long diff = Math.abs(cal.getTimeInMillis() - t1);
        final int ONE_DAY = 1000 * 60 * 60 * 24;
        final int ONE_HOUR = ONE_DAY / 24;
        final int ONE_MINUTE = ONE_HOUR / 60;
        final int ONE_SECOND = ONE_MINUTE / 60;

        long d = diff / ONE_DAY;
        diff %= ONE_DAY;

        long h = diff / ONE_HOUR;
        diff %= ONE_HOUR;

        long m = diff / ONE_MINUTE;
        diff %= ONE_MINUTE;

        long s = diff / ONE_SECOND;
        long ms = diff % ONE_SECOND;
        result[0] = d;
        result[1] = h;
        result[2] = m;
        result[3] = s;
        result[4] = ms;

        return result;
}

// today 
Date now = new Date();
Calendar date = new GregorianCalendar();
// reset hour, minutes, seconds and millis for midnight tonight
date.set(Calendar.HOUR_OF_DAY, 0);
date.set(Calendar.MINUTE, 0);
date.set(Calendar.SECOND, 0);
date.set(Calendar.MILLISECOND, 0);
// next day midnight is tonight midnight
date.add(Calendar.DAY_OF_MONTH, 1);

//caclulate the absolute difference      
long[] diff = getTimeDifference(date.getTime(), now);

//get hours and minutes for the reminder
patExecContext.setProcessDataIntValue( "/process_data/taskReminder/object/reminder/@hours", (int)diff[1] );
patExecContext.setProcessDataIntValue( "/process_data/taskReminder/object/reminder/@minutes", (int)diff[2] );

//enable the reminder
patExecContext.setProcessDataBooleanValue( "/process_data/taskReminder/object/@selected", true );

Now just assign the taskReminder variable to the Task Reminder in the Assign Task activity and your Reminder will be sent at midnight tonight!

Automatically clean JBoss tmp directory

JBoss_by_Red_Hat I have been poking about JBoss lately trying to get messaging working and I had a look in the %JBOSS_HOME%/server/lc_turnkey/tmp directory. As you would imagine, it was chock full of temporary files (no surprise there). What I didn’t expect was how many files! So after a quick email to Adobe Support to confirm I wasn’t going to upset anything Livecycle related, I shut down JBoss and decided to clean the files out. Sometime into the delete, I got bored and lost count at 82,000 files and something like 6GB of space!

JBoss EAP 5.1.0

I had a quick Google around and it took me to a post on the JBoss Community Forum about deleting out files on a JBoss redeploy. Another user mentions that the tmp directory is never purged on restart or starting and stopping JBoss as it was in previous versions. The recommendation was to edit the run.bat file to do this every time the server is started.

The fix

The suggestion was to delete the files before JBoss starts up in the run.bat file. This example is for JBoss on Windows, but its pretty easy to adapt for Linux (just edit the run.sh file instead)

Below is the excerpt from my C:\Adobe\Adobe LiveCycle ES4\jboss\bin\run.bat

pushd %DIRNAME%..
if "x%JBOSS_HOME%" == "x" (
  set "JBOSS_HOME=%CD%"
)
popd

rem Remove tmp folder on startup
set TMP_FOLDER=%JBOSS_HOME%\server\lc_turnkey\tmp
rmdir "%TMP_FOLDER%" /s /q

set DIRNAME=

if "%OS%" == "Windows_NT" (
  set "PROGNAME=%~nx0%"
) else (
  set "PROGNAME=run.bat"
)

Using Quartz as a cron scheduler for LiveCycle

LCA lot of projects I have worked on have required the ability to schedule a LiveCycle short-lived process to run at a particular time of the day and on particular days. Normally, running a script on any platform is a fairly trivial exercise as you have Task Manager in Windows (ugh) and cron under all Linux and Unix variants.  Being an old SCO OpenServer System V admin, cron is by far my scheduler of choice and people have ported cron over to Windows in binaries such as nnCron and CRONw just to name two. But it still requires you to do something outside of LiveCycle to hook it into your processes. Doing this on Windows can get quite complicated as you have to use batch files to execute PowerShell scripts with security disabled and by the end you just want to have a shower afterwards because its such a hack-a-thon. There had to be a cleaner, more integrated way to do this.

DSC is the key

So I knew LiveCycle ES4 Turnkey runs on JBoss EAP 5.1.0 and that comes with the Quartz Scheduler built in. It dawned on me that I could make use of this from within LiveCycle natively. The key was to use a Component DSC to create your very own scheduler. DSC’s can implement the LifeCycle Interface that contains onStart() and onStop() functions that run whenever the DSC or LiveCycle is started or stopped. So why not use this feature to start and stop your own Quartz Scheduler and plug in whatever Jobs needed doing?

Finding Quartz

First thing I did was locate the version of Quartz that JBoss was running. I scanned the server.log file and found this entry:

2013-08-07 19:20:12,195 INFO [org.quartz.core.QuartzScheduler] (main) Quartz Scheduler v.1.5.2 created.
2013-08-07 19:20:12,204 INFO [org.quartz.simpl.RAMJobStore] (main) RAMJobStore initialized.
2013-08-07 19:20:12,207 INFO [org.quartz.impl.DirectSchedulerFactory] (main) Quartz scheduler 'SimpleQuartzScheduler
2013-08-07 19:20:12,207 INFO [org.quartz.impl.DirectSchedulerFactory] (main) Quartz scheduler version: 1.5.2
2013-08-07 19:20:12,210 INFO [org.quartz.core.QuartzScheduler] (main) Scheduler SimpleQuartzScheduler_$_SIMPLE_NON_CLUSTERED started.

Quartz v1.5.2 was pretty old but would still serve for the purposes I wanted it for. Curiously, the IDPSchedulerService was using v1.6.0 and I dug around for this version, but it seems to be only for use by Adobe’s DSC1, so I left that alone. Also I noticed that deep in the crx-repository there was a Quartz v2.1.1 but I doubted that will be available from within JBoss either.

Creating a test DSC

I started by creating a standard DSC project in Eclipse and included the quartz.jar file I found in C:\Adobe\Adobe LiveCycle ES4\jboss\common\lib (I wont go into how you create a DSC here but this guide is a good place as any to start).

I started by creating a simple CustomSchedulerService class (SchedulerService was already taken by an Adobe DSC) to start and stop the Quartz Scheduler:

import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;

public class CustomSchedulerServiceImpl {

	private static Scheduler scheduler;

	/**
	 * Starts the Quartz Scheduler
	 */
	public static void startScheduler() {
		try {
			SchedulerFactory schedFact = new StdSchedulerFactory();

			scheduler = schedFact.getScheduler();
			scheduler.start();

		} catch (SchedulerException se) {
			se.printStackTrace();
		}
	}

	/**
	 * Stops the Quartz Scheduler
	 */
	public static void stopScheduler() {
		try {
			scheduler.shutdown();
		} catch (SchedulerException se) {
			se.printStackTrace();
		}
	}
}

Then I added the startScheduler() and stopScheduler() methods to LifeCycleImpl.java:

package com.btes.lc.dsc;

import java.util.logging.Level;
import com.adobe.idp.dsc.component.ComponentContext;
import com.adobe.idp.dsc.component.LifeCycle;
import com.adobe.logging.AdobeLogger;
import com.btes.lc.dsc.customschedulerservice.CustomSchedulerServiceImpl;

public class LifeCycleImpl implements LifeCycle {

	private static final AdobeLogger logger = AdobeLogger
			.getAdobeLogger(LifeCycleImpl.class);

	private ComponentContext m_ctx;

	public void setComponentContext(ComponentContext aCtx) {
		if (logger.isLoggable(Level.FINE)) {
			logger.log(Level.FINE, "setComponentContext: "
					+ aCtx.getComponent().getComponentId());
		}
		m_ctx = aCtx;
	}

	public void onStart() {
		logger.log(Level.INFO, "Called onStart: "
				+ m_ctx.getComponent().getComponentId());
		//start up our Quartz scheduler
		CustomSchedulerServiceImpl.startScheduler();
	}

	public void onStop() {
		logger.log(Level.INFO, "Called onStop: "
				+ m_ctx.getComponent().getComponentId());
		//shutdown the scheduler
		CustomSchedulerServiceImpl.stopScheduler();
	}
}

Created a simple component.xml file:

<component xmlns="http://adobe.com/idp/dsc/component/document">
	<component-id>com.btes.lc.dsc.customschedulerservice.CustomSchedulerService</component-id>
	<version>1.0</version>
	<bootstrap-class>com.btes.lc.dsc.BootstrapImpl</bootstrap-class>
	<lifecycle-class>com.btes.lc.dsc.LifeCycleImpl</lifecycle-class>
	<services>
		<service name="CustomSchedulerService" orchestrateable="true" title="CustomSchedulerService">
			<implementation-class>com.btes.lc.dsc.customschedulerservice.CustomSchedulerServiceImpl</implementation-class>
			<auto-deploy major-version="1" minor-version="0" category-id="CustomScheduler" />
			<operations>
			</operations>
		</service>
	</services>
	<supports-export>true</supports-export>
   <class-path>quartz.jar</class-path>
</component>

I then did a quick build and installed and started the Component into LiveCycle via Workbench and voila!

2013-08-07 22:22:56,610 INFO  [com.btes.lc.dsc.LifeCycleImpl] (http-0.0.0.0-8080-5) Called onStart: com.btes.lc.dsc.customschedulerservice.CustomSchedulerService
2013-08-07 22:22:56,616 INFO  [org.quartz.simpl.SimpleThreadPool] (http-0.0.0.0-8080-5) Job execution threads will use class loader of thread: http-0.0.0.0-8080-5
2013-08-07 22:22:56,618 INFO  [org.quartz.core.QuartzScheduler] (http-0.0.0.0-8080-5) Quartz Scheduler v.1.5.2 created.
2013-08-07 22:22:56,618 INFO  [org.quartz.simpl.RAMJobStore] (http-0.0.0.0-8080-5) RAMJobStore initialized.
2013-08-07 22:22:56,618 INFO  [org.quartz.impl.StdSchedulerFactory] (http-0.0.0.0-8080-5) Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
2013-08-07 22:22:56,618 INFO  [org.quartz.impl.StdSchedulerFactory] (http-0.0.0.0-8080-5) Quartz scheduler version: 1.5.2
2013-08-07 22:22:56,618 INFO  [org.quartz.core.QuartzScheduler] (http-0.0.0.0-8080-5) Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.

Stopping the DSC also stopped the scheduler:

2013-08-07 22:27:03,949 INFO  [com.adobe.idp.dsc.registry.service.impl.ServiceRegistryImpl] (http-0.0.0.0-8080-5) ServiceRegistryImpl:stop(CustomSchedulerService, 1.0)
2013-08-07 22:27:03,982 INFO  [com.btes.lc.dsc.LifeCycleImpl] (http-0.0.0.0-8080-5) Called onStop: com.btes.lc.dsc.customschedulerservice.CustomSchedulerService
2013-08-07 22:27:03,982 INFO  [org.quartz.core.QuartzScheduler] (http-0.0.0.0-8080-5) Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down.
2013-08-07 22:27:03,982 INFO  [org.quartz.core.QuartzScheduler] (http-0.0.0.0-8080-5) Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED paused.
2013-08-07 22:27:03,982 INFO  [org.quartz.core.QuartzScheduler] (http-0.0.0.0-8080-5) Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutdown complete.

I am assuming that DefaultQuartzScheduler is the name assigned inside the default quartz.properties configuration file that is inside the quartz.jar file provided by JBoss.

Do something

Next I created a simple Job file that I would eventually use to invoke a process. For now it will just print some output into the server.log at regular intervals.

package com.btes.lc.dsc.customschedulerservice.jobs;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
 * Simple Process Job for Livecycle
 *
 */
public class ProcessJob implements Job {

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		String jobKey = context.getJobDetail().getName();
		String date = new Date().toString();
		System.out.println("ProcessJob says: " + jobKey + " executing at " + date);
	}
}

Then I would need a Trigger to start the Job which I put into the main Component code for now and scheduled the job as soon as the Scheduler was started. The cron expression "0/20 * * * * ?" would execute the CronTrigger every 20 seconds until the scheduler was stopped.

package com.btes.lc.dsc.customschedulerservice;

import java.text.ParseException;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;
import com.btes.lc.dsc.customschedulerservice.jobs.ProcessJob;

public class CustomSchedulerServiceImpl implements CustomSchedulerService {

	private static Scheduler scheduler;

	/**
	 * Starts the Quartz Scheduler
	 */
	public static void startScheduler() {
		try {
			SchedulerFactory schedFact = new StdSchedulerFactory();

			scheduler = schedFact.getScheduler();
			scheduler.start();

			CustomSchedulerServiceImpl.scheduleJobs();

		} catch (SchedulerException se) {
			se.printStackTrace();
		}
	}

	/**
	 * Stops the Quartz Scheduler
	 */
	public static void stopScheduler() {
		try {
			scheduler.shutdown();
		} catch (SchedulerException se) {
			se.printStackTrace();
		}
	}

	/**
	 * Schedules some jobs
	 */
	public static void scheduleJobs() {
		try {
			JobDetail job = new JobDetail("myJob", Scheduler.DEFAULT_GROUP, ProcessJob.class);
			CronTrigger trigger = new CronTrigger();
			trigger.setCronExpression("0/20 * * * * ?");
			trigger.setGroup(Scheduler.DEFAULT_GROUP);
			trigger.setName("Trigger1");

			scheduler.scheduleJob(job, trigger);

		} catch (SchedulerException se) {
			se.printStackTrace();
		} catch (ParseException e) {
			e.printStackTrace();
		}
	}
}

Compiling this code and deploying it to the LiveCycle server gave this output to STDOUT:

2013-08-07 22:45:00,017 INFO  [STDOUT] (DefaultQuartzScheduler_Worker-7) ProcessJob says: myJob executing at Wed Aug 07 22:45:00 EST 2013
2013-08-07 22:45:20,019 INFO  [STDOUT] (DefaultQuartzScheduler_Worker-8) ProcessJob says: myJob executing at Wed Aug 07 22:45:20 EST 2013

Now to make it really DoSomething

Next, I created a very simple application within Workbench that would accept some input and write that out to the server.log file. Crucially, this input should come from the Quartz Scheduler.

SchedulerTest Application

SchedulerTest Application

The executeScript process just grabs the two incoming parameters (inJobName and inDate) and writes them to STDOUT in the server.log file.

System.out.println("SchedulerTest::DoSomething - I am doing something with: "
+ patExecContext.getProcessDataStringValue("/process_data/@inJobName")
+ " invoked on " + patExecContext.getProcessDataStringValue("/process_data/@inDate"));

Next I added the relevant libraries to my Eclipse project and the code to invoke the LiveCycle process to the Job:

package com.btes.lc.dsc.customschedulerservice.jobs;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import com.adobe.idp.dsc.DSCException;
import com.adobe.idp.dsc.InvocationRequest;
import com.adobe.idp.dsc.InvocationResponse;
import com.adobe.idp.dsc.clientsdk.ServiceClient;
import com.adobe.idp.dsc.clientsdk.ServiceClientFactory;
import com.adobe.idp.dsc.clientsdk.ServiceClientFactoryProperties;

/**
 * Simple Process Job for Livecycle
 *
 */
public class ProcessJob implements Job {

	@Override
	public void execute(JobExecutionContext context)
			throws JobExecutionException {
		String jobKey = context.getJobDetail().getName();
		String date = new Date().toString();
		System.out.println("ProcessJob says: " + jobKey + " executing at " + date);

		Properties ConnectionProps = new Properties();
		ConnectionProps.setProperty(
				ServiceClientFactoryProperties.DSC_DEFAULT_EJB_ENDPOINT,
				"jnp://localhost:1099");
		ConnectionProps.setProperty(
				ServiceClientFactoryProperties.DSC_TRANSPORT_PROTOCOL,
				ServiceClientFactoryProperties.DSC_EJB_PROTOCOL);
		ConnectionProps.setProperty(
				ServiceClientFactoryProperties.DSC_SERVER_TYPE, "JBoss");
		ConnectionProps.setProperty(
				ServiceClientFactoryProperties.DSC_CREDENTIAL_USERNAME,
				"administrator");
		ConnectionProps.setProperty(
				ServiceClientFactoryProperties.DSC_CREDENTIAL_PASSWORD,
				"password");

		ServiceClientFactory myFactory = ServiceClientFactory
				.createInstance(ConnectionProps);

		ServiceClient myServiceClient = myFactory.getServiceClient();

		// Create a Map object to store the parameter value

		Map params = new HashMap();

		// Populate the Map object with a parameter value
		params.put("inJobName", context.getJobDetail().getName());
		params.put("inDate", date);

		// Create an InvocationRequest object
		InvocationRequest esRequest = myFactory.createInvocationRequest(
				"SchedulerTest/DoSomething", // Specify the LiveCycle ES process name
				"invoke", // Specify the operation name
				params, // Specify input values
				true); // Create a synchronous request

		// Send the invocation request to the process and
		// get back an invocation response object
		try {
			InvocationResponse esResponse = myServiceClient.invoke(esRequest);
		} catch (DSCException e) {
			e.printStackTrace();
		}
	}
}

I deployed both the Application and the DSC to LiveCycle. I started the DSC and got this as the output:

2013-08-07 23:06:00,020 INFO  [STDOUT] (DefaultQuartzScheduler_Worker-9) ProcessJob says: myJob executing at Wed Aug 07 23:06:00 EST 2013
2013-08-07 23:06:00,060 INFO  [STDOUT] (DefaultQuartzScheduler_Worker-9) SchedulerTest::DoSomething - I am doing something with: myJob invoked on Wed Aug 07 23:06:00 EST 2013
2013-08-07 23:06:20,015 INFO  [STDOUT] (DefaultQuartzScheduler_Worker-9) ProcessJob says: myJob executing at Wed Aug 07 23:06:20 EST 2013
2013-08-07 23:06:20,047 INFO  [STDOUT] (DefaultQuartzScheduler_Worker-9) SchedulerTest::DoSomething - I am doing something with: myJob invoked on Wed Aug 07 23:06:20 EST 2013

As you can see, the CronTrigger ran the ProcessJob every 20 seconds which in turn invoked the LiveCycle process. I’m happy!

Where to from here?

This is obviously a very basic proof-of-concept to show that invoking a LiveCycle process using cron expressions is possible natively from within Livecycle on JBoss. There are a number of things you would need to do to harden this up like use your own quartz.properties file as well as extend it for all sorts of custom configuration. There are plugins for Quartz that allow you to create an XML file to define your jobs and pass in all of the required information to start, stop and handle the execution of those jobs properly. It wouldn’t be a complex exercise to set this up.

Sample files

Below is the sample LCA and the DSC source that I created for this exercise. The libraries are for ES3/ADEP and ES4 but if you replace the libs with ES2.5 libraries, the code should also work.

CustomSchedulerService.zip

SchedulerTest.lca

1 I dug around some more and the IDPSchedulerService does indeed bundle a version of v1.6.0 of quartz.jar inside the adobe-scheduler-jboss.jar which makes me wonder if its possible to replace the v1.5.2 version in JBoss with a later v1.8.5 without breaking anything LiveCycle related…

Log file viewer for Workbench

LCLiveCycle Workbench includes a Server Log Viewer by default but sometimes you want to view multiple log files and have syntax highlighting to help you debug your applications while you work on your LiveCycle application. I also found the built-in viewer extremely limited and it didn’t always tail the server.log file properly and also required you to be logged into LiveCycle to use it!

Eclipse Labs logviewer

For both Eclipse and Workbench I use the logviewer Eclipse plugin to look at any log files I am generating during development. It’s simple, very configurable, has syntax highlighting and allows you to open multiple files at once.

http://logviewer.eclipselabs.org.codespot.com/git/de.anbos.eclipse.logviewer.update/

Wizard Install

Installation is simple since Adobe left in the Eclipse Software Updates feature:

1. Open Help > Software Updates > Find and Install…

Find and Install...

Find and Install…

2. Select Search for new features to install then Next >

Search for new features to install

Search for new features to install

3. Click New Remote Site… and copy the Update Site URL from the logviewer page and click OK

New Update Site

New Update Site

Install

Install

4. Click Finish then Select logviewer feature checkbox and then click Next >

Select logviewer feature

Select logviewer feature

5. Select I accept the terms in the license agreement and Next >, Finish then Install All

Feature License

Feature License

Installation

Installation

Feature Verification

Feature Verification

6. Click Yes  to Restart Workbench

Restart Workbench

Restart Workbench

7. Once Workbench has restarted, click Window > Show View > Other…

Show View

Show View

8. Select Log Viewer plugin from within its folder and click OK

Log Viewer Plugin

Log Viewer Plugin

9. Enjoy a decent log viewer / file tailer with plenty of features!

Bonus: you don’t even need to be logged into LiveCycle to use it…

Log Viewer action shot

Log Viewer action shot

Manual Install

If the install above is all too hard or you are a manual-eclipse-plugin-installer-kinda-developer, then:

1. Copy the latest Jar from here: http://logviewer.eclipselabs.org.codespot.com/git/de.anbos.eclipse.logviewer.update/plugins/

2. Bung it in the Workbench plugins folder (probably something like C:\Program Files (x86)\Adobe LiveCycle Workbench ES4\workbench\plugins)

3. Start Workbench and tail away…

Enable anonymous access for testing Mobile Forms

IconSometimes it’s easier to enable anonymous unsecured access to pages, assets, etc for testing and quick demos so you don’t have to keep logging in via the SSO screen. After playing with the user security in CQ/CRX in Livecycle ES4, you soon discover that just giving the anonymous user access to your pages or assets isn’t enough to make this happen.  You have to explicitly allow access to these resources from within the OSGi configuration in the Adobe CQ Web Console.

If you want to allow anonymous access to your resources then do the following:

  1. Open OSGi console:  http://[server]:[port]/lc/system/console/configMgr
  2. Search for Apache Sling Authentication Service Bundle
  3. Click the bundle to open it
  4. Ensure Allow Anonymous Access is checked
  5. Under Authentication Requirements section, you need to add an entry that starts
    -/content/node/you/want/to/allow/anonymous/access/to” (without quotes)
  6. Click Save

    Apache Sling Authentication Service

    Apache Sling Authentication Service

  7. Open CQ5 Security:  http://[server]:[port]/lc/useradmin
  8. Double click the everyone group
  9. On the right, click the Permissions tab
  10. Browse to the content node you allowed above
  11. Add the rights you require for the anonymous user (usually its just Read)
  12. Click Save
    CQ5 Security

    CQ5 Security

aemblog

Everything AEM aka CQ5 based on my experience listed here.

Adobe AEM The Right Way

Best practices, tips, and tricks for your Adobe AEM project

/home/bkondepudi

A WCM journey with Day/Adobe CQ

Technoracle (a.k.a. "Duane's World")

A multi-purpose toolkit for the Adobe LiveCycle and AEM Forms developer.

Adobe LiveCycle Blog

A multi-purpose toolkit for the Adobe LiveCycle and AEM Forms developer.

A multi-purpose toolkit for the Adobe LiveCycle and AEM Forms developer.

XFA@Mobile

A multi-purpose toolkit for the Adobe LiveCycle and AEM Forms developer.

Code Monkey

Ramblings of a Developer