| /* |
| * 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.settings.impl; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.Serializable; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.regex.Pattern; |
| |
| import org.apache.sling.settings.SlingSettingsService; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.service.component.annotations.Activate; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.Modified; |
| import org.osgi.service.component.propertytypes.ServiceDescription; |
| import org.osgi.service.metatype.annotations.AttributeDefinition; |
| import org.osgi.service.metatype.annotations.Designate; |
| import org.osgi.service.metatype.annotations.ObjectClassDefinition; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** This is the basic implementation of the sling settings service. */ |
| @Component |
| @Designate(ocd = SlingSettingsServiceImpl.Configuration.class) |
| @ServiceDescription("Apache Sling Settings Service") |
| public class SlingSettingsServiceImpl |
| implements SlingSettingsService { |
| |
| /** The logger */ |
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
| |
| /** The sling instance id. */ |
| private String slingId; |
| |
| /** The sling home */ |
| private String slingHome; |
| |
| /** The sling home url */ |
| private URL slingHomeUrl; |
| |
| /** The set of run modes . */ |
| private Set<String> runModes; |
| |
| private Configuration configuration; |
| |
| /** The name of the data file holding the sling id. */ |
| private static final String ID_FILE = "sling.id.file"; |
| |
| /** The name of the data file holding install run mode options */ |
| private static final String OPTIONS_FILE = "sling.options.file"; |
| |
| @ObjectClassDefinition(id = "org.apache.sling.settings.impl.SlingSettingsServiceImpl", name = "Apache Sling Settings Service", |
| description = "The settings service manages some basic settings of Sling like run modes or information about the current instance.") |
| static @interface Configuration { |
| @AttributeDefinition(name = "Instance Name", description = "A human readable name for the current instance.") |
| String sling_name(); |
| |
| @AttributeDefinition(name = "Instance Description", description = "A human readable description for the current instance.") |
| String sling_description(); |
| } |
| |
| /** Create the service and search the Sling home urls and get/create a sling id. Setup run modes |
| * |
| * @param context The bundle context */ |
| @Activate |
| public SlingSettingsServiceImpl(final Configuration configuration, final BundleContext context) { |
| this.configuration = configuration; |
| this.setupSlingHome(context); |
| this.setupSlingId(context); |
| this.setupRunModes(context); |
| } |
| |
| /** |
| * Constructor only to be used from tests |
| * @param runModes |
| */ |
| public SlingSettingsServiceImpl(String runModes) { |
| this.runModes = parseRunModes(runModes); |
| } |
| |
| /** Get sling home and sling home URL */ |
| private void setupSlingHome(final BundleContext context) { |
| this.slingHome = context.getProperty(SLING_HOME); |
| final String url = context.getProperty(SLING_HOME_URL); |
| if (url != null) { |
| try { |
| this.slingHomeUrl = new URL(url); |
| } catch (MalformedURLException e) { |
| logger.error("Sling home url is not a url: {}", url); |
| } |
| } |
| } |
| |
| /** Get / create sling id */ |
| private void setupSlingId(final BundleContext context) { |
| // try to read the id from the id file first |
| final File idFile = context.getDataFile(ID_FILE); |
| if (idFile == null) { |
| // the osgi framework does not support storing something in the file system |
| throw new RuntimeException("Unable to read from bundle data file."); |
| } |
| |
| try { |
| slingId = SlingIdUtil.readSlingId(idFile); |
| logger.info("Read Sling ID {} from file {}", slingId, idFile); |
| } catch (final Throwable t) { |
| logger.error("Failed reading Sling ID from file " + idFile, t); |
| } |
| |
| // no sling id yet or failure to read file: create an id and store |
| if (slingId == null) { |
| slingId = SlingIdUtil.createSlingId(); |
| logger.info("Created new Sling ID {}", slingId); |
| try { |
| SlingIdUtil.writeSlingId(idFile, slingId); |
| } catch (final Throwable t) { |
| logger.error("Failed writing Sling ID to file " + idFile, t); |
| } |
| } |
| } |
| |
| static final class Options implements Serializable { |
| private static final long serialVersionUID = 1L; |
| String[] modes; |
| String selected; |
| } |
| |
| private List<Options> handleOptions(final Set<String> modesSet, final String propOptions) { |
| final List<Options> optionsList = new ArrayList<Options>(); |
| if (propOptions != null && propOptions.trim().length() > 0) { |
| |
| final String[] options = propOptions.trim().split("\\|"); |
| for (final String opt : options) { |
| String selected = null; |
| final String[] modes = opt.trim().split(","); |
| for (int i = 0; i < modes.length; i++) { |
| modes[i] = modes[i].trim(); |
| if (selected != null) { |
| modesSet.remove(modes[i]); |
| } else { |
| if (modesSet.contains(modes[i])) { |
| selected = modes[i]; |
| } |
| } |
| } |
| if (selected == null) { |
| selected = modes[0]; |
| modesSet.add(modes[0]); |
| } |
| final Options o = new Options(); |
| o.selected = selected; |
| o.modes = modes; |
| optionsList.add(o); |
| } |
| } |
| return optionsList; |
| } |
| |
| private Set<String> parseRunModes(String runModes) { |
| final Set<String> modesSet = new HashSet<>(); |
| final String[] modes = runModes.split(","); |
| for (int i = 0; i < modes.length; i++) { |
| modesSet.add(modes[i].trim()); |
| } |
| return modesSet; |
| } |
| |
| /** Set up run modes. */ |
| private void setupRunModes(final BundleContext context) { |
| final Set<String> modesSet; |
| |
| // check configuration property first |
| final String prop = context.getProperty(RUN_MODES_PROPERTY); |
| if (prop != null && prop.trim().length() > 0) { |
| modesSet = parseRunModes(prop); |
| } else { |
| modesSet = new HashSet<>(); |
| } |
| |
| // handle configured options |
| this.handleOptions(modesSet, context.getProperty(RUN_MODE_OPTIONS)); |
| |
| // handle configured install options |
| // read persisted options if restart or update |
| final List<Options> storedOptions = readOptions(context); |
| if (storedOptions != null) { |
| for (final Options o : storedOptions) { |
| for (final String m : o.modes) { |
| modesSet.remove(m); |
| } |
| modesSet.add(o.selected); |
| } |
| } |
| |
| // now install options |
| final List<Options> optionsList = this.handleOptions(modesSet, context.getProperty(RUN_MODE_INSTALL_OPTIONS)); |
| // and always save new install options |
| writeOptions(context, optionsList); |
| |
| // make the set unmodifiable and synced |
| // we probably don't need a synced set as it is read only |
| this.runModes = Collections.synchronizedSet(Collections.unmodifiableSet(modesSet)); |
| if (this.runModes.size() > 0) { |
| logger.info("Active run modes: {}", this.runModes); |
| } else { |
| logger.info("No run modes active"); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private List<Options> readOptions(final BundleContext context) { |
| List<Options> optionsList = null; |
| final File file = context.getDataFile(OPTIONS_FILE); |
| if (file.exists()) { |
| FileInputStream fis = null; |
| ObjectInputStream ois = null; |
| try { |
| fis = new FileInputStream(file); |
| ois = new ObjectInputStream(fis); |
| |
| optionsList = (List<Options>) ois.readObject(); |
| } catch (final IOException ioe) { |
| throw new RuntimeException("Unable to read from options data file.", ioe); |
| } catch (ClassNotFoundException cnfe) { |
| throw new RuntimeException("Unable to read from options data file.", cnfe); |
| } finally { |
| if (ois != null) { |
| try { |
| ois.close(); |
| } catch (final IOException ignore) { |
| } |
| } |
| if (fis != null) { |
| try { |
| fis.close(); |
| } catch (final IOException ignore) { |
| } |
| } |
| } |
| } |
| return optionsList; |
| } |
| |
| void writeOptions(final BundleContext context, final List<Options> optionsList) { |
| final File file = context.getDataFile(OPTIONS_FILE); |
| FileOutputStream fos = null; |
| ObjectOutputStream oos = null; |
| try { |
| fos = new FileOutputStream(file); |
| oos = new ObjectOutputStream(fos); |
| oos.writeObject(optionsList); |
| } catch (final IOException ioe) { |
| throw new RuntimeException("Unable to write to options data file.", ioe); |
| } finally { |
| if (oos != null) { |
| try { |
| oos.close(); |
| } catch (final IOException ignore) { |
| } |
| } |
| if (fos != null) { |
| try { |
| fos.close(); |
| } catch (final IOException ignore) { |
| } |
| } |
| } |
| } |
| |
| /** @see org.apache.sling.settings.SlingSettingsService#getAbsolutePathWithinSlingHome(String) */ |
| @Override |
| public String getAbsolutePathWithinSlingHome(final String relativePath) { |
| return new File(slingHome, relativePath).getAbsolutePath(); |
| } |
| |
| /** @see org.apache.sling.settings.SlingSettingsService#getSlingId() */ |
| @Override |
| public String getSlingId() { |
| return this.slingId; |
| } |
| |
| /** @see org.apache.sling.settings.SlingSettingsService#getSlingHome() */ |
| @Override |
| public URL getSlingHome() { |
| return this.slingHomeUrl; |
| } |
| |
| /** @see org.apache.sling.settings.SlingSettingsService#getSlingHomePath() */ |
| @Override |
| public String getSlingHomePath() { |
| return this.slingHome; |
| } |
| |
| /** @see org.apache.sling.settings.SlingSettingsService#getRunModes() */ |
| @Override |
| public Set<String> getRunModes() { |
| return this.runModes; |
| } |
| |
| @Override |
| public int getBestRunModeMatchCountFromSpec(String spec) { |
| return getBestRunModeMatchCountFromSpec(spec, runModes); |
| } |
| |
| static int getBestRunModeMatchCountFromSpec(String spec, Collection<String> activeRunModes) { |
| int numMatchingRunModes = 0; |
| // 1. support OR |
| for (String discjunctivePart : spec.split(Pattern.quote(RUN_MODE_SPEC_OR_SEPARATOR))) { |
| int newNumMatchingRunModes = getBestRunModeMatchCountFromConjunctions(discjunctivePart, activeRunModes); |
| if (newNumMatchingRunModes > numMatchingRunModes) { |
| numMatchingRunModes = newNumMatchingRunModes; |
| } |
| } |
| return numMatchingRunModes; |
| } |
| |
| static int getBestRunModeMatchCountFromConjunctions(String conjunctions, Collection<String> activeRunModes) { |
| int numMatchingRunModes = 0; |
| // 2. support AND |
| for (String conjunctivePart : conjunctions.split(Pattern.quote(RUN_MODE_SPEC_AND_SEPARATOR))) { |
| // 3. support NOT operator |
| if (conjunctivePart.startsWith(RUN_MODE_SPEC_NOT_PREFIX)) { |
| if (activeRunModes.contains(conjunctivePart.substring(RUN_MODE_SPEC_NOT_PREFIX.length()))) { |
| return 0; |
| } |
| } else { |
| if (!activeRunModes.contains(conjunctivePart)) { |
| return 0; |
| } |
| } |
| numMatchingRunModes++; |
| } |
| return numMatchingRunModes; |
| } |
| |
| /** |
| * @see org.apache.sling.settings.SlingSettingsService#getSlingName() |
| */ |
| @Override |
| public String getSlingName() { |
| String name = configuration.sling_name(); |
| if ( name == null ) { |
| name = "Instance " + this.slingId; // default |
| } |
| return name; |
| } |
| |
| /** @see org.apache.sling.settings.SlingSettingsService#getSlingDescription() */ |
| @Override |
| public String getSlingDescription() { |
| String desc = configuration.sling_description(); |
| if (desc == null) { |
| desc = "Instance with id " + this.slingId + " and run modes " + this.getRunModes(); // default |
| } |
| return desc; |
| } |
| |
| /** Update the configuration of this service */ |
| @Modified |
| public void update(final Configuration configuration) { |
| // TODO is configuration thread safe i.e. new object per call? |
| this.configuration = configuration; |
| } |
| } |