| /* |
| * 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('\\', '/'); |
| } |
| } |