blob: 6ce203903c6fd900dec21409556e643833314a36 [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 org.apache.jackrabbit.vault.fs.api.IdConflictPolicy;
import org.apache.jackrabbit.vault.fs.api.ImportMode;
import org.apache.jackrabbit.vault.fs.io.AccessControlHandling;
import org.apache.jackrabbit.vault.packaging.Packaging;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.commons.scheduler.Scheduler;
import org.apache.sling.distribution.DistributionRequest;
import org.apache.sling.distribution.common.DistributionException;
import org.apache.sling.distribution.component.impl.SettingsUtils;
import org.apache.sling.distribution.monitor.impl.MonitoringDistributionPackageBuilder;
import org.apache.sling.distribution.packaging.DistributionPackage;
import org.apache.sling.distribution.packaging.DistributionPackageBuilder;
import org.apache.sling.distribution.packaging.DistributionPackageInfo;
import org.apache.sling.distribution.packaging.impl.FileDistributionPackageBuilder;
import org.apache.sling.distribution.packaging.impl.InMemoryDistributionPackageBuilder;
import org.apache.sling.distribution.packaging.impl.ResourceDistributionPackageBuilder;
import org.apache.sling.distribution.packaging.impl.ResourceDistributionPackageCleanup;
import org.apache.sling.distribution.serialization.DistributionContentSerializer;
import org.apache.sling.distribution.util.impl.FileBackedMemoryOutputStream.MemoryUnit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
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.Deactivate;
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 java.io.InputStream;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A package builder for Apache Jackrabbit FileVault based implementations.
*/
@Component(
configurationPolicy = ConfigurationPolicy.REQUIRE,
service=DistributionPackageBuilder.class,
property= {
"webconsole.configurationFactory.nameHint=Builder name: {name}"
})
@Designate(ocd=VaultDistributionPackageBuilderFactory.Config.class, factory=true)
public class VaultDistributionPackageBuilderFactory implements DistributionPackageBuilder {
@ObjectClassDefinition(name="Apache Sling Distribution Packaging - Vault Package Builder Factory",
description = "OSGi configuration for vault package builders")
public @interface Config {
@AttributeDefinition(name="name",description = "The name of the package builder.")
String name();
@AttributeDefinition(options = {
@Option(label = "jcr packages", value = "jcrvlt"),
@Option(label = "file packages",value = "filevlt"),
@Option(label = "in memory packages",value = "inmemory")},
name = "type", description = "The type of this package builder")
String type() default "jcrvlt";
@AttributeDefinition(name="Import Mode", description = "The vlt import mode for created packages.")
String importMode();
@AttributeDefinition(name="Acl Handling", description = "The vlt acl handling mode for created packages.")
String aclHandling();
@AttributeDefinition(name="Cug Handling", description = "The vlt cug handling mode for created packages.")
String cugHandling();
@AttributeDefinition(name="Package Roots", description = "The package roots to be used for created packages. "
+ "(this is useful for assembling packages with an user that cannot read above the package root)")
String[] package_roots();
@AttributeDefinition(name="Package Node Filters",
description = "The package node path filters. Filter format: path|+include|-exclude",
cardinality = 100)
String[] package_filters();
@AttributeDefinition(name="Package Property Filters",
description = "The package property path filters. Filter format: path|+include|-exclude")
String[] property_filters();
@AttributeDefinition(name="Temp Filesystem Folder",
description = "The filesystem folder where the temporary files should be saved.")
String tempFsFolder();
@AttributeDefinition(name="Use Binary References",
description = "If activated, it avoids sending binaries in the distribution package.")
boolean useBinaryReferences() default false;
@AttributeDefinition(name="Autosave threshold", description = "The value after which autosave is triggered for intermediate changes.")
int autoSaveThreshold() default -1;
@AttributeDefinition(
name = "The delay in seconds between two runs of the cleanup phase for resource persisted packages.",
description = "The resource persisted packages are cleaned up periodically (asynchronously) since SLING-6503." +
"The delay between two runs of the cleanup phase can be configured with this setting. 60 seconds by default")
long cleanupDelay() default DEFAULT_PACKAGE_CLEANUP_DELAY;
@AttributeDefinition(
name = "File threshold (in bytes)",
description = "Once the data reaches the configurable size value, buffering to memory switches to file buffering.")
int fileThreshold() default DEFAULT_FILE_THRESHOLD_VALUE;
@AttributeDefinition(
name = "The memory unit for the file threshold",
description = "The memory unit for the file threshold, Megabytes by default",
options = {
@Option(label = "Bytes", value = "BYTES"),
@Option(label = "Kilobytes", value = "KILO_BYTES"),
@Option(label = "Megabytes", value = "MEGA_BYTES"),
@Option(label = "Gigabytes", value = "GIGA_BYTES")
})
String MEGA_BYTES() default DEFAULT_MEMORY_UNIT;
@AttributeDefinition(
name = "Flag to enable/disable the off-heap memory",
description = "Flag to enable/disable the off-heap memory, false by default")
boolean useOffHeapMemory() default DEFAULT_USE_OFF_HEAP_MEMORY;
@AttributeDefinition(
name = "The digest algorithm to calculate the package checksum",
description = "The digest algorithm to calculate the package checksum, Megabytes by default",
options = {
@Option(label = DEFAULT_DIGEST_ALGORITHM, value = "Do not send digest"),
@Option(label = "MD2", value = "md2"),
@Option(label = "MD5", value = "md5"),
@Option(label = "SHA-1", value = "sha1"),
@Option(label = "SHA-256", value = "sha256"),
@Option(label = "SHA-384", value = "sha384"),
@Option(label = "SHA-512", value = "sha512")
})
String digestAlgorithm() default DEFAULT_DIGEST_ALGORITHM;
@AttributeDefinition(
name = "The number of items for monitoring distribution packages creation/installation",
description = "The number of items for monitoring distribution packages creation/installation, 100 by default")
int monitoringQueueSize() default DEFAULT_MONITORING_QUEUE_SIZE;
@AttributeDefinition(cardinality = 100,
name = "Paths mapping",
description = "List of paths that require be mapped." +
"The format is {sourcePattern}={destinationPattern}, e.g. /etc/(.*)=/var/$1/some or simply /data=/bak")
String[] pathsMapping();
@AttributeDefinition(
name = "Install a content package in a strict mode",
description = "Whether an error will be thrown, if a content package is incorrectly installed")
boolean strictImport() default DEFAULT_IS_STRICT;
@AttributeDefinition(
name = "Legacy Folder Primary Type Mode",
description = "Whether to overwrite the primary type of folders during imports")
boolean overwritePrimaryTypesOfFolders() default DEFAULT_OVERWRITE_PRIMARY_TYPES_OF_FOLDER;
@AttributeDefinition(
name = "ID Conflict Policy",
description = "Node id conflict policy to be used during import")
IdConflictPolicy idConflictPolicy() default IdConflictPolicy.LEGACY;
}
private static final long DEFAULT_PACKAGE_CLEANUP_DELAY = 60L;
// 1M
private static final int DEFAULT_FILE_THRESHOLD_VALUE = 1;
private static final String DEFAULT_MEMORY_UNIT = "MEGA_BYTES";
private static final boolean DEFAULT_USE_OFF_HEAP_MEMORY = false;
private static final String DEFAULT_DIGEST_ALGORITHM = "NONE";
private static final int DEFAULT_MONITORING_QUEUE_SIZE = 0;
// importer setting constants
private static final boolean DEFAULT_IS_STRICT = true;
private static final boolean DEFAULT_OVERWRITE_PRIMARY_TYPES_OF_FOLDER = true;
@Reference
private Packaging packaging;
@Reference
private ResourceResolverFactory resolverFactory;
private ServiceRegistration<Runnable> packageCleanup = null;
private MonitoringDistributionPackageBuilder packageBuilder;
@Activate
public void activate(BundleContext context, Config conf) {
String name = conf.name();
String type = conf.type();
String importModeString = SettingsUtils.removeEmptyEntry(conf.importMode());
String aclHandlingString = SettingsUtils.removeEmptyEntry(conf.aclHandling());
String cugHandlingString = SettingsUtils.removeEmptyEntry(conf.cugHandling());
String[] packageRoots = SettingsUtils.removeEmptyEntries(conf.package_roots());
String[] packageNodeFilters = SettingsUtils.removeEmptyEntries(conf.package_filters());
String[] packagePropertyFilters = SettingsUtils.removeEmptyEntries(conf.property_filters());
long cleanupDelay = conf.cleanupDelay();
String tempFsFolder = SettingsUtils.removeEmptyEntry(conf.tempFsFolder());
boolean useBinaryReferences = conf.useBinaryReferences();
String digestAlgorithm = conf.digestAlgorithm();
if (DEFAULT_DIGEST_ALGORITHM.equals(digestAlgorithm)) {
digestAlgorithm = null;
}
// check the mount path patterns, if any
Map<String, String> pathsMapping = toMap(conf.pathsMapping(), new String[0]);
pathsMapping = SettingsUtils.removeEmptyEntries(pathsMapping);
// import settings
int autosaveThreshold = conf.autoSaveThreshold();
ImportMode importMode = null;
if (importModeString != null) {
importMode = ImportMode.valueOf(importModeString.trim());
}
AccessControlHandling aclHandling = null;
if (aclHandlingString != null) {
aclHandling = AccessControlHandling.valueOf(aclHandlingString.trim());
}
AccessControlHandling cugHandling = null;
if (cugHandlingString != null) {
cugHandling = AccessControlHandling.valueOf(cugHandlingString.trim());
}
boolean strictImport = conf.strictImport();
boolean overwritePrimaryTypesOfFolders = conf.overwritePrimaryTypesOfFolders();
IdConflictPolicy idConflictPolicy = conf.idConflictPolicy();
ImportSettings importSettings = new ImportSettings(importMode, aclHandling, cugHandling, autosaveThreshold, strictImport,
overwritePrimaryTypesOfFolders, idConflictPolicy);
DistributionContentSerializer contentSerializer = new FileVaultContentSerializer(name, packaging, packageRoots, packageNodeFilters,
packagePropertyFilters, useBinaryReferences, pathsMapping, importSettings);
DistributionPackageBuilder wrapped;
if ("filevlt".equals(type)) {
wrapped = new FileDistributionPackageBuilder(name, contentSerializer, tempFsFolder, digestAlgorithm, packageNodeFilters, packagePropertyFilters);
} else if ("inmemory".equals(type)) {
wrapped = new InMemoryDistributionPackageBuilder(name, contentSerializer, packageNodeFilters, packagePropertyFilters);
} else {
final int fileThreshold = conf.fileThreshold();
String memoryUnitName = conf.MEGA_BYTES();
final MemoryUnit memoryUnit = MemoryUnit.valueOf(memoryUnitName);
final boolean useOffHeapMemory = conf.useOffHeapMemory();
ResourceDistributionPackageBuilder resourceDistributionPackageBuilder = new ResourceDistributionPackageBuilder(contentSerializer.getName(), contentSerializer, tempFsFolder, fileThreshold, memoryUnit, useOffHeapMemory, digestAlgorithm, packageNodeFilters, packagePropertyFilters);
Runnable cleanup = new ResourceDistributionPackageCleanup(resolverFactory, resourceDistributionPackageBuilder);
Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put(Scheduler.PROPERTY_SCHEDULER_CONCURRENT, false);
props.put(Scheduler.PROPERTY_SCHEDULER_PERIOD, cleanupDelay);
props.put(Scheduler.PROPERTY_SCHEDULER_RUN_ON, Scheduler.VALUE_RUN_ON_SINGLE);
packageCleanup = context.registerService(Runnable.class, cleanup, props);
wrapped = resourceDistributionPackageBuilder;
}
int monitoringQueueSize = conf.monitoringQueueSize();
packageBuilder = new MonitoringDistributionPackageBuilder(monitoringQueueSize, wrapped, context);
}
@Deactivate
public void deactivate() {
packageBuilder.clear();
if (packageCleanup != null) {
packageCleanup.unregister();
}
}
public String getType() {
return packageBuilder.getType();
}
@NotNull
public DistributionPackage createPackage(@NotNull ResourceResolver resourceResolver, @NotNull DistributionRequest request) throws DistributionException {
return packageBuilder.createPackage(resourceResolver, request);
}
@NotNull
public DistributionPackage readPackage(@NotNull ResourceResolver resourceResolver, @NotNull InputStream stream) throws DistributionException {
return packageBuilder.readPackage(resourceResolver, stream);
}
@Nullable
public DistributionPackage getPackage(@NotNull ResourceResolver resourceResolver, @NotNull String id) throws DistributionException {
return packageBuilder.getPackage(resourceResolver, id);
}
public boolean installPackage(@NotNull ResourceResolver resourceResolver, @NotNull DistributionPackage distributionPackage) throws DistributionException {
return packageBuilder.installPackage(resourceResolver, distributionPackage);
}
@NotNull
@Override
public DistributionPackageInfo installPackage(@NotNull ResourceResolver resourceResolver, @NotNull InputStream stream) throws DistributionException {
return packageBuilder.installPackage(resourceResolver, stream);
}
/**
* (taken and adjusted from PropertiesUtil, because the handy toMap() function is not available as
* part of the metatype functionality
*
* @param values The path mappings.
* @param defaultArray The default array converted to map.
* @return Map value
*/
private static Map<String, String> toMap(String[] values, String[] defaultArray) {
if (values == null) {
values = defaultArray;
}
//in property values
Map<String, String> result = new LinkedHashMap<String, String>();
for (String kv : values) {
int indexOfEqual = kv.indexOf('=');
if (indexOfEqual > 0) {
String key = trimToNull(kv.substring(0, indexOfEqual));
String value = trimToNull(kv.substring(indexOfEqual + 1));
if (key != null) {
result.put(key, value);
}
}
}
return result;
}
private static String trimToNull(String str) {
String ts = trim(str);
return isEmpty(ts) ? null : ts;
}
private static String trim(String str){
return str == null ? null : str.trim();
}
private static boolean isEmpty(String str){
return str == null || str.length() == 0;
}
}