CRXDE Lite code formatting

CRXDELite_Logo

One thing I always missed in CRXDE Lite was the lack of any code formatting tools. I always had to copy the code out into Notepad++ or Eclipse to format the code and then paste back into my JSP files. Not very convenient.

Today I accidentally stumbled across what I expect is an undocumented feature in CRXDE Lite – Code Formatting!

Shift-Tab

If you select any or all of the code on a page, and then hit Shift-Tab, it doesn’t tab the code back one tab stop (as you would expect), it actually formats the code.

Before

Before

After

After

Version

This was done with CRXDE Lite in CRX 2.4.23 (CQ 5.6.0.20130125) that comes with Adobe Livecycle ES4.  It seems to work in Chrome, Firefox and Intenet Exploder 10.

Advertisements

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