Image via Wikipedia
In a previous post, I mentioned how to add a shutdown hook to your application. This is great for the case where you need to do some cleanup prior to exiting. The example below shows how to convert your class to work like a daemon on UNIX, or a service on Windows. I will not go into details on how to create a UNIX, or Windows service. I will provide a StartupItem
example for Mac OS X, or a modern inetd
plist for launchd
.In the example classes below, I create a value holder object, a
Callable<ValueHolder>
, a Future<ValueHolder>
, an ExecutorService
which uses a SingleThreadExecutor
. The concurrency libraries are from java.util.concurrent which is based on the original work done by Doug Lea, et. al. These classes make doing Thread
based programming much simpler.Here are the relevant files for the application:
JavaDaemonExample.zip (NetBeans 7.0 project)
JavaDaemonStartupItem.zip (This is for OS X 10.4 or less. It will work on 10.5+, but is not preferred)
com.bluelotussoftware.examples.Daemon (This is the
inetd
plist for launchd
10.5+ preferred)Before we get to the code, the Mac OS X daemon controls must be modified to match your environment.
The
com.bluelotussoftware.examples.Daemon
plist file needs to modified to point to the jar file to execute. This is for Leopard, and Snow Leopard.- To install it, place it in the
~/Library/LaunchAgents
directory. - From a command prompt, type:
launchctl load -w /Users/jyeary/Library/LaunchAgents/JavaDaemonStartupItem/com.bluelotussoftware.examples.Daemon
This loads it. - To execute from
launchctl
:
root:$ launctl
launchd% start com.bluelotussoftware.examples.Daemon
launchd% list
launchd% quit
root:$
Alternatively, you can use the
JavaDaemonStartupItem
.- Modify the
JavaDaemonStartupItem
script to match your environment. - Place the files in the
/Library/StartupItems
directory. - Change permissions:
sudo chown -R root:wheel JavaDaemonStartupItem
- Execute the application using:
sudo /sbin/SystemStarter start "Java Daemon Example"
This is the main code Example:
Daemon.java
/* * Copyright 2011 John Yeary <jyeary@bluelotussoftware.com>. * * Licensed 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. */ /* * $Id: Daemon.java 355 2011-05-31 21:05:01Z jyeary $ */ package com.bluelotussoftware.examples; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.logging.FileHandler; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; /** * Main executable daemon class. * @author John Yeary <jyeary@bluelotussoftware.com> * @version 1.0 */ public class Daemon { private static final Logger logger = Logger.getLogger(Daemon.class.getName()); /** * Default no-argument constructor. */ public Daemon() { } /** * Main execution entry point. * @param args the command line arguments */ public static void main(String[] args) { try { /* Create a file handler which is used to log output. This * becomes vitally important since we close the System.out, * System.err, and System.in streams next. */ Handler handler = new FileHandler(Daemon.class.getName() + ".log"); logger.addHandler(handler); } catch (IOException ex) { ex.printStackTrace(System.err); } catch (SecurityException ex) { ex.printStackTrace(System.err); } logger.log(Level.INFO, "Closing streams"); closeDefaultStreams(); logger.log(Level.INFO, "Fetching ExecutorService"); final ExecutorService executorService = Executors.newSingleThreadExecutor(); logger.log(Level.INFO, "Adding Shutdown Hook"); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { shutdown(executorService); } }); logger.log(Level.INFO, "Submitting CallableDaemon"); Future<ValueHolder> future = executorService.submit(new CallableDaemon()); logger.log(Level.INFO, "Waiting for results..."); try { StringBuilder sb = new StringBuilder(); sb.append("Results: "); sb.append(Arrays.toString(future.get().getDates().toArray())); logger.log(Level.INFO, sb.toString()); } catch (InterruptedException ex) { logger.log(Level.SEVERE, null, ex); } catch (ExecutionException ex) { logger.log(Level.SEVERE, null, ex); } logger.log(Level.INFO, "Shutting down ExecutorService normally."); /* Call to shutdown executor service, otherwise the application blocks * and waits for another Runnable to be submitted. You can verify this in the * debugger, or by submitting another Runnable. */ executorService.shutdown(); } //TODO Need to verify that this is necessary for Windows systems. /** * This method closes {@code System#out}, {@code System#err}, and {@code System#in} * streams. This prevents premature shutdown from closure of terminal. */ private static void closeDefaultStreams() { System.out.close(); System.err.close(); try { System.in.close(); } catch (IOException ex) { logger.log(Level.WARNING, "Closing System.in() caused an Exception", ex); } } /** * Shutdown hook which calls {@code java.util.concurrent.ExecutorService#shutdownNow()} * @param executorService the executor which we want to attach to, and shutdown. */ private static void shutdown(ExecutorService executorService) { logger.log(Level.INFO, "Executing shutdown hook"); List<Runnable> runnables = executorService.shutdownNow(); logger.log(Level.INFO, "The following items were awaiting execution when the daemon shutdown", Arrays.toString(runnables.toArray(new Runnable[runnables.size()]))); } }
This is the
Callable<ValueHolder>
:CallableDaemon.java
/* * Copyright 2011 John Yeary <jyeary@bluelotussoftware.com>. * * Licensed 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. */ /* * $Id: CallableDaemon.java 355 2011-05-31 21:05:01Z jyeary $ */ package com.bluelotussoftware.examples; import java.util.Date; import java.util.concurrent.Callable; /** * An example <code>Callable</code> implementation which executes and * waits. Every execution adds a <code>Date</code> to a <code>ValueHolder</code>. * On <code>call</code> completion, it returns the <code>ValueHolder</code>. * @author John Yeary <jyeary@bluelotussoftware.com> * @version 1.0 */ public class CallableDaemon implements Callable<ValueHolder> { private int counter; private int iterations = 12; /** * Default no-argument constructor. */ public CallableDaemon() { } /** * Constructor which sets the number of iterations. * @param iterations integral value which sets the number * of iterations to be executed. */ public CallableDaemon(int iterations) { this.iterations = iterations; } /** * {@inheritDoc java.util.concurrent.Callable#call()} */ @Override public ValueHolder call() throws Exception { ValueHolder valueHolder = new ValueHolder(); for (; counter < iterations; counter++) { valueHolder.getDates().add(new Date()); synchronized (this) { this.wait(5000); } } return valueHolder; } }
ValueHolder
/* * Copyright 2011 John Yeary <jyeary@bluelotussoftware.com>. * * Licensed 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. */ /* * $Id: ValueHolder.java 355 2011-05-31 21:05:01Z jyeary $ */ package com.bluelotussoftware.examples; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * A value holder object. * @author John Yeary <jyeary@bluelotussoftware.com> */ public class ValueHolder { private List<Date> dates; /** * Default no-argument constructor. */ public ValueHolder() { dates = new ArrayList<Date>(); } /** * Returns a {@code List<Date>} objects contained in the <code>ValueHolder</code>. * The method is synchronized because it can be used to add values to the internal * <code>List</code>. * @return {@code List<Date>} objects contained in the <code>ValueHolder</code>. */ public synchronized List<Date> getDates() { return dates; } }
No comments:
Post a Comment