/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Car.java * Creation date: Jan 10, 2006. * By: Joseph Wong */ package org.openquark.cal.services; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import org.openquark.cal.machine.ProgramResourceLocator; import org.openquark.cal.metadata.MetadataJarStore; import org.openquark.cal.metadata.MetadataManager; import org.openquark.util.Pair; /** * A Car represents a CAL Archive file (a Car file) as a workspace resource. * <p> * A Car is like a jar in that it is based on the zip file format. The contents * of a Car file are CAL resources: workspace declarations, module-based * resources like CAL source files, metadata, gem designs, and also * program-oriented files like cmi and lc files. * <p> * Car files are intended to be used in scenarios where developers would like to * run the Quark platform but do not need to modify the resources contained in * the Car files in question. * <p> * A module is either completely defined in a Car, or lives completely outside a * Car. What this means is that the resources for a module cannot be scattered * across a Car boundary. Therefore, we now maintain a mapping from a module * name to the corresponding location where resources for the module are to be * found - either on disk or from a particular Car file. (See * {@link VirtualResourceManager}). * * @author Joseph Wong */ /* * Performance note: * * The use of Car files should not have any negative impact on performance. In * measurements using EverythingICE in Eclipse (in run mode), the use of Cars * has resulted in a performance gain of up to 15% in the time it takes to load * the workspace. This is due to the fact that there all the resources are in * one location in a Car, versus having to find the resources scattered across * the multiple directories that make up the StandardVault. A similar * performance gain can be achieved by having all the resources colocated in one * directory (as in the case of the packaged Quark builds). */ public final class Car extends WorkspaceResource { /** The file which contains the Car. */ private final File file; /** The singleton JarFileManager for this Car. It starts out as null and is lazily initialized on first use. */ private JarFileManager jarFileManager; /** The folder within a Car that contains Car workspace spec files. */ static final ResourcePath.Folder CAR_WORKSPACE_SPEC_FOLDER = new ResourcePath.Folder(new String[] {"META-INF", "Car Workspace Spec"}); /** * Instances of Car are meant to be created by an instance of this Factory class. This factory * caches Cars that it creates, so that for the same name and File, the factory returns the same Car object * in its getCar() method. * * @author Joseph Wong */ public final static class Factory { /** * A Map mapping a (car name, car file path) pair to the corresponding cached Car instance. */ private final Map<Pair<String, String>, Car> carCache = new WeakHashMap<Pair<String, String>, Car>(); /** * Factory method for getting a Car instance for the given name and File. * @param carName the name of the Car. * @param carFile the file which contains the Car. * @return the corresponding Car instance. */ public synchronized Car getCar(String carName, File carFile) { Pair<String, String> carID = new Pair<String, String>(carName, carFile.getPath()); Car car = carCache.get(carID); if (car == null) { car = new Car(carName, carFile); carCache.put(carID, car); } return car; } } /** * The Car.Accessor provides read access to a Car in its role as a container for other resources. * * @author Joseph Wong */ public static final class Accessor { /** * The jar file manager that encapsulates the underlying Car file. */ private final JarFileManager jarFileManager; /** * The registry of the resource managers which provide access to resources stored <i>within</i> the Car. */ private final ResourceManagerRegistry resourceManagerRegistry; /** * Private constructor for this class. Instances should be created via {@link Car#getAccessor(Status)}. * @param jarFileManager the jar file manager that encapsulates the underlying Car file. */ private Accessor(JarFileManager jarFileManager) { if (jarFileManager == null) { throw new NullPointerException("jarFileManager cannot be null."); } this.jarFileManager = jarFileManager; resourceManagerRegistry = new ResourceManagerRegistry(); resourceManagerRegistry.registerResourceManager(new CALSourceManager(new CALSourceJarStore(jarFileManager))); resourceManagerRegistry.registerResourceManager(new MetadataManager(new MetadataJarStore(jarFileManager))); resourceManagerRegistry.registerResourceManager(new GemDesignManager(new GemDesignJarStore(jarFileManager))); resourceManagerRegistry.registerResourceManager(new WorkspaceDeclarationManager(new WorkspaceDeclarationJarStore(jarFileManager))); resourceManagerRegistry.registerResourceManager(new UserResourceManager(new UserResourceJarStore(jarFileManager))); } /** * @return the name of the Car file. */ public String getCarFileName() { return getJarFile().getName(); } /** * @return the underlying JarFile representing the Car file. */ public JarFile getJarFile() { return jarFileManager.getJarFile(); } /** * @return the jar file manager encapsulating the Car file. */ public JarFileManager getJarFileManager() { return jarFileManager; } /** * Registers a resource manager for accessing resources within the Car. * * Package-scoped... expected to be called only by {@link VirtualResourceManager} * * @param resourceManager */ void registerResourceManager(ResourceManager resourceManager) { resourceManagerRegistry.registerResourceManager(resourceManager); } /** * @return the registered source manager, or null if there is none. */ public CALSourceManager getSourceManager() { return resourceManagerRegistry.getSourceManager(); } /** * @return the registered metadata manager, or null if there is none. */ public MetadataManager getMetadataManager() { return resourceManagerRegistry.getMetadataManager(); } /** * @return the registered gem design manager, or null if there is none. */ public GemDesignManager getDesignManager() { return resourceManagerRegistry.getDesignManager(); } /** * @return the registered workspace declaration manager, or null if there is none. */ public WorkspaceDeclarationManager getWorkspaceDeclarationManager() { return resourceManagerRegistry.getWorkspaceDeclarationManager(); } /** * @return the registered user resource manager, or null if there is none. */ public UserResourceManager getUserResourceManager() { return resourceManagerRegistry.getUserResourceManager(); } /** * Returns the resource manager which handles resources stored in the Car of a given type. * @param resourceType the String which identifies the type of resource. * @return the resource manager which handles resource of that type, or null if there isn't any. */ public ResourceManager getResourceManager(String resourceType) { return resourceManagerRegistry.getResourceManager(resourceType); } /** * @return the resource managers in the registry. */ public Set<ResourceManager> getResourceManagers() { return resourceManagerRegistry.getResourceManagers(); } /** * Returns an InputStream for the named Car workspace spec file in the Car. * @param specName the name of the spec file. * @param status the tracking status object. * @return the InputStream for the spec file, or null if the file cannot be found in the Car. */ public InputStream getWorkspaceSpec(String specName, Status status) { String relativePath = CAR_WORKSPACE_SPEC_FOLDER.extendFile(specName).getPathStringMinusSlash(); JarFile jarFile = getJarFile(); try { ZipEntry entry = jarFile.getEntry(relativePath); if (entry == null) { String errorString = "Error reading workspace spec from Car: The file " + relativePath + " cannot be found in the Car."; status.add(new Status(Status.Severity.ERROR, errorString)); return null; } else { return jarFile.getInputStream(entry); } } catch (IOException e) { String errorString = "Error reading workspace spec from Car."; status.add(new Status(Status.Severity.ERROR, errorString)); } return null; } /** * Returns the locators of the files inside the given folder. * @param folder the locator of the folder. * @param resourcePath the resource path that corresponds to the folder locator. * @return an array of ProgramResourceLocators for the files inside the folder. */ ProgramResourceLocator[] getFolderMembers(ProgramResourceLocator.Folder folder, ResourcePath resourcePath) { String relativePath = resourcePath.getPathStringMinusSlash(); Set<String> fileNames = jarFileManager.getFileNamesInFolder(relativePath); if (fileNames == null) { return new ProgramResourceLocator[0]; } else { List<ProgramResourceLocator> locatorList = new ArrayList<ProgramResourceLocator>(); for (final String trailingPath : fileNames) { locatorList.add(folder.extendFile(trailingPath)); } return locatorList.toArray(new ProgramResourceLocator[locatorList.size()]); } } /** * Fetches the named resource from inside the Car. * @param resourcePath the path to the resource within the Car. * @return an InputStream for the resource, or null if the resource cannot be found in the Car. * @throws IOException */ InputStream getResource(ResourcePath resourcePath) throws IOException { String relativePath = resourcePath.getPathStringMinusSlash(); JarFile jarFile = getJarFile(); ZipEntry entry = jarFile.getEntry(relativePath); if (entry == null) { throw new IOException("The resource " + relativePath + " cannot be found in the Car " + getCarFileName()); } return jarFile.getInputStream(entry); } /** * Returns whether the named resource exists in the Car. * @param resourcePath the path to the resource within the Car. * @return true if the resource is found in the Car, false otherwise. */ boolean doesResourceExist(ResourcePath resourcePath) { String relativePath = resourcePath.getPathStringMinusSlash(); ZipEntry entry = getJarFile().getEntry(relativePath); return entry != null; } /** * Returns the last modified time of the named resource in the Car. * @param resourcePath the path to the resource within the Car. * @return the last modified time of the resource, or 0 if the resource cannot be found in the Car. */ long getResourceLastModifiedTime(ResourcePath resourcePath) { String relativePath = resourcePath.getPathStringMinusSlash(); ZipEntry entry = getJarFile().getEntry(relativePath); if (entry == null) { // according to the contract of java.io.File.lastModified(), the value 0 is returned if the file does not exist return 0; } return entry.getTime(); } /** * Returns the size of the named resource in the Car. * @param resourcePath the path to the resource within the Car. * @return the size of resource, or 0 if the resource cannot be found in the Car. */ long getResourceSize(ResourcePath resourcePath) { String relativePath = resourcePath.getPathStringMinusSlash(); ZipEntry entry = getJarFile().getEntry(relativePath); if (entry == null) { // according to the contract of java.io.File.length(), the value 0 is returned if the file does not exist return 0; } return entry.getSize(); } /** * @param resourcePath the path to the resource within the Car. * @return debugging information about the resource, e.g. the actual location of the resource. Can be null if the resource does not exist. */ String getDebugInfo(ResourcePath resourcePath) { String relativePath = resourcePath.getPathStringMinusSlash(); JarFile jarFile = getJarFile(); ZipEntry entry = jarFile.getEntry(relativePath); if (entry == null) { return "The resource " + relativePath + " cannot be found in the Car " + getCarFileName(); } return "from Car: " + getCarFileName() + ", entry: " + entry; } } /** * Private constructor. The {@link Car.Factory} should be used to create instances of this class. * * @param carName the name of the Car. * @param carFile the file which contains the Car. */ private Car(String carName, File carFile) { super(ResourceIdentifier.make(WorkspaceResource.CAR_RESOURCE_TYPE, CarFeatureName.getCarFeatureName(carName))); if (carFile == null) { throw new NullPointerException("File must not be null."); } this.file = carFile; } /** * @return the file which contains the Car. */ public File getFile() { return file; } /** * @return the name of the Car. */ public String getName() { return getIdentifier().getFeatureName().getName(); } /** * {@inheritDoc} */ @Override public InputStream getInputStream(Status status) { try { return new FileInputStream(file); } catch (FileNotFoundException fnfe) { String errorString = "Error loading Car. File: \"" + file + "\" could not be found."; status.add(new Status(Status.Severity.ERROR, errorString)); } return null; } /** * {@inheritDoc} */ @Override public long getTimeStamp() { return file.lastModified(); } /** * {@inheritDoc} */ @Override public String getDebugInfo() { return "from file: " + file.getAbsolutePath(); } /** * Returns an accessor for reading the resources within the Car. * * @param status the tracking status object. * @return a Car.Accessor for accessing the Car, or null if there is a problem with reading the Car. */ public synchronized Accessor getAccessor(Status status) { try { if (jarFileManager == null) { jarFileManager = new JarFileManager(new JarFile(file)); } return new Accessor(jarFileManager); } catch (IOException e) { String errorString = "Error reading Car."; status.add(new Status(Status.Severity.ERROR, errorString)); } return null; } }