Java tutorial
/* * Copyright (C) 2010-2011 The University of Manchester * * See the file "LICENSE.txt" for license terms. */ package org.taverna.server.master.localworker; import static java.util.Calendar.MINUTE; import static java.util.Collections.unmodifiableSet; import static java.util.UUID.randomUUID; import static org.apache.commons.logging.LogFactory.getLog; import static org.taverna.server.master.localworker.RemoteRunDelegate.checkBadFilename; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.rmi.MarshalledObject; import java.rmi.RemoteException; import java.security.GeneralSecurityException; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.logging.Log; import org.taverna.server.localworker.remote.IllegalStateTransitionException; import org.taverna.server.localworker.remote.ImplementationException; import org.taverna.server.localworker.remote.RemoteDirectory; import org.taverna.server.localworker.remote.RemoteDirectoryEntry; import org.taverna.server.localworker.remote.RemoteFile; import org.taverna.server.localworker.remote.RemoteInput; import org.taverna.server.localworker.remote.RemoteListener; import org.taverna.server.localworker.remote.RemoteSingleRun; import org.taverna.server.localworker.remote.RemoteStatus; import org.taverna.server.master.common.Status; import org.taverna.server.master.common.Workflow; import org.taverna.server.master.exceptions.BadPropertyValueException; import org.taverna.server.master.exceptions.BadStateChangeException; import org.taverna.server.master.exceptions.FilesystemAccessException; import org.taverna.server.master.exceptions.NoListenerException; import org.taverna.server.master.interfaces.Directory; import org.taverna.server.master.interfaces.DirectoryEntry; import org.taverna.server.master.interfaces.File; import org.taverna.server.master.interfaces.Input; import org.taverna.server.master.interfaces.Listener; import org.taverna.server.master.interfaces.SecurityContextFactory; import org.taverna.server.master.interfaces.TavernaRun; import org.taverna.server.master.interfaces.TavernaSecurityContext; import org.taverna.server.master.utils.UsernamePrincipal; /** * Bridging shim between the WebApp world and the RMI world. * * @author Donal Fellows */ @edu.umd.cs.findbugs.annotations.SuppressWarnings("SE_NO_SERIALVERSIONID") public class RemoteRunDelegate implements TavernaRun { private transient Log log = getLog("Taverna.Server.LocalWorker"); transient TavernaSecurityContext secContext; Date creationInstant; Workflow workflow; Date expiry; HashSet<String> readers; HashSet<String> writers; HashSet<String> destroyers; transient String id; transient RemoteSingleRun run; transient RunDBSupport db; boolean doneTransitionToFinished; RemoteRunDelegate(Date creationInstant, Workflow workflow, RemoteSingleRun rsr, int defaultLifetime, RunDBSupport db, UUID id) { if (rsr == null) { throw new IllegalArgumentException("remote run must not be null"); } this.creationInstant = creationInstant; this.workflow = workflow; Calendar c = Calendar.getInstance(); c.add(MINUTE, defaultLifetime); this.expiry = c.getTime(); this.run = rsr; this.db = db; if (id != null) this.id = id.toString(); } RemoteRunDelegate() { } @Override public void addListener(Listener listener) { if (listener instanceof ListenerDelegate) try { run.addListener(((ListenerDelegate) listener).getRemote()); } catch (RemoteException e) { log.warn("communication problem adding listener", e); } catch (ImplementationException e) { log.warn("implementation problem adding listener", e); } else log.fatal("bad listener " + listener.getClass() + "; not applicable remotely!"); } @Override public String getId() { if (id == null) id = randomUUID().toString(); return id; } /** * Attach a listener to a workflow run and return its local delegate. * * @param type * The type of listener to create. * @param config * The configuration of the listener. * @return The local delegate of the listener. * @throws NoListenerException * If anything goes wrong. */ public Listener makeListener(String type, String config) throws NoListenerException { try { return new ListenerDelegate(run.makeListener(type, config)); } catch (RemoteException e) { throw new NoListenerException("failed to make listener", e); } } @Override public void destroy() { try { run.destroy(); } catch (RemoteException e) { log.warn("failed to destroy run", e); } catch (ImplementationException e) { log.warn("failed to destroy run", e); } } @Override public Date getExpiry() { return new Date(expiry.getTime()); } @Override public List<Listener> getListeners() { ArrayList<Listener> listeners = new ArrayList<Listener>(); try { for (RemoteListener rl : run.getListeners()) { listeners.add(new ListenerDelegate(rl)); } } catch (RemoteException e) { log.warn("failed to get listeners", e); } return listeners; } @Override public TavernaSecurityContext getSecurityContext() { return secContext; } @Override public Status getStatus() { try { switch (run.getStatus()) { case Initialized: return Status.Initialized; case Operating: return Status.Operating; case Stopped: return Status.Stopped; case Finished: return Status.Finished; } } catch (RemoteException e) { log.warn("problem getting remote status", e); } return Status.Finished; } @Override public Workflow getWorkflow() { return workflow; } @Override public Directory getWorkingDirectory() throws FilesystemAccessException { try { return new DirectoryDelegate(run.getWorkingDirectory()); } catch (Throwable e) { if (e.getCause() != null) e = e.getCause(); throw new FilesystemAccessException("problem getting main working directory handle", e); } } @Override public void setExpiry(Date d) { if (d.after(new Date())) expiry = new Date(d.getTime()); db.flushToDisk(this); } @Override public void setStatus(Status s) throws BadStateChangeException { try { switch (s) { case Initialized: run.setStatus(RemoteStatus.Initialized); break; case Operating: if (run.getStatus() == RemoteStatus.Initialized) { secContext.conveySecurity(); } run.setStatus(RemoteStatus.Operating); break; case Stopped: run.setStatus(RemoteStatus.Stopped); break; case Finished: run.setStatus(RemoteStatus.Finished); break; } } catch (IllegalStateTransitionException e) { throw new BadStateChangeException(e.getMessage()); } catch (RemoteException e) { throw new BadStateChangeException(e.getMessage(), e.getCause()); } catch (GeneralSecurityException e) { throw new BadStateChangeException(e.getMessage(), e); } catch (IOException e) { throw new BadStateChangeException(e.getMessage(), e); } catch (ImplementationException e) { if (e.getCause() != null) throw new BadStateChangeException(e.getMessage(), e.getCause()); throw new BadStateChangeException(e.getMessage(), e); } } static void checkBadFilename(String filename) throws FilesystemAccessException { if (filename.startsWith("/")) throw new FilesystemAccessException("filename may not be absolute"); if (Arrays.asList(filename.split("/")).contains("..")) throw new FilesystemAccessException("filename may not refer to parent"); } @Override public String getInputBaclavaFile() { try { return run.getInputBaclavaFile(); } catch (RemoteException e) { log.warn("problem when fetching input baclava file", e); return null; } } @Override public List<Input> getInputs() { ArrayList<Input> inputs = new ArrayList<Input>(); try { for (RemoteInput ri : run.getInputs()) { inputs.add(new RunInput(ri)); } } catch (RemoteException e) { log.warn("problem when fetching list of workflow inputs", e); } return inputs; } @Override public String getOutputBaclavaFile() { try { return run.getOutputBaclavaFile(); } catch (RemoteException e) { log.warn("problem when fetching output baclava file", e); return null; } } @Override public Input makeInput(String name) throws BadStateChangeException { try { return new RunInput(run.makeInput(name)); } catch (RemoteException e) { throw new BadStateChangeException("failed to make input", e); } } @Override public void setInputBaclavaFile(String filename) throws FilesystemAccessException, BadStateChangeException { checkBadFilename(filename); try { run.setInputBaclavaFile(filename); } catch (RemoteException e) { throw new FilesystemAccessException("cannot set input baclava file name", e); } } @Override public void setOutputBaclavaFile(String filename) throws FilesystemAccessException, BadStateChangeException { checkBadFilename(filename); try { run.setOutputBaclavaFile(filename); } catch (RemoteException e) { throw new FilesystemAccessException("cannot set output baclava file name", e); } } @Override public Date getCreationTimestamp() { return creationInstant == null ? null : new Date(creationInstant.getTime()); } @Override public Date getFinishTimestamp() { try { return run.getFinishTimestamp(); } catch (RemoteException e) { log.info("failed to get finish timestamp", e); return null; } } @Override public Date getStartTimestamp() { try { return run.getStartTimestamp(); } catch (RemoteException e) { log.info("failed to get finish timestamp", e); return null; } } /** * @param readers * the readers to set */ public void setReaders(Set<String> readers) { this.readers = new HashSet<String>(readers); db.flushToDisk(this); } /** * @return the readers */ public Set<String> getReaders() { return readers == null ? new HashSet<String>() : unmodifiableSet(readers); } /** * @param writers * the writers to set */ public void setWriters(Set<String> writers) { this.writers = new HashSet<String>(writers); db.flushToDisk(this); } /** * @return the writers */ public Set<String> getWriters() { return writers == null ? new HashSet<String>() : unmodifiableSet(writers); } /** * @param destroyers * the destroyers to set */ public void setDestroyers(Set<String> destroyers) { this.destroyers = new HashSet<String>(destroyers); db.flushToDisk(this); } /** * @return the destroyers */ public Set<String> getDestroyers() { return destroyers == null ? new HashSet<String>() : unmodifiableSet(destroyers); } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeUTF(secContext.getOwner().getName()); out.writeObject(secContext.getFactory()); out.writeObject(new MarshalledObject<RemoteSingleRun>(run)); } @SuppressWarnings("unchecked") private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); if (log == null) log = getLog("Taverna.Server.LocalWorker"); final String creatorName = in.readUTF(); SecurityContextFactory factory = (SecurityContextFactory) in.readObject(); try { secContext = factory.create(this, new UsernamePrincipal(creatorName)); } catch (RuntimeException e) { throw e; } catch (IOException e) { throw e; } catch (Exception e) { throw new SecurityContextReconstructionException(e); } run = ((MarshalledObject<RemoteSingleRun>) in.readObject()).get(); } void setSecurityContext(TavernaSecurityContext tavernaSecurityContext) { secContext = tavernaSecurityContext; } } abstract class DEDelegate implements DirectoryEntry { private Log log = getLog("Taverna.Server.LocalWorker"); private RemoteDirectoryEntry entry; private String name; private String full; DEDelegate(RemoteDirectoryEntry entry) { this.entry = entry; } @Override public void destroy() throws FilesystemAccessException { try { entry.destroy(); } catch (IOException e) { throw new FilesystemAccessException("failed to delete directory entry", e); } } @Override public String getFullName() { if (full != null) return full; String n = getName(); RemoteDirectoryEntry re = entry; try { while (true) { RemoteDirectory parent = re.getContainingDirectory(); if (parent == null) break; n = parent.getName() + "/" + n; re = parent; } } catch (RemoteException e) { log.warn("failed to generate full name", e); } return (full = n); } @Override public String getName() { if (name == null) try { name = entry.getName(); } catch (RemoteException e) { log.error("failed to get name", e); } return name; } @Override public int compareTo(DirectoryEntry de) { return getFullName().compareTo(de.getFullName()); } @Override public boolean equals(Object o) { return o != null && o instanceof DEDelegate && getFullName().equals(((DEDelegate) o).getFullName()); } @Override public int hashCode() { return getFullName().hashCode(); } } class DirectoryDelegate extends DEDelegate implements Directory { private RemoteDirectory rd; DirectoryDelegate(RemoteDirectory dir) { super(dir); rd = dir; } @Override public Collection<DirectoryEntry> getContents() throws FilesystemAccessException { ArrayList<DirectoryEntry> result = new ArrayList<DirectoryEntry>(); try { for (RemoteDirectoryEntry rde : rd.getContents()) { if (rde instanceof RemoteDirectory) result.add(new DirectoryDelegate((RemoteDirectory) rde)); else result.add(new FileDelegate((RemoteFile) rde)); } } catch (IOException e) { throw new FilesystemAccessException("failed to get directory contents", e); } return result; } @Override public File makeEmptyFile(Principal actor, String name) throws FilesystemAccessException { try { return new FileDelegate(rd.makeEmptyFile(name)); } catch (IOException e) { throw new FilesystemAccessException("failed to make empty file", e); } } @Override public Directory makeSubdirectory(Principal actor, String name) throws FilesystemAccessException { try { return new DirectoryDelegate(rd.makeSubdirectory(name)); } catch (IOException e) { throw new FilesystemAccessException("failed to make subdirectory", e); } } @Override public byte[] getContentsAsZip() throws FilesystemAccessException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ZipOutputStream zos = new ZipOutputStream(baos); try { zipDirectory(this.rd, null, zos); zos.close(); zos = null; return baos.toByteArray(); } catch (Exception e) { throw new FilesystemAccessException("failed to zip up directory", e); } finally { try { if (zos != null) zos.close(); } catch (IOException e) { // Ignore this; it should be impossible. } } } /** * Compresses a directory tree into a ZIP. * * @param dir * The directory to compress. * @param base * The base name of the directory (or <tt>null</tt> if this is * the root directory of the ZIP). * @param zos * Where to write the compressed data. * @throws RemoteException * If some kind of problem happens with the remote delegates. * @throws IOException * If we run into problems with reading or writing data. */ private void zipDirectory(RemoteDirectory dir, String base, ZipOutputStream zos) throws RemoteException, IOException { for (RemoteDirectoryEntry rde : dir.getContents()) { String name = rde.getName(); if (base != null) name = base + "/" + name; if (rde instanceof RemoteDirectory) { RemoteDirectory rd = (RemoteDirectory) rde; zipDirectory(rd, name, zos); } else { RemoteFile rf = (RemoteFile) rde; zos.putNextEntry(new ZipEntry(name)); try { int off = 0; while (true) { byte[] c = rf.getContents(off, 64 * 1024); if (c == null || c.length == 0) break; zos.write(c); off += c.length; } } finally { zos.closeEntry(); } } } } } class FileDelegate extends DEDelegate implements File { RemoteFile rf; FileDelegate(RemoteFile f) { super(f); this.rf = f; } @Override public byte[] getContents(int offset, int length) throws FilesystemAccessException { try { return rf.getContents(offset, length); } catch (IOException e) { throw new FilesystemAccessException("failed to read file contents", e); } } @Override public long getSize() throws FilesystemAccessException { try { return rf.getSize(); } catch (IOException e) { throw new FilesystemAccessException("failed to get file length", e); } } @Override public void setContents(byte[] data) throws FilesystemAccessException { try { rf.setContents(data); } catch (IOException e) { throw new FilesystemAccessException("failed to write file contents", e); } } @Override public void appendContents(byte[] data) throws FilesystemAccessException { try { rf.appendContents(data); } catch (IOException e) { throw new FilesystemAccessException("failed to write file contents", e); } } @Override public void copy(File from) throws FilesystemAccessException { FileDelegate fromFile; try { fromFile = (FileDelegate) from; } catch (ClassCastException e) { throw new FilesystemAccessException("different types of File?!"); } try { rf.copy(fromFile.rf); } catch (Exception e) { throw new FilesystemAccessException("failed to copy file contents", e); } return; } } class ListenerDelegate implements Listener { private Log log = getLog("Taverna.Server.LocalWorker"); private RemoteListener r; String conf; ListenerDelegate(RemoteListener l) { r = l; } RemoteListener getRemote() { return r; } @Override public String getConfiguration() { try { if (conf == null) conf = r.getConfiguration(); } catch (RemoteException e) { log.warn("failed to get configuration", e); } return conf; } @Override public String getName() { try { return r.getName(); } catch (RemoteException e) { log.warn("failed to get name", e); return "UNKNOWN NAME"; } } @Override public String getProperty(String propName) throws NoListenerException { try { return r.getProperty(propName); } catch (RemoteException e) { throw new NoListenerException("no such property: " + propName, e); } } @Override public String getType() { try { return r.getType(); } catch (RemoteException e) { log.warn("failed to get type", e); return "UNKNOWN TYPE"; } } @Override public String[] listProperties() { try { return r.listProperties(); } catch (RemoteException e) { log.warn("failed to list properties", e); return new String[0]; } } @Override public void setProperty(String propName, String value) throws NoListenerException, BadPropertyValueException { try { r.setProperty(propName, value); } catch (RemoteException e) { log.warn("failed to set property", e); if (e.getCause() != null && e.getCause() instanceof RuntimeException) throw new NoListenerException("failed to set property", e.getCause()); if (e.getCause() != null && e.getCause() instanceof Exception) throw new BadPropertyValueException("failed to set property", e.getCause()); throw new BadPropertyValueException("failed to set property", e); } } } class RunInput implements Input { private final RemoteInput i; RunInput(RemoteInput remote) { this.i = remote; } @Override public String getFile() { try { return i.getFile(); } catch (RemoteException e) { return null; } } @Override public String getName() { try { return i.getName(); } catch (RemoteException e) { return null; } } @Override public String getValue() { try { return i.getValue(); } catch (RemoteException e) { return null; } } @Override public void setFile(String file) throws FilesystemAccessException, BadStateChangeException { checkBadFilename(file); try { i.setFile(file); } catch (RemoteException e) { throw new FilesystemAccessException("cannot set file for input", e); } } @Override public void setValue(String value) throws BadStateChangeException { try { i.setValue(value); } catch (RemoteException e) { throw new BadStateChangeException(e); } } } class SecurityContextReconstructionException extends RuntimeException { public SecurityContextReconstructionException(Throwable t) { super("failed to rebuild security context", t); } }