blob: e88386030778efe53b67c2801eb765ae785b35f4 [file] [log] [blame]
/*
* 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;
}
}