| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.jackrabbit.vault.packaging.registry.impl; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import javax.annotation.Nonnull; |
| import javax.annotation.Nullable; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.jackrabbit.vault.fs.api.PathFilterSet; |
| import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter; |
| import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter; |
| import org.apache.jackrabbit.vault.fs.config.MetaInf; |
| import org.apache.jackrabbit.vault.fs.io.Archive; |
| import org.apache.jackrabbit.vault.fs.io.ImportOptions; |
| import org.apache.jackrabbit.vault.fs.io.MemoryArchive; |
| import org.apache.jackrabbit.vault.packaging.Dependency; |
| import org.apache.jackrabbit.vault.packaging.NoSuchPackageException; |
| import org.apache.jackrabbit.vault.packaging.PackageException; |
| import org.apache.jackrabbit.vault.packaging.PackageExistsException; |
| import org.apache.jackrabbit.vault.packaging.PackageId; |
| import org.apache.jackrabbit.vault.packaging.ScopedWorkspaceFilter; |
| import org.apache.jackrabbit.vault.packaging.SubPackageHandling; |
| import org.apache.jackrabbit.vault.packaging.VaultPackage; |
| import org.apache.jackrabbit.vault.packaging.events.PackageEvent; |
| import org.apache.jackrabbit.vault.packaging.events.PackageEvent.Type; |
| import org.apache.jackrabbit.vault.packaging.events.impl.PackageEventDispatcher; |
| import org.apache.jackrabbit.vault.packaging.impl.HollowVaultPackage; |
| import org.apache.jackrabbit.vault.packaging.impl.PackagePropertiesImpl; |
| import org.apache.jackrabbit.vault.packaging.impl.ZipVaultPackage; |
| import org.apache.jackrabbit.vault.packaging.registry.DependencyReport; |
| import org.apache.jackrabbit.vault.packaging.registry.PackageRegistry; |
| import org.apache.jackrabbit.vault.packaging.registry.RegisteredPackage; |
| import org.apache.jackrabbit.vault.util.InputStreamPump; |
| import org.apache.jackrabbit.vault.util.PlatformNameFormat; |
| import org.apache.jackrabbit.vault.util.Text; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.service.component.annotations.Activate; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.ConfigurationPolicy; |
| import org.osgi.service.component.annotations.Reference; |
| import org.osgi.service.metatype.annotations.AttributeDefinition; |
| import org.osgi.service.metatype.annotations.Designate; |
| import org.osgi.service.metatype.annotations.ObjectClassDefinition; |
| import org.osgi.service.metatype.annotations.Option; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * FileSystem based registry not depending on a JCR Session. All metadata is stored in Filesystem and can be prepared and used without a running JCR repository |
| * Only methods to install or uninstall packages require an active {@code Session} object of running jcr instance to perform the actual installation tasks |
| */ |
| @Component( |
| service = PackageRegistry.class, |
| configurationPolicy = ConfigurationPolicy.REQUIRE, |
| property = {"service.vendor=The Apache Software Foundation"} |
| ) |
| @Designate(ocd = FSPackageRegistry.Config.class) |
| public class FSPackageRegistry extends AbstractPackageRegistry { |
| |
| private static final String REPOSITORY_HOME = "repository.home"; |
| |
| /** |
| * default logger |
| */ |
| private static final Logger log = LoggerFactory.getLogger(FSPackageRegistry.class); |
| |
| /** |
| * Suffixes for metadata files |
| */ |
| private final String[] META_SUFFIXES = {"xml"}; |
| |
| private Map<PackageId, FSInstallState> stateCache = new ConcurrentHashMap<>(); |
| |
| /** |
| * Contains a map of all filesystem paths to package IDs |
| */ |
| private Map<Path, PackageId> pathIdMapping = new ConcurrentHashMap<>(); |
| |
| |
| private boolean packagesInitializied = false; |
| |
| @Reference |
| private PackageEventDispatcher dispatcher; |
| |
| private File homeDir; |
| |
| private InstallationScope scope; |
| |
| private File getHomeDir() { |
| return homeDir; |
| } |
| |
| /** |
| * Creates a new FSPackageRegistry based on the given home directory. |
| * |
| * @param homeDir the directory in which packages and their metadata is stored |
| * @throws IOException If an I/O error occurs. |
| */ |
| public FSPackageRegistry(@Nonnull File homeDir) throws IOException { |
| this(homeDir, InstallationScope.UNSCOPED); |
| } |
| |
| /** |
| * Creates a new FSPackageRegistry based on the given home directory. |
| * |
| * @param homeDir the directory in which packages and their metadata is stored |
| * @param scope to set a corresponding workspacefilter |
| * @throws IOException If an I/O error occurs. |
| */ |
| public FSPackageRegistry(@Nonnull File homeDir, InstallationScope scope) throws IOException { |
| this.homeDir = homeDir; |
| this.scope = scope; |
| loadPackageCache(); |
| } |
| |
| /** |
| * Deafult constructor for OSGi initialization (homeDir defined via activator) |
| */ |
| public FSPackageRegistry() { |
| } |
| |
| |
| @ObjectClassDefinition( |
| name = "Apache Jackrabbit FS Package Registry Service" |
| ) |
| @interface Config { |
| |
| @AttributeDefinition |
| String homePath() default "packageregistry"; |
| |
| @AttributeDefinition(name = "Installation Scope", |
| description = "Allows to limit the installation scope of this Apache Jackrabbit FS Package Registry Service. " |
| + "Packages installed from this registry may be unscoped (unfiltered), " |
| + "application scoped (only content for /apps & /libs) " |
| + "or content scoped (all content despite of /libs & /apps)", |
| options = { |
| @Option(label = "Unscoped", value = "UNSCOPED"), |
| @Option(label = "Application Scoped", value = "APPLICATION_SCOPED"), |
| @Option(label = "Content Scoped", value = "CONTENT_SCOPED") |
| }) |
| String scope() default "UNSCOPED"; |
| } |
| |
| @Activate |
| private void activate(BundleContext context, Config config) throws IOException { |
| String repoHome = context.getProperty(REPOSITORY_HOME); |
| if (repoHome == null) { |
| this.homeDir = context.getDataFile(config.homePath()); |
| } else { |
| this.homeDir = new File(config.homePath()); |
| if (!this.homeDir.isAbsolute()) { |
| this.homeDir = new File(repoHome + "/" + config.homePath()); |
| } |
| if (!homeDir.exists()) { |
| homeDir.mkdirs(); |
| } |
| } |
| this.scope = InstallationScope.valueOf(config.scope()); |
| loadPackageCache(); |
| log.info("Jackrabbit Filevault FS Package Registry initialized with home location {}", this.homeDir.getPath()); |
| } |
| |
| /** |
| * Sets the event dispatcher |
| * |
| * @param dispatcher the dispatcher. |
| */ |
| public void setDispatcher(@Nullable PackageEventDispatcher dispatcher) { |
| this.dispatcher = dispatcher; |
| } |
| |
| /** |
| * Dispatches a package event using the configured dispatcher. |
| * |
| * @param type event type |
| * @param id package id |
| * @param related related packages |
| */ |
| public void dispatch(@Nonnull PackageEvent.Type type, @Nonnull PackageId id, @Nullable PackageId[] related) { |
| if (dispatcher == null) { |
| return; |
| } |
| dispatcher.dispatch(type, id, related); |
| } |
| |
| @Nullable |
| @Override |
| public RegisteredPackage open(@Nonnull PackageId id) throws IOException { |
| FSInstallState state = getInstallState(id); |
| return FSPackageStatus.NOTREGISTERED != state.getStatus() ? new FSRegisteredPackage(this, state) : null; |
| } |
| |
| @Override |
| public boolean contains(@Nonnull PackageId id) throws IOException { |
| return stateCache.containsKey(id); |
| } |
| |
| @Nullable |
| private File getPackageFile(@Nonnull PackageId id) { |
| try { |
| FSInstallState state = getInstallState(id); |
| if (FSPackageStatus.NOTREGISTERED == state.getStatus()) { |
| return buildPackageFile(id); |
| } else { |
| return state.getFilePath().toFile(); |
| } |
| } catch (IOException e) { |
| log.error("Couldn't get install state of packageId {}", id, e); |
| } |
| return null; |
| } |
| |
| private File buildPackageFile(@Nonnull PackageId id) { |
| String path = getInstallationPath(id); |
| return new File(getHomeDir(), path + ".zip"); |
| } |
| |
| /** |
| * Returns the meta data file of the package with the given Id. |
| * |
| * @param id The package Id. |
| * @return the meta data file. |
| */ |
| @Nonnull |
| private File getPackageMetaDataFile(@Nonnull PackageId id) { |
| final String path = getInstallationPath(id); |
| return new File(getHomeDir(), path + ".xml"); |
| } |
| |
| /** |
| * Opens the package of a file with the given Id. |
| * @param id The Id of package file. |
| * @return the package |
| * @throws IOException if an I/O error occurrs. |
| */ |
| @Nonnull |
| protected VaultPackage openPackageFile(@Nonnull PackageId id) throws IOException { |
| File pkg = getPackageFile(id); |
| if (pkg == null) { |
| throw new IOException("Could not find package file for id " + id); |
| } |
| |
| if (pkg.exists() && pkg.length() > 0) { |
| try { |
| return new ZipVaultPackage(pkg, false, true); |
| } catch (IOException e) { |
| log.error("Cloud not open file {} as ZipVaultPackage.", pkg.getPath(), e); |
| throw e; |
| } |
| } else { |
| return new HollowVaultPackage(getInstallState(id).getProperties()); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Nonnull |
| @Override |
| public DependencyReport analyzeDependencies(@Nonnull PackageId id, boolean onlyInstalled) throws IOException, NoSuchPackageException { |
| List<Dependency> unresolved = new LinkedList<>(); |
| List<PackageId> resolved = new LinkedList<>(); |
| FSInstallState state = getInstallState(id); |
| if (FSPackageStatus.NOTREGISTERED == state.getStatus()) { |
| throw new NoSuchPackageException().setId(id); |
| } |
| |
| // Make sure that also dependencies of contained packages are considered as packages will be installed in a joined sequence. |
| Set<Dependency> allDependencies = new HashSet<>(); |
| allDependencies.addAll(state.getDependencies()); |
| for (PackageId subId : state.getSubPackages().keySet()) { |
| FSInstallState subState = getInstallState(subId); |
| allDependencies.addAll(subState.getDependencies()); |
| } |
| |
| for (Dependency dep : allDependencies) { |
| PackageId resolvedId = resolve(dep, onlyInstalled); |
| if (resolvedId == null) { |
| unresolved.add(dep); |
| } else { |
| resolved.add(resolvedId); |
| } |
| } |
| |
| return new DependencyReportImpl(id, unresolved.toArray(new Dependency[unresolved.size()]), |
| resolved.toArray(new PackageId[resolved.size()]) |
| ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public PackageId resolve(Dependency dependency, boolean onlyInstalled) throws IOException { |
| PackageId bestId = null; |
| for (PackageId id : packages()) { |
| if (!onlyInstalled || isInstalled(id)) { |
| if (dependency.matches(id)) { |
| if (bestId == null || id.getVersion().compareTo(bestId.getVersion()) > 0) { |
| bestId = id; |
| } |
| } |
| } |
| } |
| return bestId; |
| } |
| |
| /** |
| * Returns {@code true} when state {@link FSPackageStatus#EXTRACTED} is recorded for given {@code PackageId} |
| * |
| * @param id PackageId of the package to test. |
| * @return {@code true} if package is in state {@link FSPackageStatus#EXTRACTED} |
| * |
| * @throws IOException If an I/O error occurs. |
| */ |
| boolean isInstalled(PackageId id) throws IOException { |
| FSPackageStatus status = getInstallState(id).getStatus(); |
| return FSPackageStatus.EXTRACTED == status; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Nonnull |
| @Override |
| public PackageId register(@Nonnull InputStream in, boolean replace) throws IOException, PackageExistsException { |
| return register(in, replace, null); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Nonnull |
| private PackageId register(@Nonnull InputStream in, boolean replace, Dependency autoDependency) throws IOException, PackageExistsException { |
| ZipVaultPackage pkg = upload(in, replace); |
| |
| Map<PackageId, SubPackageHandling.Option> subpackages = registerSubPackages(pkg, replace); |
| File pkgFile = buildPackageFile(pkg.getId()); |
| HashSet<Dependency> dependencies = new HashSet<>(); |
| dependencies.addAll(Arrays.asList(pkg.getDependencies())); |
| if (autoDependency != null) { |
| dependencies.add(autoDependency); |
| } |
| FSInstallState state = new FSInstallState(pkg.getId(), FSPackageStatus.REGISTERED) |
| .withFilePath(pkgFile.toPath()) |
| .withDependencies(dependencies) |
| .withSubPackages(subpackages) |
| .withFilter(pkg.getArchive().getMetaInf().getFilter()) |
| .withSize(pkg.getSize()) |
| .withProperties(pkg.getArchive().getMetaInf().getProperties()) |
| .withExternal(false); |
| setInstallState(state); |
| return pkg.getId(); |
| } |
| |
| /** |
| * Registers subpackages in registry |
| * |
| * @param pkg The package to regist |
| * @param replace {@code true} to replace |
| * @return {@code Map} of {@code PackageId}s along with the corresponding {@code SubPackageHandling.Option} registered from a given {@code VaultPackage} |
| * |
| * @throws IOException |
| * @throws PackageExistsException |
| */ |
| private Map<PackageId, SubPackageHandling.Option> registerSubPackages(VaultPackage pkg, boolean replace) |
| throws IOException, PackageExistsException { |
| Map<PackageId, SubPackageHandling.Option> subpackages = new HashMap<>(); |
| |
| Archive.Entry packagesRoot = pkg.getArchive().getEntry(ARCHIVE_PACKAGE_ROOT_PATH); |
| if (packagesRoot != null) { |
| // As for JcrPackageImpl subpackages need to get an implicit autoDependency to the parent in case they have own content |
| boolean hasOwnContent = false; |
| for (PathFilterSet root : pkg.getArchive().getMetaInf().getFilter().getFilterSets()) { |
| // todo: find better way to detect subpackages |
| if (!Text.isDescendantOrEqual(DEFAULT_PACKAGE_ROOT_PATH, root.getRoot())) { |
| log.debug( |
| "Package {}: contains content outside /etc/packages. Sub packages will have a dependency to it", |
| pkg.getId()); |
| hasOwnContent = true; |
| } |
| } |
| Dependency autoDependency = hasOwnContent ? new Dependency(pkg.getId()) : null; |
| registerSubPackages(pkg, packagesRoot, DEFAULT_PACKAGE_ROOT_PATH, replace, subpackages, autoDependency); |
| dispatch(Type.EXTRACT_SUB_PACKAGES, pkg.getId(), subpackages.keySet().toArray(new PackageId[subpackages.size()])); |
| } |
| return subpackages; |
| } |
| |
| /** |
| * Parses given {@link Archive.Entry} for .jar & .zip binaries and tries to register given subpackage. |
| * |
| * @param vltPkg |
| * @param directory |
| * @param parentPath |
| * @param replace |
| * @param subpackages |
| * @throws IOException |
| * @throws PackageExistsException |
| */ |
| private void registerSubPackages(VaultPackage vltPkg, Archive.Entry directory, String parentPath, boolean replace, Map<PackageId, SubPackageHandling.Option> subpackages, Dependency autoDependency) |
| throws IOException, PackageExistsException { |
| Collection<? extends Archive.Entry> files = directory.getChildren(); |
| |
| for (Archive.Entry file : files) { |
| String fileName = file.getName(); |
| String repoName = PlatformNameFormat.getRepositoryName(fileName); |
| String repoPath = parentPath + "/" + repoName; |
| if (file.isDirectory()) { |
| registerSubPackages(vltPkg, file, repoPath, replace, subpackages, autoDependency); |
| } else { |
| if (repoPath.startsWith(DEFAULT_PACKAGE_ROOT_PATH_PREFIX) && (repoPath.endsWith(".jar") || repoPath.endsWith(".zip"))) { |
| try (InputStream in = vltPkg.getArchive().openInputStream(file)) { |
| if (in == null) { |
| throw new IOException("Unable to open archive input stream of " + file); |
| } |
| PackageId id = register(in, replace); |
| SubPackageHandling.Option option = vltPkg.getSubPackageHandling().getOption(id); |
| subpackages.put(id, option); |
| } catch (PackageExistsException e) { |
| log.info("Subpackage already registered, skipping subpackage extraction."); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public ZipVaultPackage upload(InputStream in, boolean replace) |
| throws IOException, PackageExistsException { |
| |
| MemoryArchive archive = new MemoryArchive(false); |
| File tempFile = File.createTempFile("upload", ".zip"); |
| |
| try (InputStreamPump pump = new InputStreamPump(in, archive)) { |
| // this will cause the input stream to be consumed and the memory |
| // archive being initialized. |
| try { |
| |
| FileUtils.copyInputStreamToFile(pump, tempFile); |
| } catch (Exception e) { |
| String msg = "Stream could be read successfully."; |
| log.error(msg); |
| throw new IOException(msg, e); |
| } |
| |
| if (archive.getJcrRoot() == null) { |
| String msg = "Stream is not a content package. Missing 'jcr_root'."; |
| log.error(msg); |
| throw new IOException(msg); |
| } |
| |
| final MetaInf inf = archive.getMetaInf(); |
| PackagePropertiesImpl props = new PackagePropertiesImpl() { |
| @Override |
| protected Properties getPropertiesMap() { |
| return inf.getProperties(); |
| } |
| }; |
| PackageId pid = props.getId(); |
| |
| // invalidate pid if path is unknown |
| if (pid == null) { |
| throw new IllegalArgumentException("Unable to create package. No package pid set."); |
| } |
| if (!pid.isValid()) { |
| throw new IllegalArgumentException("Unable to create package. Illegal package name."); |
| } |
| |
| File oldPkgFile = getPackageFile(pid); |
| FSInstallState state = getInstallState(pid); |
| |
| if (oldPkgFile != null && oldPkgFile.exists()) { |
| if (replace && !state.isExternal()) { |
| oldPkgFile.delete(); |
| } else { |
| throw new PackageExistsException("Package already exists: " + pid).setId(pid); |
| } |
| } |
| |
| ZipVaultPackage pkg = new ZipVaultPackage(archive, true); |
| registerSubPackages(pkg, replace); |
| File pkgFile = buildPackageFile(pid); |
| FileUtils.moveFile(tempFile, pkgFile); |
| dispatch(Type.UPLOAD, pid, null); |
| return pkg; |
| } |
| |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Nonnull |
| @Override |
| public PackageId register(@Nonnull File file, boolean replace) throws IOException, PackageExistsException { |
| ZipVaultPackage pack = new ZipVaultPackage(file, false, true); |
| try { |
| File pkgFile = buildPackageFile(pack.getId()); |
| if (pkgFile.exists()) { |
| if (replace) { |
| pkgFile.delete(); |
| } else { |
| throw new PackageExistsException("Package already exists: " + pack.getId()).setId(pack.getId()); |
| } |
| } |
| Map<PackageId, SubPackageHandling.Option> subpackages = registerSubPackages(pack, replace); |
| FileUtils.copyFile(file, pkgFile); |
| Set<Dependency> dependencies = new HashSet<>(Arrays.asList(pack.getDependencies())); |
| FSInstallState state = new FSInstallState(pack.getId(), FSPackageStatus.REGISTERED) |
| .withFilePath(pkgFile.toPath()) |
| .withDependencies(dependencies) |
| .withSubPackages(subpackages) |
| .withFilter(pack.getArchive().getMetaInf().getFilter()) |
| .withSize(pack.getSize()) |
| .withProperties(pack.getArchive().getMetaInf().getProperties()) |
| .withExternal(false); |
| setInstallState(state); |
| return pack.getId(); |
| } finally { |
| if (!pack.isClosed()) { |
| pack.close(); |
| } |
| } |
| } |
| |
| @Nonnull |
| @Override |
| public PackageId registerExternal(@Nonnull File file, boolean replace) throws IOException, PackageExistsException { |
| if (!replace && pathIdMapping.containsKey(file.toPath())) { |
| PackageId pid = pathIdMapping.get(file.toPath()); |
| throw new PackageExistsException("Package already exists: " + pid).setId(pid); |
| } |
| ZipVaultPackage pack = new ZipVaultPackage(file, false, true); |
| try { |
| |
| FSInstallState state = getInstallState(pack.getId()); |
| if (!(FSPackageStatus.NOTREGISTERED == state.getStatus())) { |
| if (replace) { |
| try { |
| remove(pack.getId()); |
| } catch (NoSuchPackageException e) { |
| log.error("Status isn't NOTREGISTERD but no metafile exists to remove", e); |
| } |
| } else { |
| throw new PackageExistsException("Package already exists: " + pack.getId()).setId(pack.getId()); |
| } |
| } |
| Map<PackageId, SubPackageHandling.Option> subpackages = registerSubPackages(pack, replace); |
| Set<Dependency> dependencies = new HashSet<>(Arrays.asList(pack.getDependencies())); |
| FSInstallState targetState = new FSInstallState(pack.getId(), FSPackageStatus.REGISTERED) |
| .withFilePath(file.toPath()) |
| .withDependencies(dependencies) |
| .withSubPackages(subpackages) |
| .withFilter(pack.getArchive().getMetaInf().getFilter()) |
| .withSize(pack.getSize()) |
| .withProperties(pack.getArchive().getMetaInf().getProperties()) |
| .withExternal(true); |
| setInstallState(targetState); |
| return pack.getId(); |
| } finally { |
| if (!pack.isClosed()) { |
| pack.close(); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void remove(@Nonnull PackageId id) throws IOException, NoSuchPackageException { |
| FSInstallState state = getInstallState(id); |
| File metaData = getPackageMetaDataFile(id); |
| |
| if (!metaData.exists()) { |
| throw new NoSuchPackageException().setId(id); |
| } |
| metaData.delete(); |
| |
| if (!state.isExternal()) { |
| getPackageFile(id).delete(); |
| } |
| updateInstallState(id, FSPackageStatus.NOTREGISTERED); |
| dispatch(PackageEvent.Type.REMOVE, id, null); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Nonnull |
| @Override |
| public Set<PackageId> packages() throws IOException { |
| return packagesInitializied ? stateCache.keySet() : loadPackageCache(); |
| } |
| |
| /** |
| * Loads all state from files persisted in configured homeDir, adds to cache and returns all cached {@code PackageId}s. |
| * |
| * @return {@code Set} of all cached {@code PackageId}s |
| * |
| * @throws IOException If an I/O error occurs |
| */ |
| private Set<PackageId> loadPackageCache() throws IOException { |
| Map<PackageId, FSInstallState> cacheEntries = new HashMap<>(); |
| Map<Path, PackageId> idMapping = new HashMap<>(); |
| |
| |
| Collection<File> files = FileUtils.listFiles(getHomeDir(), META_SUFFIXES, true); |
| for (File file : files) { |
| FSInstallState state = FSInstallState.fromFile(file); |
| if (state != null) { |
| PackageId id = state.getPackageId(); |
| if (id != null) { |
| cacheEntries.put(id, state); |
| idMapping.put(state.getFilePath(), id); |
| |
| } |
| } |
| } |
| stateCache.putAll(cacheEntries); |
| pathIdMapping.putAll(idMapping); |
| packagesInitializied = true; |
| return cacheEntries.keySet(); |
| } |
| |
| /** |
| * Returns the path of this package.this also includes the version, but |
| * never the extension (.zip). |
| * |
| * @param id the package id |
| * @return the path of this package |
| */ |
| public String getInstallationPath(PackageId id) { |
| return getRelativeInstallationPath(id); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void installPackage(@Nonnull Session session, @Nonnull RegisteredPackage pkg, @Nonnull ImportOptions opts, |
| boolean extract) throws IOException, PackageException { |
| |
| // For now FS based persistence only supports extraction but no reversible installation |
| if (!extract) { |
| String msg = "Only extraction supported by FS based registry"; |
| log.error(msg); |
| throw new PackageException(msg); |
| } |
| try (VaultPackage vltPkg = pkg.getPackage()) { |
| WorkspaceFilter filter = getInstallState(vltPkg.getId()).getFilter(); |
| switch(scope) { |
| case APPLICATION_SCOPED: |
| if (filter instanceof DefaultWorkspaceFilter) { |
| opts.setFilter(ScopedWorkspaceFilter.createApplicationScoped((DefaultWorkspaceFilter)filter)); |
| } else { |
| String msg = "Scoped only supports WorkspaceFilters extending DefaultWorkspaceFilter"; |
| log.error(msg); |
| throw new PackageException(msg); |
| } |
| break; |
| case CONTENT_SCOPED: |
| if (filter instanceof DefaultWorkspaceFilter) { |
| opts.setFilter(ScopedWorkspaceFilter.createContentScoped((DefaultWorkspaceFilter)filter)); |
| } else { |
| String msg = "Scoped only supports WorkspaceFilters extending DefaultWorkspaceFilter"; |
| log.error(msg); |
| throw new PackageException(msg); |
| } |
| break; |
| default: |
| // no need to set filter in other cases |
| |
| } |
| vltPkg.extract(session, opts); |
| dispatch(PackageEvent.Type.EXTRACT, pkg.getId(), null); |
| updateInstallState(vltPkg.getId(), FSPackageStatus.EXTRACTED); |
| |
| } catch (RepositoryException e) { |
| throw new IOException(e); |
| } |
| } |
| |
| /** |
| * Uninstallation not supported for FS based PackageRegistry |
| */ |
| @Override |
| public void uninstallPackage(@Nonnull Session session, @Nonnull RegisteredPackage pkg, @Nonnull ImportOptions opts) throws IOException, PackageException { |
| String msg = "Uninstallation not supported by FS based registry"; |
| log.error(msg); |
| throw new PackageException(msg); |
| } |
| |
| /** |
| * Shortcut to just change the status of a package - implicitly sets the installtime when switching to EXTRACTED |
| * |
| * @param pid PackageId of the package to update |
| * @param targetStatus Status to update |
| * @throws IOException If an I/O error occurs. |
| */ |
| private void updateInstallState(PackageId pid, FSPackageStatus targetStatus) throws IOException { |
| FSInstallState state = getInstallState(pid); |
| Long installTime = state.getInstallationTime(); |
| if (FSPackageStatus.EXTRACTED == targetStatus) { |
| installTime = Calendar.getInstance().getTimeInMillis(); |
| } |
| FSInstallState targetState = new FSInstallState(pid, targetStatus) |
| .withFilePath(state.getFilePath()) |
| .withDependencies(state.getDependencies()) |
| .withSubPackages(state.getSubPackages()) |
| .withInstallTime(installTime) |
| .withSize(state.getSize()) |
| .withProperties(state.getProperties()) |
| .withExternal(state.isExternal()); |
| setInstallState(targetState); |
| } |
| |
| /** |
| * Persists the installState to a metadatafile and adds current state to cache |
| * @param state |
| * @throws IOException |
| */ |
| private void setInstallState(@Nonnull FSInstallState state) throws IOException { |
| PackageId pid = state.getPackageId(); |
| File metaData = getPackageMetaDataFile(pid); |
| |
| if (state.getStatus() == FSPackageStatus.NOTREGISTERED) { |
| pathIdMapping.remove(stateCache.get(pid).getFilePath()); |
| metaData.delete(); |
| stateCache.remove(pid); |
| } else { |
| state.save(metaData); |
| stateCache.put(pid, state); |
| pathIdMapping.put(state.getFilePath(), pid); |
| } |
| } |
| |
| /** |
| * Retrieves {@code InstallState} from cache, falls back to reading from metafile and returns state for {@code FSPackageStatus.NOTREGISTERED} in case not found. |
| * |
| * @param pid the PackageId of the package to retrieve the install state from. |
| * @return {@code InstallState} found for given {@code PackageId} or a fresh one with status {@code FSPackageStatus.NOTREGISTERED} |
| * |
| * @throws IOException if an I/O error occurs. |
| */ |
| @Nonnull |
| public FSInstallState getInstallState(PackageId pid) throws IOException { |
| if (stateCache.containsKey(pid)) { |
| return stateCache.get(pid); |
| } else { |
| File metaFile = getPackageMetaDataFile(pid); |
| FSInstallState state = FSInstallState.fromFile(metaFile); |
| if (state != null) { |
| //theoretical file - should only be feasible when manipulating on filesystem, writing metafile automatically updates cache |
| stateCache.put(pid, state); |
| pathIdMapping.put(state.getFilePath(), pid); |
| } |
| return state != null ? state : new FSInstallState(pid, FSPackageStatus.NOTREGISTERED); |
| } |
| } |
| |
| |
| } |