This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourcebuilder.git
commit a3e05facc03919f2c21a32918bde2b5fc9ddecc7 Author: Bertrand Delacretaz <[email protected]> AuthorDate: Wed Jan 6 13:59:59 2016 +0000 SLING-5356 - move resourcebuilder under bundles/extensions git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1723329 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 255 ++++++++++++++++ .../sling/resourcebuilder/api/ResourceBuilder.java | 102 +++++++ .../resourcebuilder/impl/MapArgsConverter.java | 43 +++ .../resourcebuilder/impl/ResourceBuilderImpl.java | 261 ++++++++++++++++ .../impl/ResourceBuilderService.java | 110 +++++++ .../customizers/RBIT_TeleporterCustomizer.java | 46 +++ .../resourcebuilder/impl/MapArgsConverterTest.java | 49 +++ .../impl/ResourceBuilderImplTest.java | 335 +++++++++++++++++++++ .../sling/resourcebuilder/it/FileRetrievalIT.java | 93 ++++++ .../resourcebuilder/it/ResourceBuilderIT.java | 127 ++++++++ .../sling/resourcebuilder/it/TestEnvironment.java | 57 ++++ .../resourcebuilder/test/ResourceAssertions.java | 136 +++++++++ src/test/resources/files/models.js | 1 + src/test/resources/files/myapp.json | 4 + src/test/resources/files/text.html | 3 + 15 files changed, 1622 insertions(+) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..427019b --- /dev/null +++ b/pom.xml @@ -0,0 +1,255 @@ +<?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>26</version> + <relativePath/> + </parent> + + <artifactId>org.apache.sling.resourcebuilder</artifactId> + <version>0.0.1-SNAPSHOT</version> + <packaging>bundle</packaging> + + <name>Apache Sling Resource Builder</name> + <description>Utilities to create Sling content</description> + + <properties> + <!-- Set this to run the server on a specific port + <http.port></http.port> + --> + + <!-- Set this to run tests against an existing server instance --> + <keepJarRunning>false</keepJarRunning> + + <!-- + Options for the VM that executes our runnable jar. + Set debugging options here to debug teleported tests. + --> + <jar.executor.vm.options>-Xmx512m</jar.executor.vm.options> + + <!-- Options for the jar to execute. $JAREXEC_SERVER_PORT$ is replaced by the + selected port number --> + <jar.executor.jar.options>-p $JAREXEC_SERVER_PORT$</jar.executor.jar.options> + </properties> + <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> + <plugin> + <groupId>org.apache.rat</groupId> + <artifactId>apache-rat-plugin</artifactId> + <configuration> + <excludes> + <exclude>src/test/resources/**</exclude> + <exclude>sling/**</exclude> + </excludes> + </configuration> + </plugin> + <plugin> + <!-- Find free ports to run our server --> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <executions> + <execution> + <id>reserve-server-port</id> + <goals> + <goal>reserve-network-port</goal> + </goals> + <phase>process-resources</phase> + <configuration> + <portNames> + <portName>http.port</portName> + </portNames> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-clean-plugin</artifactId> + <configuration> + <filesets> + <fileset> + <directory>${basedir}</directory> + <includes> + <!-- sling folder is the workdir of the executable jar that we test --> + <include>sling/**</include> + </includes> + </fileset> + </filesets> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <executions> + <execution> + <id>integration-test</id> + <goals> + <goal>integration-test</goal> + </goals> + </execution> + <execution> + <id>verify</id> + <goals> + <goal>verify</goal> + </goals> + </execution> + </executions> + <configuration> + <systemPropertyVariables> + <!-- these are the minimal options required for the jar executor, see bundle-with-it module for more --> + <keepJarRunning>${keepJarRunning}</keepJarRunning> + <jar.executor.jar.options>${jar.executor.jar.options}</jar.executor.jar.options> + <jar.executor.vm.options>${jar.executor.vm.options}</jar.executor.vm.options> + <jar.executor.server.port>${http.port}</jar.executor.server.port> + <additional.bundles.path>${project.build.directory}</additional.bundles.path> + <server.ready.path.1>/:script src="system/sling.js"</server.ready.path.1> + <server.ready.path.2>/.explorer.html:href="/libs/sling/explorer/css/explorer.css"</server.ready.path.2> + <server.ready.path.3>/sling-test/sling/sling-test.html:Sling client library tests</server.ready.path.3> + + <!-- Additional bundles to install for testing --> + <sling.additional.bundle.1>org.apache.sling.junit.core</sling.additional.bundle.1> + <sling.additional.bundle.2>${project.artifactId}-${project.version}.jar</sling.additional.bundle.2> + </systemPropertyVariables> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>copy-runnable-jar</id> + <goals> + <goal>copy-dependencies</goal> + </goals> + <phase>process-resources</phase> + <configuration> + <includeArtifactIds>org.apache.sling.launchpad</includeArtifactIds> + <excludeTransitive>true</excludeTransitive> + <overWriteReleases>false</overWriteReleases> + <overWriteSnapshots>false</overWriteSnapshots> + </configuration> + </execution> + <execution> + <!-- + Consider all dependencies as candidates to be installed + as additional bundles. We use system properties to define + which bundles to install in which order. + --> + <id>copy-additional-bundles</id> + <goals> + <goal>copy-dependencies</goal> + </goals> + <phase>process-resources</phase> + <configuration> + <outputDirectory>${project.build.directory}</outputDirectory> + <excludeTransitive>true</excludeTransitive> + <overWriteReleases>false</overWriteReleases> + <overWriteSnapshots>false</overWriteSnapshots> + </configuration> + </execution> + </executions> + </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.commons.mime</artifactId> + <version>2.1.2</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.4</version> + <scope>test</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> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.junit.core</artifactId> + <version>1.0.14</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.testing.tools</artifactId> + <version>1.0.10</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.junit.teleporter</artifactId> + <!-- SNAPSHOT required due to SLING-5365 --> + <version>1.0.5-SNAPSHOT</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.launchpad</artifactId> + <version>8</version> + <scope>provided</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..1a1f965 --- /dev/null +++ b/src/main/java/org/apache/sling/resourcebuilder/api/ResourceBuilder.java @@ -0,0 +1,102 @@ +/* + * 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 java.io.InputStream; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; + +import aQute.bnd.annotation.ProviderType; + +/** Builds Sling Resources using a simple fluent API */ +@ProviderType +public interface ResourceBuilder { + + /** Default primary type for resources created by this builder */ + public static final String DEFAULT_PRIMARY_TYPE = "nt:unstructured"; + + /** Start a ResourceBuilder using the supplied parent resource + * @return the new builder + * */ + ResourceBuilder forParent(Resource parent); + + /** Start a ResourceBuilder using the supplied ResourceResolver, + * starting with the root resource as the builder's parent resource. + * @return the new builder + * */ + ResourceBuilder forResolver(ResourceResolver r); + + /** 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); + + /** Create a file under the current parent resource + * @param filename The name of the created file + * @param data The file data + * @param mimeType If null, use the Sling MimeTypeService to set the mime type + * @param lastModified if < 0, current time is used + * @return this builder + */ + ResourceBuilder file(String filename, InputStream data, String mimeType, long lastModified); + + /** Create a file under the current parent resource. Mime type is set using the + * Sling MimeTypeService, and last modified is set to current time. + * @param filename The name of the created file + * @param data The file data + * @return this builder + */ + ResourceBuilder file(String filename, InputStream data); + + /** 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. + * Also activates hierarchyMode which is the default mode. */ + ResourceBuilder atParent(); +} \ No newline at end of file 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..48b9be9 --- /dev/null +++ b/src/main/java/org/apache/sling/resourcebuilder/impl/ResourceBuilderImpl.java @@ -0,0 +1,261 @@ +/* + * 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.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.api.resource.ModifiableValueMap; +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.commons.mime.MimeTypeService; +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 static final String JCR_MIMETYPE = "jcr:mimeType"; + public static final String JCR_LASTMODIFIED = "jcr:lastModified"; + public static final String JCR_DATA = "jcr:data"; + public static final String JCR_CONTENT = "jcr:content"; + public static final String NT_RESOURCE = "nt:resource"; + public static final String NT_FILE = "nt:file"; + + public static final String CANNOT_RESTART = + "Cannot reset the parent resource or resource resolver, please create a new " + + "builder using the ResourceBuilder service"; + + private final MimeTypeService mimeTypeService; + + public ResourceBuilderImpl(Resource parent, MimeTypeService mts) { + mimeTypeService = mts; + if(parent == null) { + throw new IllegalArgumentException("Parent resource is null"); + } + originalParent = parent; + resourceResolver = originalParent.getResourceResolver(); + withIntermediatePrimaryType(null); + atParent(); + } + + @Override + public ResourceBuilder forParent(Resource parent) { + throw new UnsupportedOperationException(CANNOT_RESTART); + } + + @Override + public ResourceBuilder forResolver(ResourceResolver v) { + throw new UnsupportedOperationException(CANNOT_RESTART); + } + + @Override + public Resource getCurrentParent() { + return currentParent; + } + + @Override + public ResourceBuilder atParent() { + currentParent = originalParent; + hierarchyMode(); + return this; + } + + private void checkRelativePath(String relativePath) { + if(relativePath.startsWith("/")) { + throw new IllegalArgumentException("Path is not relative:" + relativePath); + } + if(relativePath.contains("..")) { + throw new IllegalArgumentException("Path contains invalid pattern '..': " + relativePath); + } + } + + private String parentPath(String relativePath) { + final String parentPath = currentParent.getPath(); + final String fullPath = + parentPath.endsWith("/") ? + parentPath + relativePath : + parentPath + "/" + relativePath; + return ResourceUtil.getParent(fullPath); + } + + @Override + public ResourceBuilder resource(String relativePath, Object... properties) { + Resource r = null; + checkRelativePath(relativePath); + final String parentPath = parentPath(relativePath); + final Resource myParent = ensureResourceExists(parentPath); + final String fullPath = currentParent.getPath() + "/" + relativePath; + + try { + r = currentParent.getResourceResolver().getResource(fullPath); + final Map<String, Object> props = MapArgsConverter.toMap(properties); + if(r == null) { + r = currentParent.getResourceResolver().create(myParent, + ResourceUtil.getName(relativePath), props); + } else { + // Resource exists, set our properties + final ModifiableValueMap mvm = r.adaptTo(ModifiableValueMap.class); + if(mvm == null) { + throw new IllegalStateException("Cannot modify properties of " + r.getPath()); + } + for(Map.Entry <String, Object> e : props.entrySet()) { + mvm.put(e.getKey(), e.getValue()); + } + } + } catch(PersistenceException pex) { + throw new RuntimeException( + "PersistenceException while creating Resource " + relativePath + + " under " + currentParent.getPath(), pex); + } + + if(r == null) { + throw new RuntimeException("Failed to get or create resource " + relativePath + + " under " + currentParent.getPath()); + } else if(hierarchyMode) { + currentParent = r; + } + 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); + } + } + + protected String getMimeType(String filename, String userSuppliedMimeType) { + if(userSuppliedMimeType != null) { + return userSuppliedMimeType; + } + return mimeTypeService.getMimeType(filename); + } + + protected long getLastModified(long userSuppliedValue) { + if(userSuppliedValue < 0) { + return System.currentTimeMillis(); + } + return userSuppliedValue; + } + + @Override + public ResourceBuilder file(String relativePath, InputStream data, String mimeType, long lastModified) { + checkRelativePath(relativePath); + final String name = ResourceUtil.getName(relativePath); + if(data == null) { + throw new IllegalArgumentException("Data is null for file " + name); + } + + Resource file = null; + final ResourceResolver resolver = currentParent.getResourceResolver(); + final String parentPath = parentPath(relativePath); + + final Resource parent = ensureResourceExists(parentPath); + try { + final String fullPath = currentParent.getPath() + "/" + name; + if(resolver.getResource(fullPath) != null) { + throw new IllegalStateException("Resource already exists:" + fullPath); + } + final Map<String, Object> fileProps = new HashMap<String, Object>(); + fileProps.put(JCR_PRIMARYTYPE, NT_FILE); + file = resolver.create(parent, name, fileProps); + + final Map<String, Object> contentProps = new HashMap<String, Object>(); + contentProps.put(JCR_PRIMARYTYPE, NT_RESOURCE); + contentProps.put(JCR_MIMETYPE, getMimeType(name, mimeType)); + contentProps.put(JCR_LASTMODIFIED, getLastModified(lastModified)); + contentProps.put(JCR_DATA, data); + resolver.create(file, JCR_CONTENT, contentProps); + } catch(PersistenceException pex) { + throw new RuntimeException("Unable to create file under " + currentParent.getPath(), pex); + } + + if(file == null) { + throw new RuntimeException("Unable to get or created file resource " + relativePath + " under " + currentParent.getPath()); + } + if(hierarchyMode) { + currentParent = file; + } + + return this; + } + + @Override + public ResourceBuilder file(String filename, InputStream data) { + return file(filename, data, null, -1); + } + + @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/main/java/org/apache/sling/resourcebuilder/impl/ResourceBuilderService.java b/src/main/java/org/apache/sling/resourcebuilder/impl/ResourceBuilderService.java new file mode 100644 index 0000000..87f68db --- /dev/null +++ b/src/main/java/org/apache/sling/resourcebuilder/impl/ResourceBuilderService.java @@ -0,0 +1,110 @@ +/* + * 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.io.InputStream; + +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.commons.mime.MimeTypeService; +import org.apache.sling.resourcebuilder.api.ResourceBuilder; + +/** ResourceBuilder service, only implements the for* methods to + * create new builders. This allows us to provide a single service + * interface which transparently becomes stateful, by switching from + * this object to the ResourceBuilderImpl. + */ +@Component +@Service(value=ResourceBuilder.class) +public class ResourceBuilderService implements ResourceBuilder { + + @Reference + private MimeTypeService mimeTypeService; + + + private ResourceBuilder notStarted() { + throw new IllegalStateException( + "This ResourceBuilder is not started, please use the" + + "forParent or forResolver methods to start it." + ); + } + + @Override + public ResourceBuilder forParent(Resource parent) { + return new ResourceBuilderImpl(parent, mimeTypeService); + } + + @Override + public ResourceBuilder forResolver(ResourceResolver r) { + final Resource root = r.getResource("/"); + if(root == null) { + throw new IllegalStateException("Cannot read root resource"); + } + return forParent(root); + } + + @Override + public ResourceBuilder resource(String relativePath, Object... properties) { + return notStarted(); + } + + @Override + public ResourceBuilder file(String filename, InputStream data, String mimeType, long lastModified) { + return notStarted(); + } + + @Override + public ResourceBuilder file(String filename, InputStream data) { + return notStarted(); + } + + @Override + public ResourceBuilder commit() { + return notStarted(); + } + + @Override + public ResourceBuilder withIntermediatePrimaryType(String primaryType) { + return notStarted(); + } + + @Override + public ResourceBuilder siblingsMode() { + return notStarted(); + } + + @Override + public ResourceBuilder hierarchyMode() { + return notStarted(); + } + + @Override + public Resource getCurrentParent() { + notStarted(); + return null; + } + + @Override + public ResourceBuilder atParent() { + return notStarted(); + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/junit/teleporter/customizers/RBIT_TeleporterCustomizer.java b/src/test/java/org/apache/sling/junit/teleporter/customizers/RBIT_TeleporterCustomizer.java new file mode 100644 index 0000000..765923b --- /dev/null +++ b/src/test/java/org/apache/sling/junit/teleporter/customizers/RBIT_TeleporterCustomizer.java @@ -0,0 +1,46 @@ +/* + * 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.junit.teleporter.customizers; + +import org.apache.sling.junit.rules.TeleporterRule; +import org.apache.sling.resourcebuilder.api.ResourceBuilder; +import org.apache.sling.testing.teleporter.client.ClientSideTeleporter; +import org.apache.sling.testing.tools.sling.SlingTestBase; +import org.apache.sling.testing.tools.sling.TimeoutsProvider; + +import aQute.bnd.osgi.Constants; + +/** Setup the ClientSideTeleporter for our integration tests. + */ +public class RBIT_TeleporterCustomizer implements TeleporterRule.Customizer { + + private final static SlingTestBase S = new SlingTestBase(); + + @Override + public void customize(TeleporterRule t, String options) { + final ClientSideTeleporter cst = (ClientSideTeleporter)t; + cst.setBaseUrl(S.getServerBaseUrl()); + cst.setServerCredentials(S.getServerUsername(), S.getServerPassword()); + cst.setTestReadyTimeoutSeconds(TimeoutsProvider.getInstance().getTimeout(5)); + + // Make sure our bundle API is imported instead of embedded + final String apiPackage = ResourceBuilder.class.getPackage().getName(); + cst.includeDependencyPrefix("org.apache.sling.resourcebuilder"); + cst.excludeDependencyPrefix(apiPackage); + cst.getAdditionalBundleHeaders().put(Constants.IMPORT_PACKAGE, apiPackage); + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/resourcebuilder/impl/MapArgsConverterTest.java b/src/test/java/org/apache/sling/resourcebuilder/impl/MapArgsConverterTest.java new file mode 100644 index 0000000..ec1a411 --- /dev/null +++ b/src/test/java/org/apache/sling/resourcebuilder/impl/MapArgsConverterTest.java @@ -0,0 +1,49 @@ +/* + * 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.assertTrue; + +import java.util.Map; + +import org.apache.sling.api.resource.PersistenceException; +import org.junit.Test; + +public class MapArgsConverterTest { + + @Test + public void validArguments() throws PersistenceException { + final Map<String, Object> m = MapArgsConverter.toMap("foo", "bar", "count", 21); + assertEquals(2, m.size()); + assertEquals("bar", m.get("foo")); + assertEquals(21, m.get("count")); + } + + @Test + public void noArguments() throws PersistenceException { + final Map<String, Object> m = MapArgsConverter.toMap(); + assertTrue(m.isEmpty()); + } + + @Test(expected=IllegalArgumentException.class) + public void barArguments() throws PersistenceException { + MapArgsConverter.toMap("foo", "bar", "count"); + } +} 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..608a813 --- /dev/null +++ b/src/test/java/org/apache/sling/resourcebuilder/impl/ResourceBuilderImplTest.java @@ -0,0 +1,335 @@ +/* + * 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 static org.junit.Assert.fail; + +import java.util.Random; +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.commons.mime.MimeTypeService; +import org.apache.sling.resourcebuilder.test.ResourceAssertions; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.apache.sling.testing.mock.sling.services.MockMimeTypeService; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class ResourceBuilderImplTest { + + private String testRootPath; + private ResourceResolver resourceResolver; + private long lastModified; + private Random random = new Random(System.currentTimeMillis()); + private static final MimeTypeService mimeTypeService = new MockMimeTypeService(); + private ResourceAssertions A; + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.RESOURCERESOLVER_MOCK); + + private Resource getTestRoot(String path) throws PersistenceException { + final Resource root = context.resourceResolver().resolve("/"); + assertNotNull("Expecting non-null root", root); + return resourceResolver.create(root, ResourceUtil.getName(path), null); + } + + private ResourceBuilderImpl getBuilder(String path) throws Exception { + lastModified = random.nextLong(); + + final Resource parent = getTestRoot(path); + final ResourceBuilderImpl result = new ResourceBuilderImpl(parent, mimeTypeService) { + @Override + protected long getLastModified(long userSuppliedValue) { + final long now = System.currentTimeMillis(); + final long superValue = super.getLastModified(-1); + final long maxDelta = 60 * 1000L; + if(superValue < now || superValue - now > maxDelta) { + fail("getLastModified does not seem to use current time as its default value"); + } + + if(userSuppliedValue >= 0) { + return super.getLastModified(userSuppliedValue); + } + return lastModified; + } + }; + return result; + } + + @Before + public void setup() { + testRootPath = "/" + UUID.randomUUID().toString(); + resourceResolver = context.resourceResolver(); + A = new ResourceAssertions(testRootPath, resourceResolver); + } + + @After + public void cleanup() throws PersistenceException { + final Resource r = resourceResolver.getResource(testRootPath); + if(r != null) { + resourceResolver.delete(r); + resourceResolver.commit(); + } + } + + @Test + public void basicResource() throws Exception { + getBuilder(testRootPath) + .resource("child", "title", "foo") + .commit(); + + A.assertProperties("child", "title", "foo"); + assertEquals(A.fullPath("child"), A.assertResource("child").getPath()); + } + + @Test + public void ensureResourceExists() throws Exception { + + class MyResourceBuilder extends ResourceBuilderImpl { + MyResourceBuilder() { + super(resourceResolver.getResource("/"), null); + } + + 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 Exception { + getBuilder(testRootPath) + .resource("a/b/c", "title", "foo") + .commit(); + + A.assertProperties("a/b/c", "title", "foo"); + assertEquals(A.fullPath("a/b/c"), A.assertResource("a/b/c").getPath()); + A.assertResource("a/b"); + A.assertResource("a"); + } + + @Test + public void intermediatePrimaryTypes() throws Exception { + getBuilder(testRootPath) + .resource("a/b/c") + .withIntermediatePrimaryType("foo") + .resource("d/e") + .withIntermediatePrimaryType(null) + .resource("f/g") + .commit(); + + A.assertProperties("a/b", ResourceBuilderImpl.JCR_PRIMARYTYPE, "nt:unstructured"); + A.assertProperties("a/b/c/d", ResourceBuilderImpl.JCR_PRIMARYTYPE, "foo"); + A.assertProperties("a/b/c/d/e/f", ResourceBuilderImpl.JCR_PRIMARYTYPE, "nt:unstructured"); + } + + @Test + public void resetParent() throws Exception { + getBuilder(testRootPath) + .resource("a/b/c") + .siblingsMode() + .resource("one") + .resource("two") + .atParent() // also sets hierarchyMode + .resource("d/e") + .resource("f/g") + .siblingsMode() + .resource("three") + .resource("four") + .commit(); + + A.assertResource("a/b/c"); + A.assertResource("a/b/c/one"); + A.assertResource("a/b/c/two"); + A.assertResource("d/e"); + A.assertResource("d/e/f/g"); + A.assertResource("d/e/f/g/three"); + A.assertResource("d/e/f/g/four"); + } + + @Test + public void noResetParent() throws Exception { + getBuilder(testRootPath) + .resource("a/b/c") + .resource("d/e") + .commit(); + + A.assertResource("a/b/c"); + A.assertResource("a/b/c/d/e"); + } + + @Test + public void getParent() throws Exception { + final Resource parent = getBuilder(testRootPath).getCurrentParent(); + assertNotNull(parent); + assertEquals(testRootPath, parent.getPath()); + } + + @Test(expected=RuntimeException.class) + public void missingParentFails() throws Exception { + new ResourceBuilderImpl(null, null).resource("foo"); + } + + @Test(expected=IllegalArgumentException.class) + public void absolutePathFails() throws Exception { + getBuilder(testRootPath).resource("/absolute"); + } + + @Test(expected=IllegalArgumentException.class) + public void aboveParentFails() throws Exception { + getBuilder(testRootPath).resource("../foo"); + } + + @Test(expected=IllegalArgumentException.class) + public void aboveParentFailsFile() throws Exception { + getBuilder(testRootPath).file("../foo.js", null); + } + + @Test + public void simpleTree() throws Exception { + getBuilder(testRootPath) + .resource("a/b/c", "title", "foo", "count", 21) + .siblingsMode() + .resource("1") + .resource("2") + .resource("3") + .hierarchyMode() + .resource("with") + .resource("more/here", "it", "worked") + .resource("deepest", "it", "worked") + .commit(); + + A.assertProperties("a/b/c", "count", 21, "title", "foo"); + A.assertProperties("a/b/c/with/more/here", "it", "worked"); + A.assertResource("a/b/c/with/more/here/deepest"); + A.assertResource("a/b/c/1"); + A.assertResource("a/b/c/2"); + A.assertResource("a/b/c/3"); + } + + @Test + public void treeWithFiles() throws Exception { + getBuilder(testRootPath) + .resource("apps/myapp/components/resource") + .siblingsMode() + .file("models.js", getClass().getResourceAsStream("/files/models.js"), "MT1", 42) + .file("text.html", getClass().getResourceAsStream("/files/text.html"), "MT2", 43) + .atParent() + .file("apps/myapp.json", getClass().getResourceAsStream("/files/myapp.json"), "MT3", 44) + .atParent() + .resource("apps/content/myapp/resource") + .atParent() + .resource("apps/content", "title", "foo") + .file("myapp.json", getClass().getResourceAsStream("/files/myapp.json"), "MT4", 45) + .commit() + ; + + A.assertResource("apps/content/myapp/resource"); + A.assertResource("apps/myapp/components/resource"); + A.assertProperties("apps/content", "title", "foo"); + + A.assertFile("apps/myapp/components/resource/models.js", + "MT1", "function someJavascriptFunction()", 42L); + A.assertFile("apps/myapp/components/resource/text.html", + "MT2", "This is an html file", 43L); + A.assertFile("apps/myapp.json", + "MT3", "\"sling:resourceType\":\"its/resource/type\"", 44L); + A.assertFile("apps/content/myapp.json", + "MT4", "\"sling:resourceType\":\"its/resource/type\"", 45L); + } + + @Test + public void autoMimetype() throws Exception { + getBuilder(testRootPath) + .file("models.js", getClass().getResourceAsStream("/files/models.js"), null, 42) + .commit() + ; + A.assertFile("models.js", + "application/javascript", "function someJavascriptFunction()", 42L); + } + + @Test + public void autoLastModified() throws Exception { + getBuilder(testRootPath) + .file("models.js", getClass().getResourceAsStream("/files/models.js"), "MT1", -1) + .commit() + ; + A.assertFile("models.js", + "MT1", "function someJavascriptFunction()", lastModified); + } + + @Test + public void autoEverything() throws Exception { + getBuilder(testRootPath) + .file("a/b/c/models.js", getClass().getResourceAsStream("/files/models.js")) + .commit() + ; + A.assertFile("a/b/c/models.js", + "application/javascript", "function someJavascriptFunction()", lastModified); + } + + @Test(expected=IllegalStateException.class) + public void duplicatedFileFails() throws Exception { + getBuilder(testRootPath) + .siblingsMode() + .file("models.js", getClass().getResourceAsStream("/files/models.js"), null, 42) + .file("models.js", getClass().getResourceAsStream("/files/models.js"), null, 42) + ; + } + + @Test(expected=IllegalArgumentException.class) + public void nullDataFails() throws Exception { + getBuilder(testRootPath) + .file("models.js", null, null, 42) + ; + } + + @Test + public void forParent() throws PersistenceException { + new ResourceBuilderService() + .forParent(getTestRoot(testRootPath)) + .resource("a/b/c") + .commit(); + A.assertResource("a/b/c"); + } + + @Test + public void forResolver() throws PersistenceException { + new ResourceBuilderService() + .forResolver(resourceResolver) + .resource("d/e/f") + .commit(); + + // Resource is created at root in this case + A.assertResource("/d/e/f"); + } +} diff --git a/src/test/java/org/apache/sling/resourcebuilder/it/FileRetrievalIT.java b/src/test/java/org/apache/sling/resourcebuilder/it/FileRetrievalIT.java new file mode 100644 index 0000000..1991c27 --- /dev/null +++ b/src/test/java/org/apache/sling/resourcebuilder/it/FileRetrievalIT.java @@ -0,0 +1,93 @@ +/* + * 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.it; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.ServletException; + +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceMetadata; +import org.apache.sling.junit.rules.TeleporterRule; +import org.apache.sling.resourcebuilder.test.ResourceAssertions; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +/** Verify that our file structure is correct, + * by creating a file and retrieving it via + * a Sling request. + */ +public class FileRetrievalIT { + + @Rule + public final TeleporterRule teleporter = + TeleporterRule + .forClass(getClass(), "RBIT_Teleporter") + .withResources("/files/"); + + private TestEnvironment E; + private ResourceAssertions A; + + @Before + public void setup() throws LoginException, PersistenceException { + E = new TestEnvironment(teleporter); + A = new ResourceAssertions(E.testRootPath, E.resolver); + } + + @After + public void cleanup() throws PersistenceException { + E.cleanup(); + } + + @Test + public void createAndeRtrieveFile() throws IOException, ServletException { + final String expected = "yes, it worked"; + final long startTime = System.currentTimeMillis(); + final String mimeType = "application/javascript"; + + E.builder + .resource("somefolder") + .file("the-model.js", getClass().getResourceAsStream("/files/models.js")) + .commit(); + + final Resource r = A.assertFile("somefolder/the-model.js", mimeType, expected, -1L); + + final ResourceMetadata meta = r.getResourceMetadata(); + assertTrue("Expecting a last modified time >= startTime", meta.getModificationTime() >= startTime); + assertEquals("Expecting the correct mime-type", mimeType, meta.getContentType()); + + final InputStream is = r.adaptTo(InputStream.class); + assertNotNull("Expecting InputStream for file resource " + r.getPath(), is); + try { + final String content = A.readFully(is); + assertTrue("Expecting [" + expected + "] in content", content.contains(expected)); + } finally { + is.close(); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/resourcebuilder/it/ResourceBuilderIT.java b/src/test/java/org/apache/sling/resourcebuilder/it/ResourceBuilderIT.java new file mode 100644 index 0000000..b869e41 --- /dev/null +++ b/src/test/java/org/apache/sling/resourcebuilder/it/ResourceBuilderIT.java @@ -0,0 +1,127 @@ +/* + * 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.it; + +import static org.junit.Assert.fail; +import java.io.IOException; +import java.util.Comparator; + +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.junit.rules.TeleporterRule; +import org.apache.sling.resourcebuilder.test.ResourceAssertions; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +/** Server-side integration test for the + * ResourceBuilder, acquired via the ResourceBuilderProvider + */ +public class ResourceBuilderIT { + + @Rule + public final TeleporterRule teleporter = + TeleporterRule + .forClass(getClass(), "RBIT_Teleporter") + .withResources("/files/"); + + private TestEnvironment E; + private ResourceAssertions A; + + @Before + public void setup() throws LoginException, PersistenceException { + E = new TestEnvironment(teleporter); + A = new ResourceAssertions(E.testRootPath, E.resolver); + } + + @After + public void cleanup() throws PersistenceException { + E.cleanup(); + } + + + @Test + public void simpleResource() { + E.builder + .resource("foo", "title", E.testRootPath) + .commit(); + A.assertProperties("foo", "title", E.testRootPath); + } + + @Test + public void smallTreeWithFile() throws IOException { + E.builder + .resource("somefolder") + .file("the-model.js", getClass().getResourceAsStream("/files/models.js"), "foo", 42L) + .commit(); + + A.assertFile("somefolder/the-model.js", "foo", "yes, it worked", 42L); + } + + @Test + public void fileAutoValues() throws IOException { + final long startTime = System.currentTimeMillis(); + E.builder + .resource("a/b/c") + .file("model2.js", getClass().getResourceAsStream("/files/models.js")) + .commit(); + + final Comparator<Long> moreThanStartTime = new Comparator<Long>() { + @Override + public int compare(Long expected, Long fromResource) { + if(fromResource >= startTime) { + return 0; + } + fail("last-modified is not >= than current time:" + fromResource + " < " + startTime); + return -1; + } + }; + + A.assertFile("a/b/c/model2.js", "application/javascript", "yes, it worked", startTime, moreThanStartTime); + } + + @Test + public void usingResolver() throws IOException { + E.builderService.forResolver(E.resolver).resource("foo/a/b").commit(); + E.builderService.forResolver(E.resolver).resource("foo/c/d").commit(); + A.assertResource("/foo/a/b"); + A.assertResource("/foo/c/d"); + } + + @Test(expected=UnsupportedOperationException.class) + public void restartFailsA() throws IOException { + E.builder.forParent(E.resolver.getResource("/")); + } + + @Test(expected=UnsupportedOperationException.class) + public void restartFailsB() throws IOException { + E.builder.forResolver(E.resolver); + } + + @Test(expected=IllegalStateException.class) + public void notStartedFailsA() throws IOException { + E.builderService.resource("foo"); + } + + @Test(expected=IllegalStateException.class) + public void notStartedFailsB() throws IOException { + E.builderService.file("foo", null); + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/resourcebuilder/it/TestEnvironment.java b/src/test/java/org/apache/sling/resourcebuilder/it/TestEnvironment.java new file mode 100644 index 0000000..4749587 --- /dev/null +++ b/src/test/java/org/apache/sling/resourcebuilder/it/TestEnvironment.java @@ -0,0 +1,57 @@ +/* + * 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.it; + +import java.util.UUID; + +import org.apache.sling.api.resource.LoginException; +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.ResourceResolverFactory; +import org.apache.sling.junit.rules.TeleporterRule; +import org.apache.sling.resourcebuilder.api.ResourceBuilder; +import org.apache.sling.resourcebuilder.test.ResourceAssertions; + +class TestEnvironment { + + final ResourceBuilder builder; + final ResourceBuilder builderService; + final ResourceResolver resolver; + final String testRootPath; + final Resource parent; + final ResourceAssertions A; + + TestEnvironment(TeleporterRule teleporter) throws LoginException, PersistenceException { + testRootPath = getClass().getSimpleName() + "-" + UUID.randomUUID().toString(); + resolver = teleporter.getService(ResourceResolverFactory.class).getAdministrativeResourceResolver(null); + final Resource root = resolver.getResource("/"); + parent = resolver.create(root, testRootPath, null); + builderService = teleporter.getService(ResourceBuilder.class); + builder = builderService.forParent(parent); + A = new ResourceAssertions(testRootPath, resolver); + } + + void cleanup() throws PersistenceException { + if(resolver != null && parent != null) { + resolver.delete(parent); + resolver.commit(); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/resourcebuilder/test/ResourceAssertions.java b/src/test/java/org/apache/sling/resourcebuilder/test/ResourceAssertions.java new file mode 100644 index 0000000..cea2069 --- /dev/null +++ b/src/test/java/org/apache/sling/resourcebuilder/test/ResourceAssertions.java @@ -0,0 +1,136 @@ +/* + * 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.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Calendar; +import java.util.Comparator; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.resourcebuilder.impl.MapArgsConverter; +import org.apache.sling.resourcebuilder.impl.ResourceBuilderImpl; + +/** Utilities for asserting Resources and their properties */ +public class ResourceAssertions { + + private final ResourceResolver resourceResolver; + private final String testRootPath; + + public ResourceAssertions(String testRootPath, ResourceResolver r) { + this.testRootPath = testRootPath; + this.resourceResolver = r; + } + + public String fullPath(String path) { + return path.startsWith("/") ? path : testRootPath + "/" + path; + } + + public Resource assertResource(String path) { + final Resource result = resourceResolver.resolve(fullPath(path)); + assertNotNull("Expecting resource to exist:" + path, result); + return result; + } + + /** Assert that a file exists and verify its properties. */ + public Resource assertFile(String path, String mimeType, String expectedContent, Long lastModified) throws IOException { + final Comparator<Long> defaultComparator = new Comparator<Long>() { + @Override + public int compare(Long expected, Long fromResource) { + if(expected == -1) { + return 0; + } + return expected.compareTo(fromResource); + } + }; + return assertFile(path, mimeType, expectedContent, lastModified, defaultComparator); + } + + public String readFully(InputStream is) throws IOException { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + IOUtils.copy(is, bos); + return new String(bos.toByteArray()); + } finally { + bos.close(); + is.close(); + } + } + + /** Assert that a file exists and verify its properties. */ + public Resource assertFile(String path, String mimeType, String expectedContent, Long lastModified, Comparator<Long> lastModifiedComparator) throws IOException { + final Resource r = assertResource(path); + assertNotNull("Expecting resource to exist:" + path, r); + + // Files are stored according to the standard JCR structure + final ValueMap fileVm = r.adaptTo(ValueMap.class); + assertNotNull("Expecting ValueMap for " + r.getPath(), fileVm); + assertEquals("Expecting an nt:file at " + r.getPath(), + ResourceBuilderImpl.NT_FILE, fileVm.get(ResourceBuilderImpl.JCR_PRIMARYTYPE)); + final Resource jcrContent = r.getChild(ResourceBuilderImpl.JCR_CONTENT); + assertNotNull("Expecting subresource:" + ResourceBuilderImpl.JCR_CONTENT, jcrContent); + final ValueMap vm = jcrContent.adaptTo(ValueMap.class); + assertNotNull("Expecting ValueMap for " + jcrContent.getPath(), vm); + assertEquals("Expecting nt:Resource type for " + jcrContent.getPath(), + ResourceBuilderImpl.NT_RESOURCE, vm.get(ResourceBuilderImpl.JCR_PRIMARYTYPE)); + assertEquals("Expecting the correct mime-type", mimeType, vm.get(ResourceBuilderImpl.JCR_MIMETYPE)); + assertEquals("Expecting the correct last modified", + 0, lastModifiedComparator.compare(lastModified, getLastModified(vm))); + + final InputStream is = vm.get(ResourceBuilderImpl.JCR_DATA, InputStream.class); + assertNotNull("Expecting InputStream property on nt:resource:" + ResourceBuilderImpl.JCR_DATA, is); + final String content = readFully(is); + assertTrue("Expecting content to contain " + expectedContent, content.contains(expectedContent)); + + return r; + } + + private Long getLastModified(ValueMap vm) { + final Object o = vm.get(ResourceBuilderImpl.JCR_LASTMODIFIED); + if(o instanceof Long) { + return (Long)o; + } else if(o instanceof Calendar) { + return ((Calendar)o).getTimeInMillis(); + } + throw new IllegalArgumentException("Unexpected type " + o.getClass().getName()); + } + + public 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); + } + } +} \ No newline at end of file diff --git a/src/test/resources/files/models.js b/src/test/resources/files/models.js new file mode 100644 index 0000000..142f8d3 --- /dev/null +++ b/src/test/resources/files/models.js @@ -0,0 +1 @@ +function someJavascriptFunction() { return "yes, it worked." } \ No newline at end of file diff --git a/src/test/resources/files/myapp.json b/src/test/resources/files/myapp.json new file mode 100644 index 0000000..bd3edd2 --- /dev/null +++ b/src/test/resources/files/myapp.json @@ -0,0 +1,4 @@ +{ + "jcr:primaryType":"some:NodeType", + "sling:resourceType":"its/resource/type" +} \ No newline at end of file diff --git a/src/test/resources/files/text.html b/src/test/resources/files/text.html new file mode 100644 index 0000000..57dfc39 --- /dev/null +++ b/src/test/resources/files/text.html @@ -0,0 +1,3 @@ +<html> +This is an html file +</html> \ No newline at end of file -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
