You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/10/18 23:20:41 UTC
[sling-org-apache-sling-resourcebuilder] 01/25: SLING-5356 - move
resourcebuilder under bundles/extensions
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 <bd...@apache.org>
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
"commits@sling.apache.org" <co...@sling.apache.org>.