| /* |
| * 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.sling.distribution.serialization.impl.vlt; |
| |
| import javax.jcr.Node; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.NavigableMap; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.zip.Deflater; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.jackrabbit.vault.fs.api.ImportMode; |
| import org.apache.jackrabbit.vault.fs.api.PathFilterSet; |
| import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter; |
| import org.apache.jackrabbit.vault.fs.config.DefaultMetaInf; |
| import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter; |
| import org.apache.jackrabbit.vault.fs.config.MetaInf; |
| import org.apache.jackrabbit.vault.fs.filter.DefaultPathFilter; |
| import org.apache.jackrabbit.vault.fs.io.AccessControlHandling; |
| import org.apache.jackrabbit.vault.fs.io.ImportOptions; |
| import org.apache.jackrabbit.vault.packaging.ExportOptions; |
| import org.apache.jackrabbit.vault.packaging.JcrPackage; |
| import org.apache.jackrabbit.vault.packaging.PackageManager; |
| import org.apache.jackrabbit.vault.packaging.PackageProperties; |
| import org.apache.jackrabbit.vault.packaging.VaultPackage; |
| import org.apache.sling.distribution.DistributionRequest; |
| import org.apache.sling.distribution.DistributionRequestType; |
| import org.apache.sling.distribution.SimpleDistributionRequest; |
| import org.apache.sling.distribution.component.impl.SettingsUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Utility class for creating vlt filters and import/export options |
| */ |
| public class VltUtils { |
| |
| private final static Logger log = LoggerFactory.getLogger(VltUtils.class); |
| |
| /** |
| * The custom <code>Path-Mapping</code> property. |
| */ |
| private static final String PATH_MAPPING_PROPERTY = "Path-Mapping"; |
| |
| private static final String MAPPING_SEPARATOR = "="; |
| |
| private static final String MAPPING_DELIMITER = ";"; |
| |
| public static WorkspaceFilter createFilter(DistributionRequest distributionRequest, NavigableMap<String, List<String>> nodeFilters, |
| NavigableMap<String, List<String>> propertyFilters) { |
| DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); |
| |
| for (String path : distributionRequest.getPaths()) { |
| |
| // Set node path filters |
| List<String> patterns = new ArrayList<String>(); |
| patterns.addAll(Arrays.asList(distributionRequest.getFilters(path))); |
| boolean deep = distributionRequest.isDeep(path); |
| PathFilterSet nodeFilterSet = new PathFilterSet(path); |
| if (!deep) { |
| nodeFilterSet.addInclude(new DefaultPathFilter(path)); |
| } |
| initFilterSet(nodeFilterSet, nodeFilters, patterns); |
| |
| // Set property path filters |
| PathFilterSet propertyFilterSet = new PathFilterSet(path); |
| initFilterSet(propertyFilterSet, propertyFilters, new ArrayList<String>()); |
| |
| filter.add(nodeFilterSet, propertyFilterSet); |
| } |
| |
| return filter; |
| } |
| |
| public static String[] getPaths(MetaInf metaInf) { |
| if (metaInf == null) { |
| return null; |
| } |
| |
| WorkspaceFilter filter = metaInf.getFilter(); |
| if (filter == null) { |
| filter = new DefaultWorkspaceFilter(); |
| } |
| List<PathFilterSet> filterSets = filter.getFilterSets(); |
| String[] paths = new String[filterSets.size()]; |
| for (int i = 0; i < paths.length; i++) { |
| paths[i] = filterSets.get(i).getRoot(); |
| } |
| |
| return paths; |
| } |
| |
| private static void initFilterSet(PathFilterSet filterSet, NavigableMap<String, List<String>> globalFilters, List<String> patterns) { |
| |
| // add the most specific filter rules |
| String root = filterSet.getRoot(); |
| for (String key : globalFilters.descendingKeySet()) { |
| if (root.startsWith(key)) { |
| patterns.addAll(globalFilters.get(key)); |
| break; |
| } |
| } |
| |
| for (String pattern : patterns) { |
| PathFilterSet.Entry<DefaultPathFilter> entry = extractPathPattern(pattern); |
| |
| if (entry.isInclude()) { |
| filterSet.addInclude(entry.getFilter()); |
| } else { |
| filterSet.addExclude(entry.getFilter()); |
| } |
| } |
| } |
| |
| |
| public static ExportOptions getExportOptions(WorkspaceFilter filter, String[] packageRoots, |
| String packageGroup, |
| String packageName, |
| String packageVersion, |
| boolean useBinaryReferences, |
| Map<String, String> exportPathMapping) { |
| DefaultMetaInf inf = new DefaultMetaInf(); |
| ExportOptions opts = new ExportOptions(); |
| inf.setFilter(filter); |
| |
| Properties props = new Properties(); |
| props.setProperty(VaultPackage.NAME_GROUP, packageGroup); |
| props.setProperty(VaultPackage.NAME_NAME, packageName); |
| props.setProperty(VaultPackage.NAME_VERSION, packageVersion); |
| props.setProperty(PackageProperties.NAME_USE_BINARY_REFERENCES, String.valueOf(useBinaryReferences)); |
| |
| if (exportPathMapping != null && !exportPathMapping.isEmpty()) { |
| StringBuilder builder = new StringBuilder(); |
| |
| for (Entry<String, String> entry : exportPathMapping.entrySet()) { |
| if (builder.length() > 0) { |
| builder.append(MAPPING_DELIMITER); |
| } |
| |
| builder.append(entry.getKey()) |
| .append(MAPPING_SEPARATOR) |
| .append(entry.getValue()); |
| } |
| |
| props.setProperty(PATH_MAPPING_PROPERTY, builder.toString()); |
| } |
| |
| inf.setProperties(props); |
| |
| opts.setMetaInf(inf); |
| |
| String root = getPackageRoot(filter.getFilterSets(), packageRoots); |
| opts.setRootPath(root); |
| opts.setMountPath(root); |
| |
| // Set the zlib compression level to "best speed" |
| // This level enables the FileVault improvement |
| // covered by JCRVLT-163. |
| opts.setCompressionLevel(Deflater.BEST_SPEED); |
| |
| return opts; |
| } |
| |
| |
| /** |
| * Picks a package root that dominates all filter sets. If there is none then "/" is returned. |
| */ |
| private static String getPackageRoot(List<PathFilterSet> filterSets, String[] packageRoots) { |
| |
| String packageRoot = null; |
| |
| if (packageRoots != null && packageRoots.length > 0) { |
| for (String currentRoot : packageRoots) { |
| boolean filtersHaveCommonRoot = true; |
| |
| for (PathFilterSet filterSet : filterSets) { |
| String filterSetRoot = filterSet.getRoot(); |
| |
| if (!filterSetRoot.startsWith(currentRoot)) { |
| filtersHaveCommonRoot = false; |
| } |
| } |
| |
| if (filtersHaveCommonRoot) { |
| packageRoot = currentRoot; |
| break; |
| } |
| } |
| } |
| |
| if (packageRoot == null || !packageRoot.startsWith("/")) { |
| packageRoot = "/"; |
| } |
| |
| return packageRoot; |
| } |
| |
| public static ImportOptions getImportOptions(AccessControlHandling aclHandling, ImportMode importMode, int autosaveThreshold) { |
| ImportOptions opts = new ImportOptions(); |
| if (aclHandling != null) { |
| opts.setAccessControlHandling(aclHandling); |
| } else { |
| // default to overwrite |
| opts.setAccessControlHandling(AccessControlHandling.OVERWRITE); |
| } |
| if (importMode != null) { |
| opts.setImportMode(importMode); |
| } else { |
| // default to update |
| opts.setImportMode(ImportMode.UPDATE); |
| } |
| |
| opts.setPatchKeepInRepo(false); |
| |
| if (autosaveThreshold >= 0) { |
| opts.setAutoSaveThreshold(autosaveThreshold); |
| } |
| |
| opts.setStrict(true); |
| |
| return opts; |
| } |
| |
| public static VaultPackage createPackage(PackageManager packageManager, Session session, ExportOptions options, File tempFolder) throws IOException, RepositoryException { |
| File file = File.createTempFile("distr-vault-create-" + System.nanoTime(), ".zip", tempFolder); |
| |
| try { |
| return packageManager.assemble(session, options, file); |
| } catch (RepositoryException e) { |
| FileUtils.deleteQuietly(file); |
| throw e; |
| } |
| } |
| |
| public static VaultPackage readPackage(PackageManager packageManager, InputStream stream, File tempFolder) throws IOException { |
| File file = File.createTempFile("distr-vault-read-" + System.nanoTime(), ".zip", tempFolder); |
| OutputStream out = FileUtils.openOutputStream(file); |
| try { |
| IOUtils.copy(stream, out); |
| return packageManager.open(file); |
| } catch (IOException e) { |
| FileUtils.deleteQuietly(file); |
| throw e; |
| } finally { |
| IOUtils.closeQuietly(stream); |
| IOUtils.closeQuietly(out); |
| } |
| } |
| |
| public static void deletePackage(VaultPackage vaultPackage) { |
| if (vaultPackage == null) { |
| return; |
| } |
| |
| File file = vaultPackage.getFile(); |
| vaultPackage.close(); |
| |
| FileUtils.deleteQuietly(file); |
| } |
| |
| public static void deletePackage(JcrPackage jcrPackage) { |
| if (jcrPackage == null) { |
| return; |
| } |
| |
| Node node = jcrPackage.getNode(); |
| jcrPackage.close(); |
| |
| try { |
| if (node != null) { |
| node.remove(); |
| } |
| } catch (RepositoryException e) { |
| // do nothing |
| } |
| } |
| |
| public static File getTempFolder(String tempFolderPath) { |
| File directory; |
| try { |
| directory = new File(tempFolderPath); |
| if (!directory.exists() || !directory.isDirectory()) { |
| directory = null; |
| } |
| } catch (Throwable e) { |
| directory = null; |
| } |
| |
| return directory; |
| } |
| |
| public static String findParent(String path, String nodeName) { |
| path = path.endsWith("/") ? path : path + "/"; |
| |
| nodeName = "/" + nodeName + "/"; |
| |
| int idx = path.indexOf(nodeName); |
| |
| if (idx < 0) { |
| return null; |
| } |
| |
| return path.substring(0, idx); |
| } |
| |
| public static String appendMatchAll(String path) { |
| path = path.endsWith("/") ? path : path + "/"; |
| path = path + ".*"; |
| return path; |
| } |
| |
| public static TreeMap<String, List<String>> parseFilters(String[] filters) { |
| |
| TreeMap<String, List<String>> result = new TreeMap<String, List<String>>(); |
| |
| if (filters == null || filters.length == 0) { |
| return result; |
| } |
| |
| for (String filter : filters) { |
| String[] filterParts = filter.split("\\|"); |
| if (filterParts.length > 1) { |
| String path = SettingsUtils.removeEmptyEntry(filterParts[0]); |
| if (path == null) { |
| continue; |
| } |
| |
| List<String> filterSet = result.get(path); |
| if (filterSet == null) { |
| filterSet = new ArrayList<String>(); |
| } |
| |
| for (int i = 1; i < filterParts.length; i++) { |
| String filterPart = SettingsUtils.removeEmptyEntry(filterParts[i]); |
| if (filterPart == null) { |
| continue; |
| } |
| |
| filterSet.add(filterPart); |
| } |
| |
| result.put(path, filterSet); |
| |
| } |
| } |
| |
| return result; |
| } |
| |
| public static DistributionRequest sanitizeRequest(DistributionRequest request) { |
| |
| DistributionRequestType requestType = request.getRequestType(); |
| |
| if (!DistributionRequestType.ADD.equals(requestType) && !DistributionRequestType.DELETE.equals(requestType)) { |
| return request; |
| } |
| |
| Set<String> deepPaths = new HashSet<String>(); |
| List<String> paths = new ArrayList<String>(); |
| Map<String, String[]> filters = new HashMap<String, String[]>(); |
| |
| for (String path : request.getPaths()) { |
| if (VltUtils.findParent(path, "rep:policy") != null) { |
| if (DistributionRequestType.DELETE.equals(requestType)) { |
| // vlt cannot properly install delete of rep:policy subnodes |
| throw new IllegalArgumentException("cannot distribute DELETE node " + path); |
| } else if (DistributionRequestType.ADD.equals(requestType)) { |
| String newPath = VltUtils.findParent(path, "rep:policy") + "/rep:policy"; |
| paths.add(newPath); |
| deepPaths.add(newPath); |
| log.debug("changed distribution path {} to deep path {}", path, newPath); |
| } |
| } else if (request.isDeep(path)) { |
| paths.add(path); |
| deepPaths.add(path); |
| } else { |
| paths.add(path); |
| } |
| |
| filters.put(path, request.getFilters(path)); |
| } |
| |
| return new SimpleDistributionRequest(requestType, paths.toArray(new String[paths.size()]), deepPaths, filters); |
| } |
| |
| private static PathFilterSet.Entry<DefaultPathFilter> extractPathPattern(String pattern) { |
| PathFilterSet.Entry<DefaultPathFilter> result; |
| if (pattern.startsWith("+")) { |
| result = new PathFilterSet.Entry<DefaultPathFilter>(new DefaultPathFilter(pattern.substring(1)), true); |
| } else if (pattern.startsWith("-")) { |
| result = new PathFilterSet.Entry<DefaultPathFilter>(new DefaultPathFilter(pattern.substring(1)), false); |
| } else { |
| result = new PathFilterSet.Entry<DefaultPathFilter>(new DefaultPathFilter(pattern), true); |
| } |
| |
| return result; |
| } |
| } |