This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.resourcebuilder-1.0.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourcebuilder.git
commit ae44b20a796f96f0846e44042795495d31ae1d66 Author: Bertrand Delacretaz <bdelacre...@apache.org> AuthorDate: Wed Dec 9 16:58:52 2015 +0000 SLING-5356 - ResourceBuilder, work in progress git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/commons/resourcebuilder@1718908 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 85 +++++++++ .../sling/resourcebuilder/api/ResourceBuilder.java | 66 +++++++ .../api/ResourceBuilderProvider.java | 30 ++++ .../resourcebuilder/impl/MapArgsConverter.java | 43 +++++ .../resourcebuilder/impl/ResourceBuilderImpl.java | 143 +++++++++++++++ .../impl/ResourceBuilderImplTest.java | 199 +++++++++++++++++++++ 6 files changed, 566 insertions(+) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b51e328 --- /dev/null +++ b/pom.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>25</version> + <relativePath/> + </parent> + + <artifactId>org.apache.sling.commons.resourcebuilder</artifactId> + <version>0.0.1-SNAPSHOT</version> + <packaging>bundle</packaging> + + <name>Apache Sling Commons Content Builder</name> + <description>Utilities to create Sling content</description> + + <scm> + <connection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/commons/resourcebuilder</connection> + <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/commons/resourcebuilder</developerConnection> + <url>https://svn.apache.org/repos/asf/sling/trunk/bundles/commons/resourcebuilder</url> + </scm> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.api</artifactId> + <version>2.3.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.testing.sling-mock</artifactId> + <version>1.6.0</version> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/src/main/java/org/apache/sling/resourcebuilder/api/ResourceBuilder.java b/src/main/java/org/apache/sling/resourcebuilder/api/ResourceBuilder.java new file mode 100644 index 0000000..7d43ec3 --- /dev/null +++ b/src/main/java/org/apache/sling/resourcebuilder/api/ResourceBuilder.java @@ -0,0 +1,66 @@ +/* + * 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.resourcebuilder.api; + +import org.apache.sling.api.resource.Resource; + +/** Builds Sling Resources using a simple fluent API */ +public interface ResourceBuilder { + + public static final String DEFAULT_PRIMARY_TYPE = "nt:unstructured"; + + /** Create a Resource, which optionally becomes the current + * parent Resource. + * @param relativePath The path of the Resource to create, relative to + * this builder's current parent Resource. + * @param properties optional name-value pairs + * @return this builder + */ + ResourceBuilder resource(String relativePath, Object ... properties); + + /** Commit created resources */ + ResourceBuilder commit(); + + /** Set the primary type for intermediate resources created + * when the parent of resource being created does not exist. + * @param primaryType If null the DEFAULT_PRIMARY_TYPE is used. + * @return this builder + */ + ResourceBuilder withIntermediatePrimaryType(String primaryType); + + /** Set siblings mode (as opposed to hierarchy mode) where creating a resource + * doesn't change the current parent. Used to create flat structures. + * This is off by default. + * @return this builder + */ + ResourceBuilder siblingsMode(); + + /** Set hierarchy mode (as opposed to siblings mode) where creating a resource + * sets it as the current parent. Used to create tree structures. + * This is on by default. + * @return this builder + */ + ResourceBuilder hierarchyMode(); + + /** Return the current parent resource */ + Resource getCurrentParent(); + + /** Reset the current parent Resource to the original one */ + ResourceBuilder resetParent(); +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/resourcebuilder/api/ResourceBuilderProvider.java b/src/main/java/org/apache/sling/resourcebuilder/api/ResourceBuilderProvider.java new file mode 100644 index 0000000..379547e --- /dev/null +++ b/src/main/java/org/apache/sling/resourcebuilder/api/ResourceBuilderProvider.java @@ -0,0 +1,30 @@ +/* + * 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.resourcebuilder.api; + +import org.apache.sling.api.resource.Resource; + +/** A service that provides ResourceBuilders */ +public interface ResourceBuilderProvider { + + /** Provides a ResourceBuilder to create resources + * under the supplied parent Resource. + */ + ResourceBuilder getResourceBuilder(Resource parent); +} diff --git a/src/main/java/org/apache/sling/resourcebuilder/impl/MapArgsConverter.java b/src/main/java/org/apache/sling/resourcebuilder/impl/MapArgsConverter.java new file mode 100644 index 0000000..433dfb2 --- /dev/null +++ b/src/main/java/org/apache/sling/resourcebuilder/impl/MapArgsConverter.java @@ -0,0 +1,43 @@ +/* + * 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.resourcebuilder.impl; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** Convert arguments which are a list of Object to a Map, used + * to simplify our builder's syntax. + */ +public class MapArgsConverter { + + /** Convert an args list to a Map */ + public static Map<String, Object> toMap(Object ... args) { + if(args.length % 2 != 0) { + throw new IllegalArgumentException("args must be an even number of name/values:" + Arrays.asList(args)); + } + final Map<String, Object> result = new HashMap<String, Object>(); + for(int i=0 ; i < args.length; i+=2) { + result.put(args[i].toString(), args[i+1]); + } + return Collections.unmodifiableMap(result); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/resourcebuilder/impl/ResourceBuilderImpl.java b/src/main/java/org/apache/sling/resourcebuilder/impl/ResourceBuilderImpl.java new file mode 100644 index 0000000..4e8899a --- /dev/null +++ b/src/main/java/org/apache/sling/resourcebuilder/impl/ResourceBuilderImpl.java @@ -0,0 +1,143 @@ +/* + * 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.resourcebuilder.impl; + +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.resourcebuilder.api.ResourceBuilder; + +/** ResourceBuilder implementation */ +public class ResourceBuilderImpl implements ResourceBuilder { + private final Resource originalParent; + private final ResourceResolver resourceResolver; + private Resource currentParent; + private String intermediatePrimaryType; + private boolean hierarchyMode; + + public static final String JCR_PRIMARYTYPE = "jcr:primaryType"; + + public ResourceBuilderImpl(Resource parent) { + if(parent == null) { + throw new IllegalArgumentException("Parent resource is null"); + } + originalParent = parent; + resourceResolver = originalParent.getResourceResolver(); + withIntermediatePrimaryType(null); + resetParent(); + hierarchyMode(); + } + + @Override + public Resource getCurrentParent() { + return currentParent; + } + + @Override + public ResourceBuilder resetParent() { + currentParent = originalParent; + return this; + } + + @Override + public ResourceBuilder resource(String relativePath, Object... properties) { + Resource created = null; + if(relativePath.startsWith("/")) { + throw new IllegalArgumentException("Path is not relative:" + relativePath); + } + + final String fullPath = currentParent.getPath() + "/" + relativePath; + final String parentPath = ResourceUtil.getParent(fullPath); + final Resource myParent = ensureResourceExists(parentPath); + + try { + created = currentParent.getResourceResolver().create(myParent, + ResourceUtil.getName(relativePath), MapArgsConverter.toMap(properties)); + } catch(PersistenceException pex) { + throw new RuntimeException( + "PersistenceException while creating Resource " + relativePath + + " under " + currentParent.getPath(), pex); + } + + if(created == null) { + throw new RuntimeException("Failed to created resource " + relativePath + + " under " + currentParent.getPath()); + } else if(hierarchyMode) { + currentParent = created; + } + return this; + } + + /** Create a Resource at the specified path if none exists yet, + * using the current intermediate primary type. "Stolen" from + * the sling-mock module's ContentBuilder class. + * @param path Resource path + * @return Resource at path (existing or newly created) + */ + protected final Resource ensureResourceExists(String path) { + if(path == null || path.length() == 0 || path.equals("/")) { + return resourceResolver.getResource("/"); + } + Resource resource = resourceResolver.getResource(path); + if (resource != null) { + return resource; + } + String parentPath = ResourceUtil.getParent(path); + String name = ResourceUtil.getName(path); + Resource parentResource = ensureResourceExists(parentPath); + try { + resource = resourceResolver.create( + parentResource, + name, + MapArgsConverter.toMap(JCR_PRIMARYTYPE, intermediatePrimaryType)); + return resource; + } catch (PersistenceException ex) { + throw new RuntimeException("Unable to create intermediate resource at " + path, ex); + } + } + + @Override + public ResourceBuilder withIntermediatePrimaryType(String primaryType) { + intermediatePrimaryType = primaryType == null ? DEFAULT_PRIMARY_TYPE : primaryType; + return this; + } + + @Override + public ResourceBuilder siblingsMode() { + hierarchyMode = false; + return this; + } + + @Override + public ResourceBuilder hierarchyMode() { + hierarchyMode = true; + return this; + } + + @Override + public ResourceBuilder commit() { + try { + resourceResolver.commit(); + } catch (PersistenceException ex) { + throw new RuntimeException("Unable to commit", ex); + } + return this; + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/resourcebuilder/impl/ResourceBuilderImplTest.java b/src/test/java/org/apache/sling/resourcebuilder/impl/ResourceBuilderImplTest.java new file mode 100644 index 0000000..a1f6c70 --- /dev/null +++ b/src/test/java/org/apache/sling/resourcebuilder/impl/ResourceBuilderImplTest.java @@ -0,0 +1,199 @@ +/* + * 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.resourcebuilder.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Map; +import java.util.UUID; + +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class ResourceBuilderImplTest { + + private String testRootPath; + private ResourceResolver resourceResolver; + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.RESOURCERESOLVER_MOCK); + + private ResourceBuilderImpl getBuilder(String path) throws PersistenceException { + final Resource root = context.resourceResolver().resolve("/"); + assertNotNull("Expecting non-null root", root); + return new ResourceBuilderImpl(resourceResolver.create(root, ResourceUtil.getName(path), null)); + } + + @Before + public void setup() { + testRootPath = "/" + UUID.randomUUID().toString(); + resourceResolver = context.resourceResolver(); + } + + private Resource assertResource(String path) { + final Resource result = resourceResolver.resolve(fullPath(path)); + assertNotNull("Expecting resource to exist:" + path, result); + return result; + } + + private String fullPath(String path) { + return path.startsWith("/") ? path : testRootPath + "/" + path; + } + + private void assertProperties(String path, Object ...props) { + final Map<String, Object> expected = MapArgsConverter.toMap(props); + final Resource r = assertResource(path); + final ValueMap vm = r.adaptTo(ValueMap.class); + for(Map.Entry<String, Object> e : expected.entrySet()) { + final Object value = vm.get(e.getKey()); + assertNotNull("Expecting property " + e.getKey() + " for resource " + r.getPath()); + assertEquals( + "Expecting value " + e.getValue() + + " for property " + e.getKey() + " of resource " + r.getPath() + , e.getValue(), value); + } + } + + @Test + public void basicResource() throws PersistenceException { + getBuilder(testRootPath) + .resource("child", "title", "foo") + .commit(); + + assertProperties("child", "title", "foo"); + assertEquals(fullPath("child"), assertResource("child").getPath()); + } + + @Test + public void ensureResourceExists() throws PersistenceException { + + class MyResourceBuilder extends ResourceBuilderImpl { + MyResourceBuilder() { + super(resourceResolver.getResource("/")); + } + + Resource r(String path) { + return ensureResourceExists(path); + } + }; + final MyResourceBuilder b = new MyResourceBuilder(); + + assertEquals("/", b.r(null).getPath()); + assertEquals("/", b.r("").getPath()); + assertEquals("/", b.r("/").getPath()); + } + + @Test + public void deepResource() throws PersistenceException { + getBuilder(testRootPath) + .resource("a/b/c", "title", "foo") + .commit(); + + assertProperties("a/b/c", "title", "foo"); + assertEquals(fullPath("a/b/c"), assertResource("a/b/c").getPath()); + assertResource("a/b"); + assertResource("a"); + } + + @Test + public void intermediatePrimaryTypes() throws PersistenceException { + getBuilder(testRootPath) + .resource("a/b/c") + .withIntermediatePrimaryType("foo") + .resource("d/e") + .withIntermediatePrimaryType(null) + .resource("f/g") + .commit(); + + assertProperties("a/b", ResourceBuilderImpl.JCR_PRIMARYTYPE, "nt:unstructured"); + assertProperties("a/b/c/d", ResourceBuilderImpl.JCR_PRIMARYTYPE, "foo"); + assertProperties("a/b/c/d/e/f", ResourceBuilderImpl.JCR_PRIMARYTYPE, "nt:unstructured"); + } + + @Test + public void resetParent() throws PersistenceException { + getBuilder(testRootPath) + .resource("a/b/c") + .resetParent() + .resource("d/e") + .commit(); + + assertResource("a/b/c"); + assertResource("d/e"); + } + + @Test + public void noResetParent() throws PersistenceException { + getBuilder(testRootPath) + .resource("a/b/c") + .resource("d/e") + .commit(); + + assertResource("a/b/c"); + assertResource("a/b/c/d/e"); + } + + @Test + public void getParent() throws PersistenceException { + final Resource parent = getBuilder(testRootPath).getCurrentParent(); + assertNotNull(parent); + assertEquals(testRootPath, parent.getPath()); + } + + @Test(expected=RuntimeException.class) + public void missingParentFails() throws PersistenceException { + new ResourceBuilderImpl(null).resource("foo"); + } + + @Test(expected=IllegalArgumentException.class) + public void absolutePathFails() throws PersistenceException { + getBuilder(testRootPath).resource("/absolute"); + } + + @Test + public void buildATree() throws PersistenceException { + getBuilder(testRootPath) + .resource("a/b/c", "title", "foo") + .siblingsMode() + .resource("1") + .resource("2") + .resource("3") + .hierarchyMode() + .resource("with") + .resource("more/here", "it", "worked") + .resource("deepest", "it", "worked") + .commit(); + + assertProperties("a/b/c", "title", "foo"); + assertProperties("a/b/c/with/more/here", "it", "worked"); + assertResource("a/b/c/with/more/here/deepest"); + assertResource("a/b/c/1"); + assertResource("a/b/c/2"); + assertResource("a/b/c/3"); + } +} -- To stop receiving notification emails like this one, please contact "commits@sling.apache.org" <commits@sling.apache.org>.