Flux Custom Functionality Integration: Unzip File Action
March 31, 2011 Leave a comment
With any software, you’re never going to find something that does everything. Flux comes close, but we still have yet to write the “Laundry” and “Cooking” actions.
The best way to handle this in any API or framework is to make user extension seamless and easy to access. Flux does this beautifully with Custom Actions. We use the simple JavaBean Framework to integrate custom user created actions into core Flux functionality, and provide the developer with easy to program hooks into our system so that programming virtually any functionality into a Flux action becomes a snap!
Some custom functionality that has been requested as of late is support for unzipping a zip archive file using Flux. Below I will show you how easy it is to pop in this functionality. If you are interested in using this custom action and would like a copy of the source code you can download the zip file here. If you are interested in simply using this custom action in Flux and are not worried about the source, you can download the jar file here. Just place the downloaded jar file onto your engine’s classpath and you’re all set!
public interface UnzipFileAction extends Action { public void setDestination(String path); public void setZipFile(String path); }
The interface in this case extends Action to include all the Flux Action functionality. If you were writing a Trigger then you'd need to extend the Trigger interface.Next comes our implementation. In your implementing class is where the bulk of the logic will go as to what your action will be doing. The
execute(FlowContext flowContext)method is the tie in for the action's logic. This is where all the good stuff happens:public Object execute(FlowContext flowContext) throws Exception { UnzipFileVariable var = getVariable(); String base = var.getDestination(); if (base == null || base.equalsIgnoreCase(".")) { base = ""; } if (!base.equalsIgnoreCase("") && !base.endsWith("/")) { base += "/"; } ZipFile zipFile = new ZipFile(new File(var.getZipFile()), ZipFile.OPEN_READ); final Enumeration<!--? extends ZipEntry--> entries = zipFile.entries(); byte buffer[] = new byte[51200]; int bytesRead; while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); File fileEntry = new File(base + entry.getName()); if (entry.isDirectory()) { fileEntry.mkdirs(); } else { InputStream in = zipFile.getInputStream(entry); if (!fileEntry.createNewFile()) { fileEntry.delete(); fileEntry.createNewFile(); } FileOutputStream out = new FileOutputStream(fileEntry); int offset = 0; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, offset, bytesRead); } } } return null; }
The above code is all the logic to create any directories necessary, locate the zip file, and extract it to the destination. The only thing that might be considered tricky when implementing a custom action or trigger is how Flux saves the action's state. For all actions Flux uses a variable that is written to the database at runtime. Now that you know this however, writing the variable is extremely easy. The Unzip Action's variable is as follows:public class UnzipFileVariable implements Serializable { public String destination; public String zipFile; public UnzipFileVariable() { } public String getDestination() { return destination; } public void setDestination(String destination) { this.destination = destination; } public String getZipFile() { return zipFile; } public void setZipFile(String zipFile) { this.zipFile = zipFile; } }
Notice the variable implements no other interface except the Java Serializable interface. This is import as Flux needs to be able to serialize your variable into the database. Variables unable to be serialized to the database will cause problems at runtime. The next step is using your variable within your action/trigger implementation. It must be used within your property getters and setters so that Flux is able to generically access and save your variable to the database. The getters and setters for the Unzip Action are as follows:public static String VARIABLE_NAME = "UNZIP_FILE"; public String getDestination() { return getVariable().getDestination(); } public void setDestination(String path) { UnzipFileVariable var = getVariable(); var.setDestination(path); putVariable(var); } public String getZipFile() { return getVariable().getZipFile(); } public void setZipFile(String path) { UnzipFileVariable var = getVariable(); var.setZipFile(path); putVariable(var); } private UnzipFileVariable getVariable() { if (!getVariableManager().contains(VARIABLE_NAME)) { getVariableManager().put(VARIABLE_NAME, new UnzipFileVariable()); } // if return (UnzipFileVariable) getVariableManager().get(VARIABLE_NAME); } // getVariable() private void putVariable(UnzipFileVariable variable) { getVariableManager().put(VARIABLE_NAME, variable); } // putVariable()
The
getVariable()andputVariable(UnzipFileVariable variable)methods are put in for convenience with getting and setting the variable back to the variable manager each time. TheVARIABLE_NAMEvariable can be set to whatever you like.Next the
verify()method needs to be implemented. This is a pretty straight forward method as it is used by Flux to ensure all properties needed for the proper execution of your action/trigger are set. The Unzip Action simply ensures the zip file path has been set. The destination is not required as the action is designed to use the working directory of Flux as a default destination.public void verify() throws EngineException { UnzipFileVariable var = getVariable(); if (var.getZipFile() == null || var.getZipFile().equalsIgnoreCase("")) { throw new EngineException("Expected zip file path to be non-null or not empty, but it was"); } }
The next step is creating your
BeanInfo. YourBeanInfowill instruct Flux on how to handle and use your custom action/trigger just like any other core action/trigger Flux ships with.public class UnzipFileActionImplBeanInfo extends ActionImplBeanInfo { /** * A small image for this custom action to be displayed in the Flux GUI. */ private Image smallImage; /** * A large image for this custom action to be displayed in the Flux GUI. */ private Image bigImage; /** * This BeanInfo needs a BeanDescriptor. */ protected BeanDescriptor bd = new BeanDescriptor(UnzipFileActionImpl.class); /** * This BeanInfo needs a BeanDescriptor. */ public UnzipFileActionImplBeanInfo(BeanDescriptor bd) { this.bd = bd; } // constructor /** * This BeanInfo needs a BeanDescriptor. */ public UnzipFileActionImplBeanInfo() { // setup bean descriptor in constructor bd.setDisplayName("Unzip File Action"); bd.setShortDescription("Unzips a specified zip archive file."); } // constructor /** * Returns this BeanInfo's BeanDescriptor. * * @return This BeanInfo's BeanDescriptor. */ public BeanDescriptor getBeanDescriptor() { return bd; } // getBeanDescriptor() /** * You can change the icon associated with your custom action/trigger here. */ public Image getIcon(int type) { if (type == BeanInfo.ICON_COLOR_16x16) { return getSmallImage(); } // if if (type == BeanInfo.ICON_COLOR_32x32) { return getBigImage(); } // if return null; } // getIcon() /** * Returns all JavaBean descriptors described by this BeanInfo. * * @return All JavaBean descriptors described by this BeanInfo. */ public PropertyDescriptor[] getPropertyDescriptors() { Vector descriptors = new Vector(Arrays.asList(super.getPropertyDescriptors())); PropertyDescriptor nameDescriptor = null, contentDescriptor = null; try { nameDescriptor = new PropertyDescriptor("zipFile", UnzipFileActionImpl.class); nameDescriptor.setDisplayName("Zip File"); nameDescriptor.setShortDescription("Zip file path"); descriptors.add(nameDescriptor); contentDescriptor = new PropertyDescriptor("destination", UnzipFileActionImpl.class); contentDescriptor.setDisplayName("Destination"); contentDescriptor.setShortDescription("Destination directory path"); descriptors.add(contentDescriptor); } // try catch (IntrospectionException e) { e.printStackTrace(); throw new IllegalStateException("could not create property descriptor"); } // catch return (PropertyDescriptor[]) descriptors.toArray(new PropertyDescriptor[descriptors.size()]); } // getPropertyDescriptors() /** * Returns a small image that represents this JavaBean. * * @return A small image that represents this JavaBean. */ private Image getSmallImage() { if (smallImage == null) { smallImage = loadImage("/fluximpl/resources/images/action_icons/default_small.png"); } // if return smallImage; } // getSmallImage() /** * Returns a large image that represents this JavaBean. * * @return A large image that represents this JavaBean. */ private Image getBigImage() { if (bigImage == null) { bigImage = loadImage("/fluximpl/resources/images/action_icons/default_big.png"); } // if return bigImage; } // getBigImage() }
The Unzip Action's
BeanInfoextendsActionImplBeanInfowhich is the baseBeanInfofor Flux actions. This provides you with the other necessary property descriptors your custom action uses. Notice thegetPropertyDescriptors()method. This is where you define what your custom properties are for your action/trigger.Finally the last bit of implementation is the factory with which you create the action from.
public class UnzipFileFactory implements AdapterFactory { private FlowChartImpl flowChart; public void init(FlowChartImpl flowChart) { this.flowChart = flowChart; } public UnzipFileAction makeFileAction(String name) { return new UnzipFileActionImpl(flowChart, name); } }
This class implements
AdapterFactorywhich is what you need in order for Flux to recognize your factory as a Flux factory object. The last thing you'll need to do is tie in your factory with thefactories.propertiesfile. The file has a single entry for this action,UnzipFileFactory=unzip.UnzipFileFactory. As long as this properties file is on your engine's classpath, Flux will pick up and recognize your new factory!Now to actually use the Unzip Action. The following code creates a flow chart with the custom Unzip File Action and a Console Action. Notice how the
UnzipFileFactoryis created and used.public class Main { /** * Creates a simple Flux engine. */ public Main() throws Exception { // First, make a Flux engine. Factory factory = Factory.makeInstance(); Engine engine = factory.makeEngine(); // Flux models jobs using flow charts. Create a flow chart. EngineHelper helper = factory.makeEngineHelper(); FlowChart flowChart = helper.makeFlowChart("Custom Action"); // Our flow chart will consist of a custom action. Here is how we // load custom actions and triggers. // Create a file action to append data to a file. UnzipFileFactory unzipFileFactory = (UnzipFileFactory) flowChart.makeFactory("UnzipFileFactory"); UnzipFileAction unzipFileAction = unzipFileFactory.makeFileAction("Unzip File Action"); unzipFileAction.setZipFile("flux-7-10-3.zip"); unzipFileAction.setDestination("extraction"); // Create a console action to print strings to the console. ConsoleAction consoleAction = flowChart.makeConsoleAction("Console Action"); consoleAction.setMessage("Finished unzipping contents of zip file."); // Flow from the file action to the console action. unzipFileAction.addFlow(consoleAction); // Schedule the job for immediate execution. engine.put(flowChart); // Start the engine engine.start(); // Wait for the flow chart to complete engine.join("/", "+5m", "+2s"); // Finally, shutdown the engine. engine.dispose(); } // constructor public static void main(String[] args) throws Exception { new Main(); } // main() }
That's all there is to it! I hope you can use this custom action in your Flux activities, or at least have it help you in creating new custom actions of your own!
