/******************************************************************************* * Copyright (c) 2015 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ package com.ibm.ws.massive.upload.internal; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Properties; import java.util.Set; import java.util.Stack; import java.util.StringTokenizer; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import com.ibm.ws.massive.esa.internal.EsaManifest; import com.ibm.ws.massive.upload.RepositoryArchiveEntryNotFoundException; import com.ibm.ws.massive.upload.RepositoryArchiveException; import com.ibm.ws.massive.upload.RepositoryArchiveIOException; import com.ibm.ws.massive.upload.RepositoryArchiveInvalidEntryException; import com.ibm.ws.repository.common.enums.AttachmentLinkType; import com.ibm.ws.repository.common.enums.AttachmentType; import com.ibm.ws.repository.common.enums.DownloadPolicy; import com.ibm.ws.repository.common.enums.LicenseType; import com.ibm.ws.repository.common.utils.internal.RepositoryCommonUtils; import com.ibm.ws.repository.connections.RepositoryConnection; import com.ibm.ws.repository.exceptions.RepositoryException; import com.ibm.ws.repository.resources.internal.RepositoryResourceImpl.AttachmentResourceImpl; import com.ibm.ws.repository.resources.writeable.AttachmentResourceWritable; import com.ibm.ws.repository.resources.writeable.RepositoryResourceWritable; /** * Base class for upload utilities. */ public abstract class MassiveUploader { /** * Key for a property stating what type of link this is (only relevant if the downloadUrl is * also set), can be one DIRECT, WEB_PAGE or EFD. Defaults to DIRECT. */ public final static String LINK_TYPE_PROPERTY_KEY = "linkType"; /* * This header is used with in products (JARs) to record the location of License Agreement files * within the archive. */ public final static String LA_HEADER_PRODUCT = "License-Agreement"; /* * This header is used in products (JARs) to record the location of License Information files * within the archive. */ public final static String LI_HEADER_PRODUCT = "License-Information"; /* Header used in features (ESAs) to record License Agreement location */ public final static String LA_HEADER_FEATURE = "IBM-License-Agreement"; /* Header used in features (ESAs) to record License Information location */ public final static String LI_HEADER_FEATURE = "IBM-License-Information"; /* Properties in props file */ public final static String PROP_REQUIRE_FEATURE = "requires.feature"; public final static String PROP_APPLIES_TO_MIN_VERSION = "applies.to.minimum.version"; public final static String PROP_APPLIES_TO_EDITIONS = "applies.to.editions"; public final static String PROP_PROVIDER_NAME = "provider"; public final static String PROP_NAME = "name"; public final static String PROP_DESCRIPTION = "longDescription"; public final static String PROP_SHORT_DESCRIPTION = "shortDescription"; public final static String PROP_URL = "url"; public final static String PROP_ICONS = "icons"; public final static String PROP_DOWNLOAD_URL = "downloadURL"; public final static String PROP_STAGED_URL = "stagedURL"; public final static String PROP_LICENSE_TYPE = "licenseType"; protected final RepositoryConnection repoConnection; public MassiveUploader(RepositoryConnection repoConnection) { this.repoConnection = repoConnection; } /** * Enum declaring the productEdition component of appliesTo */ public enum Edition { CORE("CORE"), BASE("BASE"), DEVELOPERS("DEVELOPERS"), EXPRESS("EXPRESS"), ND("ND"), zOS("zOS"); private String _productEdition; private Edition(String productEdition) { _productEdition = productEdition; } public String getProductEdition() { return _productEdition; } } /** * Reads from the input stream and copies to the output stream * * @param is * @param os * @throws IOException */ protected void copyStreams(InputStream is, OutputStream os) throws IOException { byte[] buffer = new byte[1024]; try { int read; while ((read = is.read(buffer)) != -1) { os.write(buffer, 0, read); buffer = new byte[1024]; } } finally { try { if (null != os) os.close(); } finally { if (null != is) is.close(); } } } public static class ExtractedFileInformation { private final File extractedFile; private final File sourceArchive; private final String selectedPathFromArchive; public ExtractedFileInformation(File extractedFile, File sourceArchive, String selectedPathFromArchive) { super(); this.extractedFile = extractedFile; this.sourceArchive = sourceArchive; this.selectedPathFromArchive = selectedPathFromArchive; } public File getExtractedFile() { return extractedFile; } public File getSourceArchive() { return sourceArchive; } public String getSelectedPathFromArchive() { return selectedPathFromArchive; } } /** * Extract a file from a jar file to a temporary location on disk that is deleted when the jvm * exits. * * @param fileName The name of the archive file to extract the file from * @param regex A regular expression, that is used to match the file to be extracted from the * archive. If more than one file matches the regular expression only the first file * is extracted * @return A file object representing the temporary file where the file in the jar was extracted * to. * @throws MassiveArchiveException if the archive could not be read or the file could not be * extracted to disk * @throws RepositoryArchiveException * @throws RepositoryArchiveIOException * @throws RepositoryArchiveEntryNotFoundException If no file matching the supplied regular * expression could be found */ protected ExtractedFileInformation extractFileFromArchive(String fileName, String regex) throws RepositoryArchiveException, RepositoryArchiveEntryNotFoundException, RepositoryArchiveIOException { JarFile jarFile = null; ExtractedFileInformation result = null; File outputFile = null; File sourceArchive = new File(fileName); try { try { jarFile = new JarFile(sourceArchive); } catch (FileNotFoundException fne) { throw new RepositoryArchiveException( "Unable to locate archive " + fileName, sourceArchive, fne); } catch (IOException ioe) { throw new RepositoryArchiveIOException( "Error opening archive ", sourceArchive, ioe); } Enumeration<JarEntry> enumEntries = jarFile.entries(); // Iterate through the files in the jar file searching for one we // are interested in while (enumEntries.hasMoreElements()) { JarEntry entry = enumEntries.nextElement(); String name = entry.getName(); if (Pattern.matches(regex, name)) { // Don't want to use the entire path to the file so create a // file // and then recreate it using just the "name" part of the // filename outputFile = new File(name); outputFile = new File(outputFile.getName()); outputFile.deleteOnExit(); try { copyStreams(jarFile.getInputStream(entry), new FileOutputStream(outputFile)); } catch (IOException ioe) { throw new RepositoryArchiveIOException( "Failed to extract file " + name + " inside " + fileName + " to " + outputFile.getAbsolutePath(), sourceArchive, ioe); } result = new ExtractedFileInformation(outputFile, sourceArchive, name); break; } } } finally { try { if (null != jarFile) { jarFile.close(); } } catch (IOException e) { e.printStackTrace(); throw new RepositoryArchiveIOException( "Error closing archive ", sourceArchive, e); } } // Make sure we have found a file if (null == outputFile) { throw new RepositoryArchiveEntryNotFoundException( "Failed to find file matching regular expression <" + regex + "> inside archive " + fileName, sourceArchive, regex); } return result; } public File unpackToTempDir(File zipFile) throws IOException { File tempDir = File.createTempFile("packedEsa", null); if (!tempDir.delete()) { throw new IOException("Couldn't delete temp file " + tempDir.getAbsolutePath()); } if (!tempDir.mkdir()) { throw new IOException("Couldn't create temp dir " + tempDir.getAbsolutePath()); } tempDir.deleteOnExit(); FileInputStream fis = new FileInputStream(zipFile); ZipInputStream zis = new ZipInputStream(fis); try { ZipEntry ze = zis.getNextEntry(); byte[] buf = new byte[2048]; while (ze != null) { if (ze.isDirectory()) { // Do nothing with pure directory entries } else { String fileName = ze.getName(); File unzippedFile = new File(tempDir.getCanonicalPath() + File.separator + fileName); File parent = new File(unzippedFile.getParent()); if (!parent.mkdirs() && !parent.exists()) { throw new IOException("Couldn't create dir " + parent.getAbsolutePath()); } FileOutputStream fos = new FileOutputStream(unzippedFile); try { int numBytes; while ((numBytes = zis.read(buf)) > 0) { fos.write(buf, 0, numBytes); } } finally { fos.close(); } } ze = zis.getNextEntry(); } } finally { zis.closeEntry(); zis.close(); } // Now mark everything below tempDir for deletion. for (File f : listAllFilesAndDirectories(tempDir)) { f.deleteOnExit(); } return tempDir; } public Collection<File> listAllFilesAndDirectories(File rootDir) { return doListAllFiles(rootDir, true); } public Collection<File> listAllFiles(File rootDir) { return doListAllFiles(rootDir, false); } // File.deleteOnExit(): // "Files (or directories) are deleted in the reverse order that they are registered. " private Collection<File> doListAllFiles(File rootDir, boolean includeDirectories) { Collection<File> result = new Stack<File>(); File[] files = rootDir.listFiles(); if (files == null) { return result; } for (File f : files) { if (f.isDirectory()) { result.addAll(doListAllFiles(f, includeDirectories)); if (includeDirectories) { result.add(f); } } else if (f.isFile()) { result.add(f); } } return result; } /** * Sometimes we want to publish an esa, product add on or similar with a license. We do this by * providing an accompany zip with its license.html files and a .properties file. This utility * method finds and explodes a .zip onto disk and pulls useful bits out. * * @param archiveFile - the .jar or .esa file to look for a sibling zip for * @return The artifact metadata from the sibling zip or <code>null</code> if none was found * @throws IOException * @throws RepositoryArchiveException */ public ArtifactMetadata explodeArtifact(File archiveFile) throws RepositoryArchiveException { String path; try { path = archiveFile.getCanonicalPath(); } catch (IOException e) { throw new RepositoryArchiveException( "Failed to get the path for the archive", archiveFile, e); } String zipPath = path + ".metadata.zip"; File zip = new File(zipPath); if (!zip.exists()) { return null; } return explodeZip(zip); } public ArtifactMetadata explodeZip(File zip) throws RepositoryArchiveException { ArtifactMetadata artifact = new ArtifactMetadata(zip); checkRequiredProperties(artifact); return artifact; } protected void checkRequiredProperties(ArtifactMetadata artifact) throws RepositoryArchiveInvalidEntryException { checkPropertySet(PROP_NAME, artifact); checkPropertySet(PROP_DESCRIPTION, artifact); checkPropertySet(PROP_SHORT_DESCRIPTION, artifact); } public void attachLicenseData(ArtifactMetadata licensedArtifact, RepositoryResourceWritable resource) throws RepositoryException { for (File licenseFile : licensedArtifact.licenseFiles) { String licenseName = licenseFile.getName(); licenseName = licenseName .substring(0, licenseName.lastIndexOf(".")); if (isLocale(licenseName)) { // You'd think there'd be a locale parser on Locale... there // isn't, sigh resource.addLicense(licenseFile, RepositoryCommonUtils.localeForString(licenseName)); } else { // Don't throw an exception: it may be something like // notices.html } } // Record the type of the license in the asset resource.setLicenseType(licensedArtifact.licenseType); } final private Set<String> languages = new HashSet<String>( Arrays.asList(Locale.getISOLanguages())); final private Set<String> countries = new HashSet<String>( Arrays.asList(Locale.getISOCountries())); public boolean isLocale(String localeText) { if (localeText.contains("_")) { // Language and country, test both String[] languageAndCountry = localeText.split("_"); return languages.contains(languageAndCountry[0]) && countries.contains(languageAndCountry[1]); } else { // Language only return languages.contains(localeText); } } /** * Locate and process license agreement and information files within a feature * * @param esa * @param resource * @throws IOException */ protected void processLAandLI(File archive, RepositoryResourceWritable resource, EsaManifest feature) throws IOException, RepositoryException { String LAHeader = feature.getHeader(LA_HEADER_FEATURE); String LIHeader = feature.getHeader(LI_HEADER_FEATURE); processLAandLI(archive, resource, LAHeader, LIHeader); } /** * Locate and process license agreement and information files within a jar * * @param esa * @param resource * @throws IOException */ protected void processLAandLI(File archive, RepositoryResourceWritable resource, Manifest manifest) throws RepositoryException, IOException { Attributes attribs = manifest.getMainAttributes(); String LAHeader = attribs.getValue(LA_HEADER_PRODUCT); String LIHeader = attribs.getValue(LI_HEADER_PRODUCT); processLAandLI(archive, resource, LAHeader, LIHeader); } /* * LAHeader typical value: "wlp/lafiles/LA" typical files names are of the form * "wlp/lafiles/LA_en" */ protected void processLAandLI(File archive, RepositoryResourceWritable resource, String LAHeader, String LIHeader) throws IOException, RepositoryException { if (LAHeader == null && LIHeader == null) { return; } // Note: we allow the user to upload a feature whose manifest has // an LA header but no LI header. File explodedEsa = unpackToTempDir(archive); String root = explodedEsa.getCanonicalPath(); String laPrefix = (LAHeader == null) ? null : LAHeader .substring(LAHeader.lastIndexOf("/") + 1) + "_"; String liPrefix = (LIHeader == null) ? null : LIHeader .substring(LIHeader.lastIndexOf("/") + 1) + "_"; for (File f : listAllFiles(explodedEsa)) { String normalizedPath = f.getCanonicalPath() .replaceAll("\\\\", "/"); String shortPath = normalizedPath.substring(root.length()); String fileName = f.getName(); if (liPrefix != null && shortPath.contains(LIHeader)) { String localeText = fileName.substring(liPrefix.length()); if (isLocale(localeText)) { resource.addLicenseInformation(f, RepositoryCommonUtils.localeForString(localeText)); } } else if (laPrefix != null && shortPath.contains(LAHeader)) { String localeText = fileName.substring(laPrefix.length()); if (isLocale(localeText)) { resource.addLicenseAgreement(f, RepositoryCommonUtils.localeForString(localeText)); } } } } /** * <p> * This method adds an attachment for the content JAR and gets the location (if set) from the * artifact metadata, if the metadata is null or doesn't have a downloadURL in it then it will * upload the attachment. * </p> * <p> * If the asset already has a main content attachment then it is deleted (including deleting in * Massive) prior to adding the attachment as an asset can only have one content attachment * </p> * * @throws RepositoryException */ protected void addContent(RepositoryResourceWritable res, File assetFile, String name, ArtifactMetadata metadata, String contentUrl) throws RepositoryException { String downloadUrl = contentUrl; String linkTypeString = null; if (metadata != null && metadata.properties != null) { if (downloadUrl == null) { downloadUrl = metadata.properties.getProperty(PROP_DOWNLOAD_URL); } linkTypeString = metadata.properties.getProperty(LINK_TYPE_PROPERTY_KEY); } /* * You can only add content once, if this hasn't been uploaded you are not allowed to call * any of the getXXXAttachment methods */ String id = res.getId(); if (id != null && !id.isEmpty()) { AttachmentResourceImpl mainAttachmentResource = (AttachmentResourceImpl) res.getMainAttachment(); if (mainAttachmentResource != null && mainAttachmentResource.getType() == AttachmentType.CONTENT) { mainAttachmentResource.deleteNow(); } } if (downloadUrl != null && !downloadUrl.isEmpty()) { AttachmentLinkType linkType = linkTypeString != null ? AttachmentLinkType .valueOf(linkTypeString) : null; res.addContent(assetFile, name, downloadUrl, linkType); } else if (assetFile != null) { res.addContent(assetFile, name); } else { // No content so set the download policy to installer to hide the // download button in the UI res.setDownloadPolicy(DownloadPolicy.INSTALLER); } } public String getAppliesTo(ArtifactMetadata amd) throws RepositoryArchiveInvalidEntryException { // turn this // minVersion=8.5.5.0 ==> _appliesToMinimumVersionProp // minEdition= ==> _appliesToEditionsProp // into // Applies-To: com.ibm.websphere.appserver; productVersion=8.5.5.0+; // productEdition="BASE,DEVELOPERS,EXPRESS,ND,zOS" StringBuilder appliesToBuilder = new StringBuilder( "com.ibm.websphere.appserver"); String minVersion = amd.getProperty(PROP_APPLIES_TO_MIN_VERSION); if (minVersion != null && !minVersion.isEmpty()) { appliesToBuilder.append("; productVersion="); appliesToBuilder.append(minVersion); appliesToBuilder.append("+"); } String editonString = convertCommaSeparatedListToEditionString(amd .getProperty(PROP_APPLIES_TO_EDITIONS)); if (editonString != null && !editonString.isEmpty()) { appliesToBuilder.append(editonString); } return appliesToBuilder.toString(); } public void checkPropertySet(String propName, ArtifactMetadata amd) throws RepositoryArchiveInvalidEntryException { String prop = amd.getProperty(propName); if (prop == null) { throw new RepositoryArchiveInvalidEntryException("No " + propName + " field was specified in the properties file", amd.getArchive(), "*.properties"); } } /** * Convert a comma separated list String of editions into the required format e.g. "; * productEdition=\"BASE,DEVELOPERS,EXPRESS,ND,zOS\" * * @param editions * @return String the product edition supported */ private String convertCommaSeparatedListToEditionString(String csList) { if (csList == null) { return null; } // Split the comma separated list into an array csList = csList.trim(); List<Edition> editions = new ArrayList<Edition>(); if (csList.length() > 0) { String[] sArray = csList.split(","); for (String s : sArray) { if (s.length() != 0) { editions.add(Edition.valueOf(s)); } } } // convert the editions into a string String editionString = null; for (Edition ed : editions) { if (editionString == null) { editionString = "; productEdition=\""; editionString = editionString + ed.getProductEdition(); } else { editionString = editionString + "," + ed.getProductEdition(); } } if (editionString == null) { editionString = ""; } else { editionString = editionString + "\""; } return editionString; } /** * Take the require.feature comma separated list and return a List of the entries * * @return List - the required features */ public List<String> getRequiresFeature(ArtifactMetadata amd) { // Now check we have non-null input. I would like to check for valid // features // but as these may be user created I cannot find any way to do this so // it // is important there are no typos in the requires feature list TODO ? List<String> requiresFeature = new ArrayList<String>(); String requiresFeatureProp = amd.getProperty(PROP_REQUIRE_FEATURE); if (requiresFeatureProp == null) { return null; } if (requiresFeatureProp.equals("")) { return requiresFeature; } else { String[] features = requiresFeatureProp.split("\\,"); for (String feature : features) { requiresFeature.add(feature.trim()); } return requiresFeature; } } /** * Process icons from the properties file * * @param amd Metadata * @param res Resource to add icons to * @throws RepositoryException */ protected void processIcons(ArtifactMetadata amd, RepositoryResourceWritable res) throws RepositoryException { int size = 0; String current = ""; String sizeString = ""; String iconName = ""; String iconNames = amd.getIcons(); if (iconNames != null) { iconNames = iconNames.replaceAll("\\s", ""); StringTokenizer s = new StringTokenizer(iconNames, ","); while (s.hasMoreTokens()) { current = s.nextToken(); if (current.contains(";")) { // if the icon has an associated // size StringTokenizer t = new StringTokenizer(current, ";"); while (t.hasMoreTokens()) { sizeString = t.nextToken(); if (sizeString.contains("size=")) { String sizes[] = sizeString.split("size="); size = Integer.parseInt(sizes[sizes.length - 1]); } else { iconName = sizeString; } } } else { iconName = current; } File icon = this.extractFileFromArchive( amd.getArchive().getAbsolutePath(), iconName) .getExtractedFile(); if (icon.exists()) { AttachmentResourceWritable at = res.addAttachment(icon, AttachmentType.THUMBNAIL); if (size != 0) { at.setImageDimensions(size, size); } } else { throw new RepositoryArchiveEntryNotFoundException( "Icon does not exist", amd.getArchive(), iconName); } } } } public String getProviderName(ArtifactMetadata amd) { return amd.getProperty(PROP_PROVIDER_NAME); } /** * Gets the following fields from the metadata and sets them on the resource: description, * shortDescription, name and url * * @param amd * @param resource */ public void setCommonFieldsFromSideZip(ArtifactMetadata amd, RepositoryResourceWritable resource) { resource.setDescription(amd.getLongDescription()); resource.setShortDescription(amd.getShortDescription()); resource.setName(amd.getName()); } /** * Returns <code>true</code> if the supplied <code>file</code> is a valid zip and contains at * least one entry of each of the supplied <code>fileTypes</code>. * * @param file The zip file to check * @param fileTypes The types of files to look for * @return <code>true</code> if file types found */ protected boolean doesZipContainFileTypes(File file, String... fileTypes) { // It must be a zip file with an XML file at it's root and a properties file in it if (!file.getName().endsWith(".zip")) { return false; } ZipFile zip = null; try { zip = new ZipFile(file); boolean[] foundFileTypes = new boolean[fileTypes.length]; boolean foundAll = false; Enumeration<? extends ZipEntry> entries = zip.entries(); while (entries.hasMoreElements() && !foundAll) { foundAll = true; ZipEntry entry = entries.nextElement(); String name = entry.getName(); for (int i = 0; i < fileTypes.length; i++) { if (!foundFileTypes[i]) { foundFileTypes[i] = name.endsWith(fileTypes[i]); if (foundAll) { foundAll = foundFileTypes[i]; } } } } return foundAll; } catch (IOException e) { return false; } finally { if (zip != null) { try { zip.close(); } catch (IOException e) { return false; } } } } protected class ArtifactMetadata { public Collection<File> licenseFiles; private final Properties properties; private final Collection<File> otherFiles; private LicenseType licenseType; private final File archive; private ArtifactMetadata(File zip) throws RepositoryArchiveException { archive = zip; licenseFiles = new ArrayList<File>(); properties = new Properties(); otherFiles = new ArrayList<File>(); // The artifact is the first file with a .artifactExtension suffix // in the tempDir // All licenses are in the 'lafiles' directory. // The first .properties file is the one we will parse // The long description can be stored in a separate description.html file File propertiesFile = null; File descriptionFile = null; File tempDir = null; try { tempDir = unpackToTempDir(zip); } catch (IOException ioe) { throw new RepositoryArchiveException( "Failed to extract contents from archive", zip, ioe); } Collection<File> allTempFiles = listAllFiles(tempDir); // Just in case the ZIP contains more than one properties file always give preference to assetInfo.properties File assetInfo = new File(tempDir, "assetInfo.properties"); if (assetInfo.exists()) { propertiesFile = assetInfo; allTempFiles.remove(assetInfo); } for (File f : allTempFiles) { if (f.isFile()) { String name; try { name = f.getCanonicalPath(); } catch (IOException e) { throw new RepositoryArchiveInvalidEntryException( "Failed to read entry " + f.getAbsolutePath() + " in archive " + zip.getName(), zip, f.getAbsolutePath(), e); } if (propertiesFile == null && name.endsWith(".properties")) { propertiesFile = f; } else if (descriptionFile == null && f.getName().equalsIgnoreCase("description.html")) { descriptionFile = f; } else if (f.getParentFile().getName() .equalsIgnoreCase("lafiles")) { licenseFiles.add(f); } else { otherFiles.add(f); } } } if (propertiesFile == null) { throw new RepositoryArchiveEntryNotFoundException( "No properties file", zip, "*.properties"); } // Determine license type FileReader propertiesReader = null; Reader descriptionReader = null; try { propertiesReader = new FileReader(propertiesFile); properties.load(propertiesReader); String licenseTypeInProps = properties .getProperty(PROP_LICENSE_TYPE); // Not all artifacts have licenses so be lenient if (licenseTypeInProps != null) { licenseType = LicenseType.valueOf(licenseTypeInProps); } // Now read in the long description if (descriptionFile != null) { descriptionReader = new InputStreamReader(new FileInputStream(descriptionFile), "UTF-8"); char[] buf = new char[1024]; StringBuilder builder = new StringBuilder(); int chars; while ((chars = descriptionReader.read(buf, 0, 1024)) != -1) { // read to EOF builder.append(buf, 0, chars); } properties.setProperty(PROP_DESCRIPTION, builder.toString()); } } catch (IOException iox) { throw new RepositoryArchiveIOException( "Failed to read properties, licence and description from archive " + zip.getName(), zip, iox); } finally { if (propertiesReader != null) { try { propertiesReader.close(); } catch (IOException e) { throw new RepositoryArchiveIOException( "Failed to close the file reader that was reading the properties in the archive" + zip.getName(), zip, e); } } if (descriptionReader != null) { try { descriptionReader.close(); } catch (IOException e) { throw new RepositoryArchiveIOException( "Failed to close the file reader that was reading the description.html file" + zip.getName(), zip, e); } } } } public String getName() { return properties.getProperty(PROP_NAME); } public String getShortDescription() { return properties.getProperty(PROP_SHORT_DESCRIPTION); } public String getLongDescription() { return properties.getProperty(PROP_DESCRIPTION); } public String getIcons() { return properties.getProperty(PROP_ICONS); } public File getArchive() { return archive; } // In case someone needs to get another, non common, property from the // props file public String getProperty(String propName) { return properties.getProperty(propName); } public Collection<File> getOtherFiles() { return otherFiles; } // return the first "other file" with the supplied extension eg ".zip" or null if not found public File getFileWithExtension(String ext) { File retFile = null; for (File f : otherFiles) { if (f.getName().endsWith(ext)) { retFile = f; break; } } return retFile; } } }