This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.jcr.repoinit-1.0.2 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-repoinit.git
commit 3fa0bab78247d746e44f8ccbc081732e13e764ad Author: Bertrand Delacretaz <[email protected]> AuthorDate: Tue Aug 16 14:28:28 2016 +0000 SLING-5943 - support multiple model/raw references in RepositoryInitializer configuration git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/jcr/repoinit@1756518 13f79535-47bb-0310-9956-ffa450edef68 --- .../jcr/repoinit/impl/RepoinitTextProvider.java | 141 +++++++++++++++++ .../jcr/repoinit/impl/RepositoryInitializer.java | 170 +++++---------------- .../jcr/repoinit/RepositoryInitializerTest.java | 38 +++-- .../jcr/repoinit/impl/RepoinitReferenceTest.java | 91 +++++++++++ 4 files changed, 294 insertions(+), 146 deletions(-) diff --git a/src/main/java/org/apache/sling/jcr/repoinit/impl/RepoinitTextProvider.java b/src/main/java/org/apache/sling/jcr/repoinit/impl/RepoinitTextProvider.java new file mode 100644 index 0000000..7b07636 --- /dev/null +++ b/src/main/java/org/apache/sling/jcr/repoinit/impl/RepoinitTextProvider.java @@ -0,0 +1,141 @@ +/* + * 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.jcr.repoinit.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.URL; +import java.net.URLConnection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.io.IOUtils; +import org.apache.sling.provisioning.model.Feature; +import org.apache.sling.provisioning.model.Model; +import org.apache.sling.provisioning.model.Section; +import org.apache.sling.provisioning.model.io.ModelReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Retrieves repoinit statements from URLs that return either + * raw repoinit text or Sling provisioning models that are parsed + * to extract the repoinit text. + * + * Uses references like + * + * <code>model@repoinit:context:/resources/provisioning/model</code>, + * + * meaning that the supplied context:/ URI returns a provisioning model + * containing repoinit statements in its "repoinit" additional section, or + * + * + * <code>raw:classpath://com.example.sling.repoinit/repoinit.txt</code> + * + * meaning that the supplied classpath: URI returns raw repoinit statements. + */ +public class RepoinitTextProvider { + public static enum TextFormat { raw, model }; + private static final String DEFAULT_MODEL_SECTION = "repoinit"; + + public static final Pattern REF_PATTERN = Pattern.compile("([a-z]+)(@([a-zA-Z0-9_-]+))?:(.*)"); + + private Logger log = LoggerFactory.getLogger(getClass()); + + static class Reference { + final TextFormat format; + final String modelSection; + final String url; + + Reference(String ref) { + if(ref == null) { + throw new IllegalArgumentException("Null reference"); + } + final Matcher m = REF_PATTERN.matcher(ref); + if(!m.matches()) { + throw new IllegalArgumentException("Invalid reference '" + ref + "', should match " + REF_PATTERN); + } + format = TextFormat.valueOf(m.group(1)); + if(format.equals(TextFormat.raw)) { + modelSection = null; + } else if(format.equals(TextFormat.model) && m.group(3) == null) { + modelSection = DEFAULT_MODEL_SECTION; + } else { + modelSection = m.group(3); + } + url = m.group(4); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName()).append(":"); + sb.append("format=").append(format); + if(modelSection != null) { + sb.append(", model section=").append(modelSection); + } + sb.append(", URL=").append(url); + return sb.toString(); + } + } + + public String getRepoinitText(String referenceString) throws IOException { + final Reference ref = new Reference(referenceString); + log.info("Reading repoinit statements from {}", ref); + final String rawText = getRawText(ref.url); + log.debug("Raw text from {}: \n{}", ref.url, rawText); + if(TextFormat.model.equals(ref.format)) { + log.debug("Extracting provisioning model section {}", ref.modelSection); + return extractFromModel(ref.url, rawText, ref.modelSection); + } else { + return rawText; + } + } + + private String extractFromModel(String sourceInfo, String rawText, String modelSection) throws IOException { + final StringReader reader = new StringReader(rawText); + final Model model = ModelReader.read(reader, sourceInfo); + final StringBuilder sb = new StringBuilder(); + if(modelSection == null) { + throw new IllegalStateException("Model section name is null, cannot read model"); + } + for (final Feature feature : model.getFeatures()) { + for (final Section section : feature.getAdditionalSections(modelSection)) { + sb.append("# ").append(modelSection).append(" from ").append(feature.getName()).append("\n"); + sb.append("# ").append(section.getComment()).append("\n"); + sb.append(section.getContents()).append("\n"); + } + } + return sb.toString(); + } + + private String getRawText(String urlString) throws IOException { + String result = ""; + final URL url = new URL(urlString); + final URLConnection c = url.openConnection(); + final InputStream is = c.getInputStream(); + if(is == null) { + log.warn("Cannot get InputStream for {}", url); + } else { + final StringWriter w = new StringWriter(); + IOUtils.copy(is, w, "UTF-8"); + result = w.toString(); + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/jcr/repoinit/impl/RepositoryInitializer.java b/src/main/java/org/apache/sling/jcr/repoinit/impl/RepositoryInitializer.java index 9b5c686..9dbab2c 100644 --- a/src/main/java/org/apache/sling/jcr/repoinit/impl/RepositoryInitializer.java +++ b/src/main/java/org/apache/sling/jcr/repoinit/impl/RepositoryInitializer.java @@ -16,34 +16,23 @@ */ package org.apache.sling.jcr.repoinit.impl; -import java.io.IOException; -import java.io.InputStream; import java.io.StringReader; -import java.io.StringWriter; -import java.net.URL; -import java.net.URLConnection; import java.util.Arrays; import java.util.List; import java.util.Map; import javax.jcr.Session; -import org.apache.commons.io.IOUtils; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Properties; import org.apache.felix.scr.annotations.Property; -import org.apache.felix.scr.annotations.PropertyOption; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.sling.commons.osgi.PropertiesUtil; import org.apache.sling.jcr.api.SlingRepository; import org.apache.sling.jcr.api.SlingRepositoryInitializer; import org.apache.sling.jcr.repoinit.JcrRepoInitOpsProcessor; -import org.apache.sling.provisioning.model.Feature; -import org.apache.sling.provisioning.model.Model; -import org.apache.sling.provisioning.model.Section; -import org.apache.sling.provisioning.model.io.ModelReader; import org.apache.sling.repoinit.parser.RepoInitParser; import org.apache.sling.repoinit.parser.operations.Operation; import org.osgi.framework.Constants; @@ -66,40 +55,18 @@ import org.slf4j.LoggerFactory; public class RepositoryInitializer implements SlingRepositoryInitializer { private final Logger log = LoggerFactory.getLogger(getClass()); - public static final String DEFAULT_TEXT_URL = "context:/resources/provisioning/model.txt"; + public static final String DEFAULT_REFERENCE = "model@repoinit:context:/resources/provisioning/model.txt"; @Property( - label="Text URL", - description="URL of the source text that provides repoinit statements.", - value=DEFAULT_TEXT_URL) - public static final String PROP_TEXT_URL = "text.url"; - private String textURL; - - public static final String DEFAULT_MODEL_SECTION_NAME = "repoinit"; - - @Property( - label="Model section name", - description= - "If using the provisioning model format, this specifies the additional section name (without leading colon) used to extract" - + " repoinit statements from the raw text provided by the configured source text URL.", - value=DEFAULT_MODEL_SECTION_NAME) - public static final String PROP_MODEL_SECTION_NAME = "model.section.name"; - private String modelSectionName; - - @Property( - label="Text format", + label="Repoinit references", description= - "The format to use to interpret the text provided by the configured source text URL. " - + "That text can be either a Sling provisioning model with repoinit statements embedded in additional sections," - + " or raw repoinit statements", - options = { - @PropertyOption(name = "MODEL", value = "Provisioning Model (MODEL)"), - @PropertyOption(name = "RAW", value = "Raw Repoinit statements (RAW)") - }, - value="MODEL") - public static final String PROP_TEXT_FORMAT = "text.format"; - public static enum TextFormat { RAW, MODEL }; - private TextFormat textFormat; + "References to the source text that provides repoinit statements." + + " format is either model@repoinit:<provisioning model URL> or raw:<raw URL>" + , + cardinality=Integer.MAX_VALUE, + value={ DEFAULT_REFERENCE }) + public static final String PROP_REFERENCES = "references"; + private String [] references; @Reference private RepoInitParser parser; @@ -109,106 +76,47 @@ public class RepositoryInitializer implements SlingRepositoryInitializer { @Activate public void activate(Map<String, Object> config) { - textURL = PropertiesUtil.toString(config.get(PROP_TEXT_URL), DEFAULT_TEXT_URL); - - final String fmt = PropertiesUtil.toString(config.get(PROP_TEXT_FORMAT), TextFormat.MODEL.toString()); - try { - textFormat = TextFormat.valueOf(fmt); - } catch(Exception e) { - throw new IllegalArgumentException("Invalid text format '" + fmt + "'," - + " valid values are " + Arrays.asList(TextFormat.values())); - } - - modelSectionName = PropertiesUtil.toString(config.get(PROP_MODEL_SECTION_NAME), DEFAULT_MODEL_SECTION_NAME); - + warnForOldConfigParameters(config); + references = PropertiesUtil.toStringArray(config.get(PROP_REFERENCES)); log.debug("Activated: {}", this.toString()); } + /** Some config parameters are not used anymore as of V1.0.2, this logs + * warnings if they are still used. + */ + private void warnForOldConfigParameters(Map<String, Object> config) { + final String [] names = { + "text.url", + "text.format", + "model.section.name" + }; + for(String name : names) { + if(config.containsKey(name)) { + log.warn("Configuration parameter '{}' is not used anymore, will be ignored", name); + } + } + } + @Override public String toString() { - final StringBuilder sb = new StringBuilder(); - sb.append(getClass().getSimpleName()).append(": "); - sb.append(PROP_TEXT_URL).append("=").append(textURL).append(", "); - sb.append(PROP_TEXT_FORMAT).append("=").append(textFormat).append(", "); - sb.append(PROP_MODEL_SECTION_NAME).append("=").append(modelSectionName); - return sb.toString(); + return getClass().getSimpleName() + ", references=" + Arrays.asList(references); } - + @Override public void processRepository(SlingRepository repo) throws Exception { - final String repoinit = getRepoInitText(); - - if(TextFormat.MODEL.equals(textFormat)) { - if(modelSectionName == null) { - throw new IllegalStateException("Section name is null, cannot read model"); - } - if(modelSectionName.trim().length() == 0) { - throw new IllegalStateException("Empty " + PROP_MODEL_SECTION_NAME + " is not supported anymore, please use " - + PROP_TEXT_FORMAT + " to specify the input text format"); - } - } - // loginAdministrative is ok here, definitely an admin operation final Session s = repo.loginAdministrative(null); try { - final List<Operation> ops = parser.parse(new StringReader(repoinit)); - log.info("Executing {} repoinit operations", ops.size()); - processor.apply(s, ops); - s.save(); + final RepoinitTextProvider p = new RepoinitTextProvider(); + for(String reference : references) { + final String repoinitText = p.getRepoinitText(reference); + final List<Operation> ops = parser.parse(new StringReader(repoinitText)); + log.info("Executing {} repoinit operations", ops.size()); + processor.apply(s, ops); + s.save(); + } } finally { s.logout(); } - } - - /** Get the repoinit statements to execute */ - private String getRawRepoInitText() { - String result = ""; - try { - final URL url = new URL(textURL); - final URLConnection c = url.openConnection(); - final InputStream is = c.getInputStream(); - if(is == null) { - log.warn("Cannot get InputStream for {}", url); - } else { - final StringWriter w = new StringWriter(); - IOUtils.copy(is, w, "UTF-8"); - result = w.toString(); - } - } catch(Exception e) { - log.warn("Error reading repoinit statements from " + textURL, e); - } - return result; - } - - private String getRepoInitText() { - final String rawText = getRawRepoInitText(); - log.debug("Raw text from {}: \n{}", textURL, rawText); - log.info("Got {} characters from {}", rawText.length(), textURL); - if(TextFormat.RAW.equals(textFormat)) { - log.info("Parsing raw repoinit statements from {}", textURL); - return rawText; - } else { - log.info("Extracting repoinit statements from section ':{}' of provisioning model {}", modelSectionName, textURL); - final StringReader reader = new StringReader(rawText); - try { - final Model model = ModelReader.read(reader, textURL); - final StringBuilder sb = new StringBuilder(); - if(modelSectionName == null) { - throw new IllegalStateException("Model section name is null, cannot read model"); - } - for (final Feature feature : model.getFeatures()) { - for (final Section section : feature.getAdditionalSections(modelSectionName)) { - sb.append("# ").append(modelSectionName).append(" from ").append(feature.getName()).append("\n"); - sb.append("# ").append(section.getComment()).append("\n"); - sb.append(section.getContents()).append("\n"); - } - } - return sb.toString(); - } catch (IOException e) { - log.warn("Error parsing provisioning model from " + textURL, e); - return ""; - } - } - } - -} + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/jcr/repoinit/RepositoryInitializerTest.java b/src/test/java/org/apache/sling/jcr/repoinit/RepositoryInitializerTest.java index 4081050..7167385 100644 --- a/src/test/java/org/apache/sling/jcr/repoinit/RepositoryInitializerTest.java +++ b/src/test/java/org/apache/sling/jcr/repoinit/RepositoryInitializerTest.java @@ -31,9 +31,10 @@ import java.util.UUID; import org.apache.sling.jcr.api.SlingRepository; import org.apache.sling.jcr.repoinit.impl.JcrRepoInitOpsProcessorImpl; +import org.apache.sling.jcr.repoinit.impl.RepoinitTextProvider.TextFormat; import org.apache.sling.jcr.repoinit.impl.RepositoryInitializer; -import org.apache.sling.jcr.repoinit.impl.RepositoryInitializer.TextFormat; import org.apache.sling.jcr.repoinit.impl.TestUtil; +import org.apache.sling.repoinit.parser.RepoInitParsingException; import org.apache.sling.repoinit.parser.impl.RepoInitParserService; import org.apache.sling.testing.mock.sling.ResourceResolverType; import org.apache.sling.testing.mock.sling.junit.SlingContext; @@ -70,18 +71,19 @@ public class RepositoryInitializerTest { final List<Object []> result = new ArrayList<Object[]>(); // Realistic cases - result.add(new Object[] { "Using provisioning model", "SECTION_" + UUID.randomUUID(), TextFormat.MODEL.toString(), true, true, null }); - result.add(new Object[] { "Default value of model section config", null, TextFormat.MODEL.toString(), true, true, null }); - result.add(new Object[] { "Raw repoinit/empty section", "", TextFormat.RAW.toString(), false, true, null }); - result.add(new Object[] { "Raw repoinit/ignored section name", "IGNORED_SectionName", TextFormat.RAW.toString(), false, true, null }); + result.add(new Object[] { "Using provisioning model", "SECTION_" + UUID.randomUUID(), TextFormat.model.toString(), true, true, null }); + result.add(new Object[] { "Default value of model section config", null, TextFormat.model.toString(), true, true, null }); + result.add(new Object[] { "Raw repoinit/empty section", "", TextFormat.raw.toString(), false, true, null }); + result.add(new Object[] { "Raw repoinit/ignored section name", "IGNORED_SectionName", TextFormat.raw.toString(), false, true, null }); // Edge and failure cases - result.add(new Object[] { "All empty, just setup + parsing", "", TextFormat.RAW.toString(), false, false, null }); - result.add(new Object[] { "Raw repoinit/null format", null, null, true, false, RuntimeException.class }); + result.add(new Object[] { "All empty, just setup + parsing", "", TextFormat.raw.toString(), false, false, null }); + result.add(new Object[] { "Raw repoinit/null format", null, null, true, false, RepoInitParsingException.class }); result.add(new Object[] { "With model/null format", null, null, false, false, RuntimeException.class }); result.add(new Object[] { "Invalid format", null, "invalidFormat", false, false, RuntimeException.class }); - result.add(new Object[] { "Empty model section", "", TextFormat.MODEL.toString(), false, false, IllegalStateException.class }); - result.add(new Object[] { "Null model section", null, TextFormat.MODEL.toString(), false, false, IllegalStateException.class }); + result.add(new Object[] { "Empty model section", "", TextFormat.model.toString(), false, false, IllegalArgumentException.class }); + result.add(new Object[] { "Null model section", null, TextFormat.model.toString(), false, false, IOException.class }); + return result; } @@ -109,14 +111,20 @@ public class RepositoryInitializerTest { initializer = new RepositoryInitializer(); config = new HashMap<String, Object>(); - config.put(RepositoryInitializer.PROP_TEXT_URL, url); - if(modelSection != null) { - config.put(RepositoryInitializer.PROP_MODEL_SECTION_NAME, modelSection); - } - if(textFormat != null) { - config.put(RepositoryInitializer.PROP_TEXT_FORMAT, textFormat); + + String ref = null; + if(TextFormat.model.toString().equals(textFormat)) { + if(modelSection != null) { + ref = "model@" + modelSection + ":" + url; + } else { + ref = "model:" + url; + } + } else { + ref = "raw:" + url; } + config.put(RepositoryInitializer.PROP_REFERENCES, new String[] { ref }); + context.registerInjectActivateService(new RepoInitParserService()); context.registerInjectActivateService(new JcrRepoInitOpsProcessorImpl()); diff --git a/src/test/java/org/apache/sling/jcr/repoinit/impl/RepoinitReferenceTest.java b/src/test/java/org/apache/sling/jcr/repoinit/impl/RepoinitReferenceTest.java new file mode 100644 index 0000000..5b6b775 --- /dev/null +++ b/src/test/java/org/apache/sling/jcr/repoinit/impl/RepoinitReferenceTest.java @@ -0,0 +1,91 @@ +/* + * 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.jcr.repoinit.impl; + +import static org.junit.Assert.fail; +import static org.junit.Assert.assertEquals; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.sling.jcr.repoinit.impl.RepoinitTextProvider.Reference; +import org.apache.sling.jcr.repoinit.impl.RepoinitTextProvider.TextFormat; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** Test the RepoinitTextProvider references parsing */ +@RunWith(Parameterized.class) +public class RepoinitReferenceTest { + + @Parameters(name="{0}") + public static Collection<Object[]> data() { + final List<Object []> result = new ArrayList<Object[]>(); + + // Valid references + result.add(new Object[] { "model@foo:uri:1234", TextFormat.model, "foo", "uri:1234", null }); + result.add(new Object[] { "model:uri:2345", TextFormat.model, "repoinit", "uri:2345", null }); + result.add(new Object[] { "raw:uri:4567", TextFormat.raw , null, "uri:4567", null }); + result.add(new Object[] { "raw:uri@5678", TextFormat.raw, null, "uri@5678", null }); + + // Invalid references + result.add(new Object[] { "model@foo", null, null, null, IllegalArgumentException.class }); + result.add(new Object[] { "model#foo:url", TextFormat.model, "repoinit", "url", IllegalArgumentException.class }); + result.add(new Object[] { "", null, null, null, IllegalArgumentException.class }); + result.add(new Object[] { null, null, null, null, IllegalArgumentException.class }); + result.add(new Object[] { "foo:url", null, null, null, IllegalArgumentException.class }); + + // foo is ignored, by design + result.add(new Object[] { "raw@foo:url", TextFormat.raw, null, "url", null }); + + return result; + } + + private final String input; + private final RepoinitTextProvider.TextFormat format; + private final String modelSection; + private final String url; + private final Class<?> expectedException; + + public RepoinitReferenceTest(String input, TextFormat format, String modelSection, String url, Class<? >expectedException) { + this.input = input; + this.format = format; + this.modelSection = modelSection; + this.url = url; + this.expectedException = expectedException; + } + + @Test + public void testParsing() { + try { + final Reference ref = new Reference(input); + if(expectedException != null) { + fail("Expected a " + expectedException.getName()); + } + assertEquals(format, ref.format); + assertEquals(modelSection, ref.modelSection); + assertEquals(url, ref.url); + } catch(Exception e) { + if(expectedException != null) { + assertEquals(expectedException, e.getClass()); + } else { + fail("Unexpected " + e); + } + } + } +} \ No newline at end of file -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
