package hk.hku.cecid.edi.sfrm.com; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.nio.channels.ReadableByteChannel; import java.util.StringTokenizer; import java.util.List; import java.util.Vector; import hk.hku.cecid.edi.sfrm.util.PathHelper; import hk.hku.cecid.piazza.commons.io.NIOHandler; /** * A Named payloads is a kind of payload (file attachment) that * use it's filename to provide some informations for the system.<br><br> * * It is a proxy design pattern that control the behavior of the * actual files. * * In SFRM plugin, the default style of named payload is shown on the below:<br> * <ul> * <li> Outgoing Payload Repository - <service>$<message_id>$timestamp> </li> * <li> Packaged Payload Repository - <service>$<message_id> </li> * <li> Incoming Payload Repository - <service>$<message_id> </li> * </ul> * * Creation Date: 6/10/2006<br>. * * @author Twinsen Tsang * @version 1.0.2 * @since 1.0.0 * * @version 2.0.0 * @since 1.0.2 */ public abstract class NamedPayloads { /** * The delimitier used for decoding. */ protected static final String decodeDelimiters = "$"; /** * The prefix of uploading payload. */ protected static final String uploadingPrefix = "~"; /** * The prefix of processing payload. */ protected static final String processingPrefix = "##"; /** * The prefix of uploading payload. */ protected static final String processedPrefix = "%%"; /** * The start bracket to enclose the filename of the single file, not packed payload */ protected static final String filenameStartBracket = "["; /** * The end bracket to enclose the filename of the single file, not packed payload */ protected static final String filenameEndBracket = "]"; /** * The state to prefix hash map */ private static String[] stateToPrefix = new String[4]; /** * The root of payloads. */ private File root; /** * The orginal root name of the payloads */ private String originalRootName; /** * The extension of the payload. */ private String extension; /** * The content type of the payload. */ private String contentType; /** * The name token of this payload. */ private List tokens = null; /** * The owner of this payload. */ private PayloadsRepository owner; static{ stateToPrefix[PayloadsState.PLS_UPLOADING] = uploadingPrefix; stateToPrefix[PayloadsState.PLS_PROCESSING] = processingPrefix; stateToPrefix[PayloadsState.PLS_PROCESSED] = processedPrefix; stateToPrefix[PayloadsState.PLS_PENDING] = ""; } /** * Explicit Constructor.<br><br> * * This constructor is mainly used for creating * a new payload proxy including the physical * file and the proxy object.<br><br> * * <strong>NOTE:</strong> * The physical file is not created until it is * necessary. * * @param payloadsName * The name of the newly created payload. * @param initialState * The initialState of the payloads, * see {@link PayloadsState} for details. * @param owner * The owner of the payloads. * @since * 1.0.2 * * @throws NullPointerException * if the <code>owner</code> is null * @throws IllegalArgumentException * if the <code>payloadsName</code> is null or * the <code>initialState</code> is not invalid. * * @see hk.hku.cecid.edi.sfrm.com.PayloadsRepostory * @see hk.hku.cecid.edi.sfrm.com.PayloadsState */ public NamedPayloads(String payloadsName, int initialState, PayloadsRepository owner) throws IOException { if (owner == null) throw new NullPointerException("Missing repository owner."); if (payloadsName == null || payloadsName.equalsIgnoreCase("")) throw new IllegalArgumentException("Invalid Payload Name."); // Setup general Stuff. this.extension = PathHelper.getExtension(payloadsName); this.owner = owner; this.originalRootName = payloadsName; payloadsName = getStateForm(initialState) + payloadsName; // Create the acutal root. this.root = new File(owner.getRepositoryPath(), payloadsName); } /** * Explicit Constructor.<br><br> * * The constructor is mainly used for creating * the new payload proxy object only. * * @param payloads * The file payload object. * @param owner * The owner of the payloads. * @see hk.hku.cecid.edi.sfrm.PayloadsRepostory */ public NamedPayloads(File payloads, PayloadsRepository owner){ this.root = payloads; this.extension = PathHelper.getExtension(payloads.getName()); this.owner = owner; this.originalRootName = this.parseOriginalFilename(); } /** * Rename the root to the specified name. (The file path * has not been changed after each invocation. * * @param newName * The name of the root. * @return true if the operation run successfully. * @throws IOException * if any kinds of I/O Exception. */ public boolean renameRoot(String newName) throws IOException { if (this.root.getName().equalsIgnoreCase(newName)) return true; File ret = PathHelper.renameTo(this.root, newName); if (ret != null){ this.root = ret; return true; } return false; } /** * Move the root to the specified path. * * @param newPath * The absolute new path * @param force * force to move the file to specified path if * there is a file with same name already exist * in that path. * @return * true if the moving operation successfully. * @since * 1.0.0 */ public boolean moveRoot(String newPath, boolean force){ if (this.root.getAbsolutePath().equalsIgnoreCase(newPath)) return true; File target = new File(newPath); if (force) if (target.exists()) if (!target.delete()) return false; if (!this.root.renameTo(target)){ target = null; // for gc return false; } this.root = target; return true; } /** * Move the root to another payloads repository.<br><br> * * The owner of this payloads will changes to * <code>newOwner</code> after invocation of * this method. * * @param newOwner * The new owner of the payload repository. * @return * true if the moving operation successfully, * false if the <code>newOwner</code> is null * or moving operation fail. * @since * 1.0.2 */ public boolean moveToRepository(PayloadsRepository newOwner){ if (newOwner == null) return false; if (this.moveRoot(newOwner.getRepositoryPath() +File.separator +this.originalRootName, false)){ this.owner = newOwner; return true; } return false; } /** * Move the root to another payloads repository * force. * * The owner of this payloads will changes to * <code>newOwner</code> after invocation of * this method. * * @param newOwner * The new owner of the payload repository. * @return * true if the moving operation successfully, * false if the <code>newOwner</code> is null * or moving operation fail. * @since * 1.0.3 */ public boolean moveToRepositoryForce(PayloadsRepository newOwner){ if (newOwner == null) return false; if (this.moveRoot(newOwner.getRepositoryPath() +File.separator +this.originalRootName, true)){ this.owner = newOwner; return true; } return false; } /** * Set the status of payload to pending. * * @return true if the operation run sucessfully. */ public boolean setToPending() throws IOException{ return this.renameRoot(this.originalRootName); } /** * Set the status of payload to uploading. * * @return true if the operation run sucessfully. */ public boolean setToUploading() throws IOException{ return this.renameRoot(NamedPayloads.uploadingPrefix + this.originalRootName); } /** * Set the status of payload to processing. * * @return true if the operation run sucessfully. */ public boolean setToProcessing() throws IOException{ return this.renameRoot(NamedPayloads.processingPrefix + this.originalRootName); } /** * Set the status of payload to processed. * * @return true if the operation run sucessfully. */ public boolean setToProcessed() throws IOException{ return this.renameRoot(NamedPayloads.processedPrefix + this.originalRootName); } /** * @return true if the payload's name is startting with processing prefix. */ public boolean isUploading(){ return this.root.getName().startsWith(NamedPayloads.uploadingPrefix); } /** * @return true if the payload's name is startting with processing prefix. */ public boolean isProcessing(){ return this.root.getName().startsWith(NamedPayloads.processingPrefix); } /** * @return true if the payload's name is starting with processed prefix. */ public boolean isProcessed(){ return this.root.getName().startsWith(NamedPayloads.processedPrefix); } /** * @return the directory of this payload set. */ public File getRoot() { return this.root; } /** * @return the original file name of the payload root. */ public String getOriginalRootname(){ return this.originalRootName; } /** * @return a list of token under the decode delimiters. */ protected List getTokens(){ if (this.tokens == null){ this.tokens = new Vector(); // Bug Fix: Directory String name = this.root.isDirectory() ? this.originalRootName : PathHelper.removeExtension(this.originalRootName); StringTokenizer st = new StringTokenizer(name, NamedPayloads.decodeDelimiters); while(st.hasMoreElements()) this.tokens.add(st.nextElement()); } return this.tokens; } /** * @return the extension */ public String getExtension() { return extension; } /** * @return the contentType */ public String getContentType() { // TODO: Use back the original content type. return "application/octet-stream"; } /** * @return get the owner of this payload. */ public PayloadsRepository getOwner() { return owner; } /** * @return get the size of the payload, return 0 if the * payload does not exist. */ public long getSize(){ if (this.root != null) try{ FileInputStream fis = new FileInputStream(this.root); long size = fis.getChannel().size(); fis.close(); return size; }catch(IOException ioe){ return 0; } return 0; } /** * Get the state form string according to the * specified state.<br><br> * * @param state * The state you want to retrieve. * @return * @since * 1.0.2 * @throws IllegalArgumentException * if the state is invalid. For all state * see PayloadsState. */ public static String getStateForm(int state){ if (state > stateToPrefix.length) throw new IllegalArgumentException("Invalid State."); return stateToPrefix[state]; } /** * Parse the payload filename and get back the original filename from inside. * * @return the original file name. */ private String parseOriginalFilename(){ if (this.isUploading()) return this.root.getName().substring(NamedPayloads.uploadingPrefix.length()); else if (this.isProcessing()) return this.root.getName().substring(NamedPayloads.processingPrefix.length()); else if (this.isProcessed()) return this.root.getName().substring(NamedPayloads.processedPrefix.length()); return this.root.getName(); } /** * Clear the tokens to free some memory. */ public void clearTokens(){ this.tokens.clear(); this.tokens = null; } /** * Clear / Delete the payload cache to free some space. */ public void clearPayloadCache(){ if (this.root != null){ if (!this.root.delete()) this.root.deleteOnExit(); this.root = null; // for gc } } /** * Load the payload content from the input stream.<br><br> * * NOTE: This method returns a new instance of input stream. * * @return the content input stream. * @throws IOException * Throws if the payload file does not exist. */ public InputStream load() throws IOException { return new BufferedInputStream(new FileInputStream(this.root)); } /** * Load the payload content from the input stream channel.<br><br> * * @return the content input channel. * @throws IOException * Throws if the payload file does not exist. */ public ReadableByteChannel loadChannel() throws IOException { return new FileInputStream(this.root).getChannel(); } /** * Save the content from the input stream to this payloads.<br><br> * * If the content stream is null, it save the file with empty content.<br> * * @param content * The input content stream. * @param append * true if the new content is added to the existing content, * false if the new content overwrite the existing. */ public void save(InputStream content, boolean append) throws IOException { // Create buffered ouput stream. OutputStream outs = new BufferedOutputStream( new FileOutputStream(this.root, append)); if (content != null){ if (content instanceof FileInputStream) // Performance gain a lot if using FileInputSteam. NIOHandler.pipe((FileInputStream)content, outs); else NIOHandler.pipe(content, outs); } outs.close(); outs = null; // For gc } /** * Decode the payload root to become some useful information. * * @throws ArrayIndexOutOfBoundsException * if the decoding fails due to the filename is in wrong format. */ protected abstract void decode() throws ArrayIndexOutOfBoundsException; /** * Encode the payload root back to a filename. */ protected abstract void encode(); /** * toString method */ public String toString() { StringBuffer ret = new StringBuffer(); ret .append("\n") .append(this.getClass().getName() + "\n") .append("Payload Root: " + this.originalRootName + " \n") .append("Extension : " + this.extension + " \n") .append("Content-type: " + this.contentType + " \n") .append("Owner : " + this.owner.getId() + " \n"); return ret.toString(); } }