| /* |
| * 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.bundleresource.impl; |
| |
| import static org.apache.jackrabbit.JcrConstants.NT_FILE; |
| import static org.apache.jackrabbit.JcrConstants.NT_FOLDER; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.json.Json; |
| import javax.json.JsonArray; |
| import javax.json.JsonNumber; |
| import javax.json.JsonObject; |
| import javax.json.JsonString; |
| import javax.json.JsonValue; |
| |
| import org.apache.sling.api.resource.AbstractResource; |
| import org.apache.sling.api.resource.Resource; |
| import org.apache.sling.api.resource.ResourceMetadata; |
| import org.apache.sling.api.resource.ResourceResolver; |
| import org.apache.sling.api.resource.ValueMap; |
| import org.apache.sling.api.wrappers.ValueMapDecorator; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** A Resource that wraps a Bundle entry */ |
| public class BundleResource extends AbstractResource { |
| |
| /** default log */ |
| private final Logger log = LoggerFactory.getLogger(getClass()); |
| |
| private final ResourceResolver resourceResolver; |
| |
| private final BundleResourceCache cache; |
| |
| private final PathMapping mappedPath; |
| |
| private final String path; |
| |
| private URL resourceUrl; |
| |
| private final ResourceMetadata metadata; |
| |
| private final ValueMap valueMap; |
| |
| private final Map<String, Map<String, Object>> subResources; |
| |
| @SuppressWarnings("unchecked") |
| public BundleResource(final ResourceResolver resourceResolver, |
| final BundleResourceCache cache, |
| final PathMapping mappedPath, |
| final String resourcePath, |
| final Map<String, Object> readProps, |
| final boolean isFolder) { |
| |
| this.resourceResolver = resourceResolver; |
| this.cache = cache; |
| this.mappedPath = mappedPath; |
| |
| metadata = new ResourceMetadata(); |
| metadata.setResolutionPath(resourcePath); |
| metadata.setCreationTime(this.cache.getBundle().getLastModified()); |
| metadata.setModificationTime(this.cache.getBundle().getLastModified()); |
| |
| this.path = resourcePath; |
| |
| final Map<String, Object> properties = new HashMap<>(); |
| this.valueMap = new ValueMapDecorator(Collections.unmodifiableMap(properties)); |
| if (isFolder) { |
| |
| properties.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, NT_FOLDER); |
| |
| } else { |
| |
| properties.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, NT_FILE); |
| |
| try { |
| final URL url = this.cache.getEntry(mappedPath.getEntryPath(resourcePath)); |
| if ( url != null ) { |
| metadata.setContentLength(url.openConnection().getContentLength()); |
| } |
| } catch (final Exception e) { |
| // don't care, we just have no content length |
| } |
| } |
| |
| Map<String, Map<String, Object>> children = null; |
| if ( readProps != null ) { |
| for(final Map.Entry<String, Object> entry : readProps.entrySet()) { |
| if ( entry.getValue() instanceof Map ) { |
| if ( children == null ) { |
| children = new HashMap<>(); |
| } |
| children.put(entry.getKey(), (Map<String, Object>)entry.getValue()); |
| } else { |
| properties.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| } |
| if ( this.mappedPath.getJSONPropertiesExtension() != null ) { |
| final String propsPath = mappedPath.getEntryPath(resourcePath.concat(this.mappedPath.getJSONPropertiesExtension())); |
| if ( propsPath != null ) { |
| |
| try { |
| final URL url = this.cache.getEntry(propsPath); |
| if (url != null) { |
| final JsonObject obj = Json.createReader(url.openStream()).readObject(); |
| for(final Map.Entry<String, JsonValue> entry : obj.entrySet()) { |
| final Object value = getValue(entry.getValue(), true); |
| if ( value != null ) { |
| if ( value instanceof Map ) { |
| if ( children == null ) { |
| children = new HashMap<>(); |
| } |
| children.put(entry.getKey(), (Map<String, Object>)value); |
| } else { |
| properties.put(entry.getKey(), value); |
| } |
| } |
| } |
| } |
| } catch (final IOException ioe) { |
| log.error( |
| "getInputStream: Cannot get input stream for " + propsPath, ioe); |
| } |
| } |
| } |
| this.subResources = children; |
| } |
| |
| Resource getChildResource(final String path) { |
| Resource result = null; |
| Map<String, Map<String, Object>> resources = this.subResources; |
| String subPath = null; |
| for(String segment : path.split("/")) { |
| if ( resources != null ) { |
| subPath = subPath == null ? segment : subPath.concat("/").concat(segment); |
| final Map<String, Object> props = resources.get(segment); |
| if ( props != null ) { |
| result = new BundleResource(this.resourceResolver, this.cache, this.mappedPath, |
| this.getPath().concat("/").concat(subPath), props, false); |
| resources = ((BundleResource)result).subResources; |
| } else { |
| result = null; |
| break; |
| } |
| } else { |
| result = null; |
| break; |
| } |
| } |
| return result; |
| } |
| |
| private static Object getValue(final JsonValue value, final boolean topLevel) { |
| switch ( value.getValueType() ) { |
| // type NULL -> return null |
| case NULL : return null; |
| // type TRUE or FALSE -> return boolean |
| case FALSE : return false; |
| case TRUE : return true; |
| // type String -> return String |
| case STRING : return ((JsonString)value).getString(); |
| // type Number -> return long or double |
| case NUMBER : final JsonNumber num = (JsonNumber)value; |
| if (num.isIntegral()) { |
| return num.longValue(); |
| } |
| return num.doubleValue(); |
| // type ARRAY -> return list and call this method for each value |
| case ARRAY : final List<Object> array = new ArrayList<>(); |
| for(final JsonValue x : ((JsonArray)value)) { |
| array.add(getValue(x, false)); |
| } |
| return array; |
| // type OBJECT -> return map |
| case OBJECT : final Map<String, Object> map = new HashMap<>(); |
| final JsonObject obj = (JsonObject)value; |
| for(final Map.Entry<String, JsonValue> entry : obj.entrySet()) { |
| map.put(entry.getKey(), getValue(entry.getValue(), false)); |
| } |
| return map; |
| } |
| return null; |
| } |
| |
| Map<String, Map<String, Object>> getSubResources() { |
| return this.subResources; |
| } |
| |
| @Override |
| public String getPath() { |
| return path; |
| } |
| |
| @Override |
| public String getResourceType() { |
| return this.valueMap.get(ResourceResolver.PROPERTY_RESOURCE_TYPE, String.class); |
| } |
| |
| @Override |
| public String getResourceSuperType() { |
| return this.valueMap.get("sling:resourceSuperType", String.class); |
| } |
| |
| @Override |
| public ResourceMetadata getResourceMetadata() { |
| return metadata; |
| } |
| |
| @Override |
| public ResourceResolver getResourceResolver() { |
| return resourceResolver; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <Type> Type adaptTo(Class<Type> type) { |
| if (type == InputStream.class) { |
| return (Type) getInputStream(); // unchecked cast |
| } else if (type == URL.class) { |
| return (Type) getURL(); // unchecked cast |
| } else if (type == ValueMap.class) { |
| return (Type) valueMap; // unchecked cast |
| } |
| |
| // fall back to adapter factories |
| return super.adaptTo(type); |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + ", type=" + getResourceType() |
| + ", path=" + getPath(); |
| } |
| |
| // ---------- internal ----------------------------------------------------- |
| |
| /** |
| * Returns a stream to the bundle entry if it is a file. Otherwise returns |
| * <code>null</code>. |
| */ |
| private InputStream getInputStream() { |
| // implement this for files only |
| if (isFile()) { |
| try { |
| URL url = getURL(); |
| if (url != null) { |
| return url.openStream(); |
| } |
| } catch (IOException ioe) { |
| log.error( |
| "getInputStream: Cannot get input stream for " + this, ioe); |
| } |
| } |
| |
| // otherwise there is no stream |
| return null; |
| } |
| |
| private URL getURL() { |
| if (resourceUrl == null) { |
| final URL url = this.cache.getEntry(mappedPath.getEntryPath(this.path)); |
| if ( url != null ) { |
| try { |
| resourceUrl = new URL(BundleResourceURLStreamHandler.PROTOCOL, null, |
| -1, path, new BundleResourceURLStreamHandler( |
| cache.getBundle(), mappedPath.getEntryPath(path))); |
| } catch (MalformedURLException mue) { |
| log.error("getURL: Cannot get URL for " + this, mue); |
| } |
| } |
| } |
| |
| return resourceUrl; |
| } |
| |
| @Override |
| public Iterator<Resource> listChildren() { |
| return new BundleResourceIterator(this); |
| } |
| |
| BundleResourceCache getBundle() { |
| return cache; |
| } |
| |
| PathMapping getMappedPath() { |
| return mappedPath; |
| } |
| |
| boolean isFile() { |
| return NT_FILE.equals(getResourceType()); |
| } |
| } |