blob: 6b7559ba12710b72c0e0c6c281cd5d9443bd8dc0 [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.installer.factories.configuration.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.apache.sling.installer.api.InstallableResource;
import org.apache.sling.installer.api.ResourceChangeListener;
import org.apache.sling.installer.api.info.InfoProvider;
import org.apache.sling.installer.api.info.Resource;
import org.apache.sling.installer.api.info.ResourceGroup;
import org.apache.sling.installer.api.tasks.ChangeStateTask;
import org.apache.sling.installer.api.tasks.InstallTask;
import org.apache.sling.installer.api.tasks.InstallTaskFactory;
import org.apache.sling.installer.api.tasks.RegisteredResource;
import org.apache.sling.installer.api.tasks.ResourceState;
import org.apache.sling.installer.api.tasks.ResourceTransformer;
import org.apache.sling.installer.api.tasks.TaskResource;
import org.apache.sling.installer.api.tasks.TaskResourceGroup;
import org.apache.sling.installer.api.tasks.TransformationResult;
import org.apache.sling.installer.factories.configuration.ConfigurationConstants;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationEvent;
import org.osgi.service.cm.ConfigurationListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Task creator for configurations.
*/
public class ConfigTaskCreator
implements InstallTaskFactory, ConfigurationListener, ResourceTransformer {
/** Logger. */
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/** Configuration admin. */
private final ConfigurationAdmin configAdmin;
/** Resource change listener. */
private final ResourceChangeListener changeListener;
/** Info Provider */
private final InfoProvider infoProvider;
public ConfigTaskCreator(final ResourceChangeListener listener,
final ConfigurationAdmin configAdmin,
final InfoProvider infoProvider) {
this.changeListener = listener;
this.configAdmin = configAdmin;
this.infoProvider = infoProvider;
}
public ServiceRegistration<?> register(final BundleContext bundleContext) {
final Hashtable<String, String> props = new Hashtable<>();
props.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Configuration Install Task Factory");
props.put(Constants.SERVICE_VENDOR, ServicesListener.VENDOR);
props.put(InstallTaskFactory.NAME, "org.osgi.service.cm");
props.put(ResourceTransformer.NAME, "org.osgi.service.cm");
final String [] serviceInterfaces = {
InstallTaskFactory.class.getName(),
ConfigurationListener.class.getName(),
ResourceTransformer.class.getName()
};
final ServiceRegistration<?> reg = bundleContext.registerService(serviceInterfaces, this, props);
this.logger.info("OSGi Configuration support for OSGi installer active, default location={}, merge schemes={}",
Activator.DEFAULT_LOCATION, Activator.MERGE_SCHEMES);
return reg;
}
/**
* Create a task to install or uninstall a configuration.
*
* @see org.apache.sling.installer.api.tasks.InstallTaskFactory#createTask(org.apache.sling.installer.api.tasks.TaskResourceGroup)
*/
@Override
public InstallTask createTask(final TaskResourceGroup group) {
final TaskResource toActivate = group.getActiveResource();
if ( !toActivate.getType().equals(InstallableResource.TYPE_CONFIG) ) {
return null;
}
final InstallTask result;
if (toActivate.getState() == ResourceState.UNINSTALL) {
// if this is an uninstall, check if we have to install an older version
// in this case we should do an update instead of uninstall/install (!)
final TaskResource second = group.getNextActiveResource();
if ( second != null
&& ( second.getState() == ResourceState.IGNORED || second.getState() == ResourceState.INSTALLED || second.getState() == ResourceState.INSTALL )
&& ( second.getDictionary() == null || second.getDictionary().get(InstallableResource.RESOURCE_IS_TEMPLATE) == null)) {
result = new ChangeStateTask(group, ResourceState.UNINSTALLED);
} else {
result = new ConfigRemoveTask(group, this.configAdmin);
}
} else {
result = new ConfigInstallTask(group, this.configAdmin);
}
return result;
}
/**
* @see org.osgi.service.cm.ConfigurationListener#configurationEvent(org.osgi.service.cm.ConfigurationEvent)
*/
@Override
public void configurationEvent(final ConfigurationEvent event) {
synchronized ( Coordinator.SHARED ) {
if ( event.getType() == ConfigurationEvent.CM_DELETED ) {
final Coordinator.Operation op = Coordinator.SHARED.get(event.getPid(), event.getFactoryPid(), true);
if ( op == null ) {
this.changeListener.resourceRemoved(InstallableResource.TYPE_CONFIG, event.getPid());
} else {
this.logger.debug("Ignoring configuration event for {}:{}", event.getPid(), event.getFactoryPid());
}
} else if ( event.getType() == ConfigurationEvent.CM_UPDATED ) {
try {
// we just need to pass in the pid as we're using named factory configs
final Configuration config = ConfigUtil.getConfiguration(configAdmin,
null,
event.getPid());
final Coordinator.Operation op = Coordinator.SHARED.get(event.getPid(), event.getFactoryPid(), false);
if ( config != null && op == null ) {
final boolean persist = ConfigUtil.toBoolean(config.getProperties().get(ConfigurationConstants.PROPERTY_PERSISTENCE), true);
final Dictionary<String, Object> dict = ConfigUtil.cleanConfiguration(config.getProperties());
final Map<String, Object> attrs = new HashMap<>();
if ( !persist ) {
attrs.put(ResourceChangeListener.RESOURCE_PERSIST, Boolean.FALSE);
}
attrs.put(Constants.SERVICE_PID, event.getPid());
attrs.put(InstallableResource.RESOURCE_URI_HINT, event.getPid());
if ( config.getBundleLocation() != null ) {
attrs.put(InstallableResource.INSTALLATION_HINT, config.getBundleLocation());
}
// Factory?
if (event.getFactoryPid() != null) {
attrs.put(ConfigurationAdmin.SERVICE_FACTORYPID, event.getFactoryPid());
}
removeDefaultProperties(event.getPid(), dict);
this.changeListener.resourceAddedOrUpdated(InstallableResource.TYPE_CONFIG, event.getPid(), null, dict, attrs);
} else {
this.logger.debug("Ignoring configuration event for {}:{}", event.getPid(), event.getFactoryPid());
}
} catch ( final Exception ignore) {
// ignore for now
}
}
}
}
private void removeDefaultProperties(final String pid, final Dictionary<String, Object> dict) {
if ( Activator.MERGE_SCHEMES != null ) {
final List<Dictionary<String, Object>> propertiesList = new ArrayList<>();
final String entityId = InstallableResource.TYPE_CONFIG.concat(":").concat(pid);
boolean done = false;
for(final ResourceGroup group : this.infoProvider.getInstallationState().getInstalledResources()) {
for(final Resource rsrc : group.getResources()) {
if ( rsrc.getEntityId().equals(entityId) ) {
done = true;
if ( Activator.MERGE_SCHEMES.contains(rsrc.getScheme()) ) {
propertiesList.add(rsrc.getDictionary());
}
}
}
if ( done ) {
break;
}
}
if ( !propertiesList.isEmpty() ) {
final Dictionary<String, Object> defaultProps = ConfigUtil.mergeReverseOrder(propertiesList);
final Enumeration<String> keyEnum = defaultProps.keys();
while ( keyEnum.hasMoreElements() ) {
final String key = keyEnum.nextElement();
final Object value = defaultProps.get(key);
final Object newValue = dict.get(key);
if ( newValue != null && ConfigUtil.isSameValue(newValue, value)) {
dict.remove(key);
}
}
}
}
}
/**
* @see org.apache.sling.installer.api.tasks.ResourceTransformer#transform(org.apache.sling.installer.api.tasks.RegisteredResource)
*/
@Override
public TransformationResult[] transform(final RegisteredResource resource) {
if ( resource.getType().equals(InstallableResource.TYPE_PROPERTIES) ) {
return checkConfiguration(resource);
}
return null;
}
private static String getResourceId(final String rawUrl) {
final String url = separatorsToUnix(rawUrl);
int pos = url.lastIndexOf('/');
if ( pos == -1 ) {
pos = url.indexOf(':');
}
final String lastIdPart;
if ( pos != -1 ) {
lastIdPart = url.substring(pos + 1);
} else {
lastIdPart = url;
}
return lastIdPart;
}
/**
* Check if the registered resource is a configuration
* @param resource The resource
*/
private TransformationResult[] checkConfiguration(final RegisteredResource resource) {
final String lastIdPart = getResourceId(resource.getURL());
// remove extension if known
final String pid = removeConfigExtension(lastIdPart);
// split pid and factory pid alias
final Map<String, Object> attr = new HashMap<>();
final String factoryPid;
final String configPid;
int n = pid.indexOf('~');
if ( n == -1 ) {
n = pid.indexOf('-');
}
if (n > 0) {
configPid = pid.substring(n + 1);
factoryPid = pid.substring(0, n);
attr.put(ConfigurationAdmin.SERVICE_FACTORYPID, factoryPid);
} else {
factoryPid = null;
configPid = pid;
}
attr.put(Constants.SERVICE_PID, configPid);
final TransformationResult tr = new TransformationResult();
final String id = (factoryPid == null ? configPid : ConfigUtil.getPIDOfFactoryPID(factoryPid, configPid));
tr.setId(id);
tr.setResourceType(InstallableResource.TYPE_CONFIG);
tr.setAttributes(attr);
return new TransformationResult[] {tr};
}
private static final List<String> EXTENSIONS = Arrays.asList(".config", ".properties", ".cfg", ".cfg.json");
private static String removeConfigExtension(final String id) {
for(final String ext : EXTENSIONS) {
if ( id.endsWith(ext) ) {
return id.substring(0, id.length() - ext.length());
}
}
return id;
}
/**
* Converts all separators to the Unix separator of forward slash.
*
* @param path the path to be changed, null ignored
* @return the updated path
*/
private static String separatorsToUnix(final String path) {
if (path == null || path.indexOf('\\') == -1) {
return path;
}
return path.replace('\\', '/');
}
}