package org.n52.movingcode.runtime.codepackage; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.apache.log4j.Logger; import org.apache.xmlbeans.XmlError; import org.apache.xmlbeans.XmlOptions; import org.joda.time.DateTime; import de.tudresden.gis.geoprocessing.movingcode.schema.PackageDescriptionDocument; import static org.n52.movingcode.runtime.codepackage.Constants.*; /** * This class provides methods for handling MovingCode Packages. This includes methods for validation, * copying, unzipping and for accessing the package's description. * * MovingCode Packages a the basic entities for shipping code from platform to platform. * * @author Matthias Mueller, Christin Henzen, TU Dresden * */ public class MovingCodePackage { private static final Logger logger = Logger.getLogger(MovingCodePackage.class); // the physical instance of this package private final ICodePackage archive; // package description XML document private PackageDescriptionDocument packageDescription = null; // identifier of the provided functionality (e.g. WPS process identifier) private final String functionIdentifier; // Package id and time stamp, i.e. date of creation or last modification private final PID packageID; private final List<FunctionalType> supportedFuncTypes; /** * Constructor for zipFiles. Creates a MovingCodePackage from a zipFile on disk. * * @param {@link File} zipFile - a zip file with a valid package structure */ public MovingCodePackage(final File zipFile) { this.archive = new ZippedPackage(zipFile); this.packageDescription = this.archive.getDescription(); if (this.packageDescription != null) { this.functionIdentifier = this.packageDescription.getPackageDescription().getFunctionality().getWps100ProcessDescription().getIdentifier().getStringValue(); this.supportedFuncTypes = getFunctionalTypes(this.packageDescription); DateTime timestamp = new DateTime(this.packageDescription.getPackageDescription().getTimestamp()); String id = this.packageDescription.getPackageDescription().getPackageId(); this.packageID = new PID(id, timestamp); } else { this.functionIdentifier = null; this.supportedFuncTypes = null; this.packageID = null; } } /** * Constructor for geoprocessing feed entries. Creates a MovingCodePackage from an atom feed entry. * * @param {@link GeoprocessingFeedEntry} atomFeedEntry - an entry from a geoprocessing feed * */ /** * Constructor for geoprocessing feed entries. Creates a MovingCodePackage from a remote URL. * Also requires the intended packageID an * * @param zipPackageURL * @param packageID * @param packageStamp */ public MovingCodePackage(final URL zipPackageURL) { PackageDescriptionDocument packageDescription = null; ZippedPackage archive = null; archive = new ZippedPackage(zipPackageURL); packageDescription = archive.getDescription(); this.packageDescription = packageDescription; // TODO: Information from the feed might lag during updates // how can deal with that? if (packageDescription != null) { this.functionIdentifier = packageDescription.getPackageDescription().getFunctionality().getWps100ProcessDescription().getIdentifier().getStringValue(); this.supportedFuncTypes = getFunctionalTypes(packageDescription); DateTime timestamp = new DateTime(this.packageDescription.getPackageDescription().getTimestamp()); String id = this.packageDescription.getPackageDescription().getPackageId(); this.packageID = new PID(id, timestamp); } else { this.functionIdentifier = null; this.supportedFuncTypes = null; this.packageID = null; } this.archive = archive; } /** * Constructor for directories. Creates a MovingCodePackage from a workspace directory on disk. * * @param {@link File} workspace - the directory where the code and possibly some related data is stored. * @param {@link PackageDescriptionDocument} packageDescription - the XML document that contains the * description of the provided logic * @param {@link DateTime} lastModified - the date of latest modification. This value is optional. If NULL, * the lastModified date is obtained from the workspace's content. */ public MovingCodePackage(final File workspace, final PackageDescriptionDocument packageDescription) { this.packageDescription = packageDescription; if (packageDescription != null) { this.functionIdentifier = packageDescription.getPackageDescription().getFunctionality().getWps100ProcessDescription().getIdentifier().getStringValue(); this.supportedFuncTypes = getFunctionalTypes(packageDescription); DateTime timestamp = new DateTime(this.packageDescription.getPackageDescription().getTimestamp()); String id = this.packageDescription.getPackageDescription().getPackageId(); this.packageID = new PID(id, timestamp); } else { this.functionIdentifier = null; this.supportedFuncTypes = null; this.packageID = null; } this.archive = new PlainPackage(workspace, packageDescription); } /** * Dump workspace to a given directory. Used to create copies from a template for execution or further * manipulation. * * @param {@link File} targetDirectory - directory to store the unzipped content * @return {@link String} dumpWorkspacePath - absolute path of the dumped workspace */ public String dumpWorkspace(File targetDirectory) { String wsRoot = this.packageDescription.getPackageDescription().getWorkspace().getWorkspaceRoot(); this.archive.dumpPackage(wsRoot, targetDirectory); if (wsRoot.startsWith("./")) { wsRoot = wsRoot.substring(2); } if (wsRoot.startsWith(".\\")) { wsRoot = wsRoot.substring(2); } return targetDirectory + File.separator + wsRoot; } /** * Writes a copy of the {@link MovingCodePackage} to a given directory. This is going to be a zipFile * * @param targetFile * - destination path and file * @return boolean - true if successful, false if not */ public boolean dumpPackage(File targetFile) { return this.archive.dumpPackage(targetFile); } /** * writes a copy of the package (zipfile) to a given directory TODO: implement for URL sources * * @param targetFile * @return boolean */ public boolean dumpDescription(File targetFile) { try { this.packageDescription.save(targetFile); return true; } catch (IOException e) { return false; } } public PID getVersionedPackageId(){ return packageID; } /** * Returns the PackageDescription * * @return {@link PackageDescriptionDocument} */ public PackageDescriptionDocument getDescription() { return this.packageDescription; } /** * Returns the PackageDescription as String * * @return XML string of {@link PackageDescriptionDocument} */ public String getDescriptionString() { StringBuilder builder = new StringBuilder(); builder.append(this.packageDescription); return builder.toString(); } /** * Does this object contain valid content? * * @return boolean - true if content is valid, false if not */ public boolean isValid() { //System.out.println("MovingCodePackage isValid"); if(this.packageDescription == null || this.packageDescription.isNil()){ return false; } // a valid Code Package must have a package ID if(packageID.id == null || packageID.equals("")){ return false; } // ... and timestamp if(packageID.timestamp == null){ return false; } // a valid Code Package MUST have a function (aka process) identifier if (this.functionIdentifier == null) { return false; } // TODO: Identifiers of IO data must be unique! // TODO: verify path to executable. String exLoc = packageDescription.getPackageDescription().getWorkspace().getExecutableLocation(); if (!this.archive.containsFileInWorkspace(exLoc)){ return false; } // check if there exists a package description // and return the validation result //information on validation errors if (!this.packageDescription.validate()) { List<XmlError> errors = new ArrayList<XmlError>(); this.packageDescription.validate(new XmlOptions().setErrorListener(errors)); logger.warn("Package is not valid: "+errors); return false; } else { return true; } } /** * Returns the unique *functional* identifier of this package. The identifier refers to the functional * contract and not to a concrete implementation in this particular package. * * @return String */ public final String getFunctionIdentifier() { return this.functionIdentifier; } /** * Returns the timestamp of this package. The timestamp indicates the last update of the package content * and can be used as a simple versioning machanism. * * TODO: timestamp now covered by package ID - is this method still required? * * * @return {@link DateTime} package timestamp */ public DateTime getTimestamp() { return this.packageID.timestamp; } /** * Static internal method to evaluate a {@link PackageDescriptionDocument} and return the type (i.e. the * schema) of the functional description. * * TODO: slight overhead (?) - currently only WPS 1.0 is supported in the schema. * * @param description * {@link PackageDescriptionDocument} * @return {@link List} of {@link FunctionalType} - the type of the functional description (i.e. WPS 1.0, * WSDL, ...). */ private static final List<FunctionalType> getFunctionalTypes(final PackageDescriptionDocument description) { ArrayList<FunctionalType> availableFunctionalDescriptions = new ArrayList<FunctionalType>(); // retrieve functional description types // TODO: make fit for WPS 2.0? availableFunctionalDescriptions.add(FunctionalType.WPS100); return availableFunctionalDescriptions; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("MovingCodePackage [archive="); builder.append(this.archive); builder.append(", packageDescription="); builder.append(this.packageDescription); builder.append(", PackageID="); builder.append(this.packageID.id); builder.append(", timeStamp="); builder.append(this.packageID.timestamp.toString()); builder.append(", supportedFuncTypes="); builder.append(this.supportedFuncTypes); builder.append("]"); return builder.toString(); } }