Java tutorial
/* * Copyright (c) 2012 Diamond Light Source Ltd. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.dawnsci.commandserver.core.process; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.lang.ProcessBuilder.Redirect; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.Topic; import org.dawnsci.commandserver.core.ConnectionFactoryFacade; import org.dawnsci.commandserver.core.beans.Status; import org.dawnsci.commandserver.core.beans.StatusBean; import org.dawnsci.commandserver.core.producer.Broadcaster; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.sun.jna.Platform; /** * Extend to provide a connection between a running process * and its * * @author Matthew Gerring * */ public abstract class ProgressableProcess implements Runnable, IBroadcaster { private boolean blocking = false; private boolean isCancelled = false; protected StatusBean bean; protected URI uri; protected String statusTName; protected String statusQName; private Broadcaster broadcaster; protected PrintStream out = System.out; protected ProgressableProcess() { super(); } public ProgressableProcess(final URI uri, final String statusTName, final String statusQName, StatusBean bean) { this.uri = uri; this.statusTName = statusTName; this.statusQName = statusQName; this.bean = bean; this.broadcaster = new Broadcaster(uri, statusQName, statusTName); bean.setStatus(Status.QUEUED); try { bean.setHostName(InetAddress.getLocalHost().getHostName()); } catch (UnknownHostException e) { // Not fatal but would be nice... e.printStackTrace(); } broadcast(bean); } protected void setLoggingFile(File logFile) throws IOException { setLoggingFile(logFile, false); } /** * Calling this method redirects the logging of this Java object * which is available through the field 'out' to a known file. * * @param logFile * @throws IOException */ protected void setLoggingFile(File logFile, boolean append) throws IOException { if (!logFile.exists()) logFile.createNewFile(); this.out = new PrintStream(new BufferedOutputStream(new FileOutputStream(logFile, append)), true, "UTF-8"); broadcaster.setLoggingStream(out); } @Override public final void run() { try { execute(); if (out != System.out) { out.close(); out = System.out; } } catch (Exception ne) { ne.printStackTrace(out); bean.setStatus(Status.FAILED); bean.setMessage(ne.getMessage()); bean.setPercentComplete(0); broadcast(bean); } } /** * Execute the process, if an exception is thrown the process is set to * failed and the message is the message of the exception. * * @throws Exception */ public abstract void execute() throws Exception; /** * Please provide a termination for the process by implementing this method. * If the process has a stop file, write it now; if it needs to be killed, * get its pid and kill it; if it is running on a cluster, use the qdel or dramaa api. * * @throws Exception */ public abstract void terminate() throws Exception; /** * @return true if windows */ static public final boolean isWindowsOS() { return (System.getProperty("os.name").indexOf("Windows") == 0); } /** * Writes the project bean at the point where it is run. * * @param processingDir2 * @param fileName - name of file * @throws IOException * @throws FileNotFoundException * @throws JsonMappingException * @throws JsonGenerationException */ protected void writeProjectBean(final String dir, final String fileName) throws Exception { writeProjectBean(new File(dir), fileName); } /** * * @param dir * @param fileName * @throws Exception */ protected void writeProjectBean(final File dir, final String fileName) throws Exception { final File beanFile = new File(dir, fileName); ObjectMapper mapper = new ObjectMapper(); beanFile.getParentFile().mkdirs(); if (!beanFile.exists()) beanFile.createNewFile(); final FileOutputStream stream = new FileOutputStream(beanFile); try { mapper.writeValue(stream, bean); } finally { stream.close(); } } /** * Call to start the process and broadcast status * updates. Subclasses may redefine what is done * on the start method, by default a thread is started * in daemon mode to run things. */ public void start() { if (isBlocking()) { run(); // Block until process has run. } else { final Thread thread = new Thread(this); thread.setDaemon(true); thread.setPriority(Thread.MAX_PRIORITY); thread.start(); } } /** * Notify any clients of the beans status * @param bean */ @Override public void broadcast(StatusBean tbean) { try { bean.merge(tbean); cancelMonitor(); broadcaster.broadcast(bean, false); } catch (Exception e) { e.printStackTrace(); } } /** * Cancels the current topic monitor, if there is one. Prints exception if cannot. */ private void cancelMonitor() { if (bean.getStatus().isFinal() && topicConnection != null) { try { topicConnection.close(); } catch (Exception ne) { ne.printStackTrace(); } } } protected Connection topicConnection; /** * Starts a connection which listens to the topic and if * a cancel is found published, tries to terminate the subprocess. * * @param p */ protected void createTerminateListener() throws Exception { ConnectionFactory connectionFactory = ConnectionFactoryFacade.createConnectionFactory(uri); ProgressableProcess.this.topicConnection = connectionFactory.createConnection(); topicConnection.start(); Session session = topicConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); final Topic topic = session.createTopic(statusTName); final MessageConsumer consumer = session.createConsumer(topic); final Class clazz = bean.getClass(); final ObjectMapper mapper = new ObjectMapper(); MessageListener listener = new MessageListener() { public void onMessage(Message message) { try { if (message instanceof TextMessage) { TextMessage t = (TextMessage) message; final StatusBean tbean = mapper.readValue(t.getText(), clazz); if (bean.getStatus().isFinal()) { // Something else already happened topicConnection.close(); return; } if (bean.getUniqueId().equals(tbean.getUniqueId())) { if (tbean.getStatus() == Status.REQUEST_TERMINATE) { bean.merge(tbean); out.println("Terminating job '" + tbean.getName() + "'"); terminate(); topicConnection.close(); bean.setStatus(Status.TERMINATED); bean.setMessage("Foricibly terminated before finishing."); broadcast(bean); return; } } } } catch (Exception e) { e.printStackTrace(); } } }; consumer.setMessageListener(listener); } protected void pkill(int pid, String dir) throws Exception { // Use pkill, seems to kill all of the tree more reliably ProcessBuilder pb = new ProcessBuilder(); // Can adjust env if needed: // Map<String, String> env = pb.environment(); pb.directory(new File(dir)); File log = new File(dir, "xia2_kill.txt"); pb.redirectErrorStream(true); pb.redirectOutput(Redirect.appendTo(log)); pb.command("bash", "-c", "pkill -9 -s " + pid); Process p = pb.start(); p.waitFor(); } protected static int getPid(Process p) throws Exception { if (Platform.isWindows()) { Field f = p.getClass().getDeclaredField("handle"); f.setAccessible(true); int pid = Kernel32.INSTANCE.GetProcessId((Long) f.get(p)); return pid; } else if (Platform.isLinux()) { Field f = p.getClass().getDeclaredField("pid"); f.setAccessible(true); int pid = (Integer) f.get(p); return pid; } else { throw new Exception("Cannot currently process pid for " + System.getProperty("os.name")); } } public boolean isCancelled() { return isCancelled; } public void setCancelled(boolean isCancelled) { this.isCancelled = isCancelled; } protected void dryRun() { dryRun(100); } protected void dryRun(int size) { for (int i = 0; i < size; i++) { if (bean.getStatus() == Status.REQUEST_TERMINATE || bean.getStatus() == Status.TERMINATED) { return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Dry run : " + bean.getPercentComplete()); bean.setPercentComplete(i); broadcast(bean); } bean.setStatus(Status.COMPLETE); bean.setPercentComplete(100); bean.setMessage("Dry run complete (no software run)"); broadcast(bean); } /** * @param dir * @param template * @param ext * @param i * @return file */ protected final static File getUnique(final File dir, final String template, int i) { final File file = new File(dir, template + i); if (!file.exists()) { return file; } return getUnique(dir, template, ++i); } public boolean isBlocking() { return blocking; } public void setBlocking(boolean blocking) { this.blocking = blocking; } public static final String getLegalFileName(String name) { name = name.replace(" ", "_"); name = name.replaceAll("[^a-zA-Z0-9_]", ""); return name; } }