/* * 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.api.resource.ValueMap; 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(); intermediatePrimaryType = DEFAULT_PRIMARY_TYPE; currentParent = parent; hierarchyMode = true; } private ResourceBuilderImpl cloneResourceBuilder(Resource newCurrentParent, String newIntermediatePrimaryType, boolean newHierarchyMode) { ResourceBuilderImpl clone = new ResourceBuilderImpl(originalParent, mimeTypeService); clone.currentParent = newCurrentParent; clone.intermediatePrimaryType = newIntermediatePrimaryType; clone.hierarchyMode = newHierarchyMode; return clone; } @Override public Resource getCurrentParent() { return currentParent; } @Override public ResourceBuilder atParent() { return cloneResourceBuilder(originalParent, this.intermediatePrimaryType, true); } private boolean isAbsolutePath(String path) { return path.startsWith("/") && !path.contains(".."); } private void checkRelativePath(String path) { if(path.startsWith("/")) { throw new IllegalArgumentException("Path is not relative:" + path); } if(path.contains("..")) { throw new IllegalArgumentException("Path contains invalid pattern '..': " + path); } } 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 path, Map<String,Object> properties) { Resource r = null; final String parentPath; final String fullPath; boolean absolutePath = isAbsolutePath(path); if (absolutePath) { parentPath = ResourceUtil.getParent(path); fullPath = path; } else { checkRelativePath(path); parentPath = parentPath(path); fullPath = currentParent.getPath() + "/" + path; } final Resource myParent = ensureResourceExists(parentPath); try { r = currentParent.getResourceResolver().getResource(fullPath); if (r == null) { r = currentParent.getResourceResolver().create(myParent, ResourceUtil.getName(fullPath), properties); } 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 : properties.entrySet()) { mvm.put(e.getKey(), e.getValue()); } } } catch(PersistenceException pex) { throw new RuntimeException( "PersistenceException while creating Resource " + fullPath, pex); } if (r == null) { throw new RuntimeException("Failed to get or create resource " + fullPath); } else if (hierarchyMode || absolutePath) { return cloneResourceBuilder(r, this.intermediatePrimaryType, true); } return this; } @SuppressWarnings("unchecked") @Override public ResourceBuilder resource(String path, Object... properties) { if (properties == null || properties.length == 0) { return resource(path, ValueMap.EMPTY); } else if (properties.length == 1 && properties[0] instanceof Map) { return resource(path, (Map<String,Object>)properties[0]); } else { return resource(path, MapArgsConverter.toMap(properties)); } } /** 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) { return cloneResourceBuilder(file, this.intermediatePrimaryType, this.hierarchyMode); } return this; } @Override public ResourceBuilder file(String filename, InputStream data) { return file(filename, data, null, -1); } @Override public ResourceBuilder withIntermediatePrimaryType(String primaryType) { String intermediatePrimaryType = primaryType == null ? DEFAULT_PRIMARY_TYPE : primaryType; return cloneResourceBuilder(currentParent, intermediatePrimaryType, hierarchyMode); } @Override public ResourceBuilder siblingsMode() { return cloneResourceBuilder(currentParent, intermediatePrimaryType, false); } @Override public ResourceBuilder hierarchyMode() { return cloneResourceBuilder(currentParent, intermediatePrimaryType, true); } @Override public ResourceBuilder commit() { try { resourceResolver.commit(); } catch (PersistenceException ex) { throw new RuntimeException("Unable to commit", ex); } return this; } }