| /* |
| * 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; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| /** ResourceBuilder implementation */ |
| @SuppressWarnings("null") |
| public class ResourceBuilderImpl implements ResourceBuilder { |
| private final @NotNull Resource originalParent; |
| private final @NotNull ResourceResolver resourceResolver; |
| private @NotNull 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 @NotNull MimeTypeService mimeTypeService; |
| |
| public ResourceBuilderImpl(@NotNull Resource parent, @NotNull 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 @NotNull ResourceBuilderImpl cloneResourceBuilder(@NotNull Resource newCurrentParent, |
| @Nullable String newIntermediatePrimaryType, boolean newHierarchyMode) { |
| ResourceBuilderImpl clone = new ResourceBuilderImpl(originalParent, mimeTypeService); |
| clone.currentParent = newCurrentParent; |
| clone.intermediatePrimaryType = newIntermediatePrimaryType; |
| clone.hierarchyMode = newHierarchyMode; |
| return clone; |
| } |
| |
| @Override |
| public @NotNull Resource getCurrentParent() { |
| return currentParent; |
| } |
| |
| @Override |
| public @NotNull 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 @NotNull ResourceBuilder resource(@NotNull String path, @NotNull 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 @NotNull ResourceBuilder resource(@NotNull String path, @NotNull Object @NotNull ... 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 @NotNull ResourceBuilder file(@NotNull String relativePath, @NotNull InputStream data, @Nullable 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 @NotNull ResourceBuilder file(@NotNull String filename, @NotNull InputStream data) { |
| return file(filename, data, null, -1); |
| } |
| |
| @Override |
| public @NotNull ResourceBuilder withIntermediatePrimaryType(@Nullable String primaryType) { |
| String intermediatePrimaryType = primaryType == null ? DEFAULT_PRIMARY_TYPE : primaryType; |
| return cloneResourceBuilder(currentParent, intermediatePrimaryType, hierarchyMode); |
| } |
| |
| @Override |
| public @NotNull ResourceBuilder siblingsMode() { |
| return cloneResourceBuilder(currentParent, intermediatePrimaryType, false); |
| } |
| |
| @Override |
| public @NotNull ResourceBuilder hierarchyMode() { |
| return cloneResourceBuilder(currentParent, intermediatePrimaryType, true); |
| } |
| |
| @Override |
| public @NotNull ResourceBuilder commit() { |
| try { |
| resourceResolver.commit(); |
| } catch (PersistenceException ex) { |
| throw new RuntimeException("Unable to commit", ex); |
| } |
| return this; |
| } |
| |
| } |