blob: b7a10ff8f9476fd67753c48b273a0ccf4b96e309 [file] [log] [blame]
/*
* 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.jcr.js.nodetypes.downloaddefaultbinary;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.nodetype.PropertyDefinition;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides a download for binary default values.
*
* The fully qualified URL to specify a default value looks like this:
* <code>/ns:ntName/binPropDef/binary/true/true/true/true/version/1.default_binary_value.bin</code>
* The fully qualified format is: <code>/node type/property definition
* name/required property type name/is autoCreated/is mandatory/is
* protected/is multiple/on parent version action name/index of the
* default value.default_binary_value.bin</code>
*
* In case you know which elements identify a property definition unambiguously
* you can shorten the URL. E.g. if you are sure the property definition
* 'binPropDef' does not exist twice within the node type 'ns:ntName' you can
* use the URL <code>/ns:ntName/binPropDef/1.default_binary_value.bin</code> to
* download the second binary default value from that property definition.
*
* If you want to download the first binary default value you can shorten the
* URL even more by skipping the index in the URL like this:
* <code>/ns:ntName/binPropDef/default_binary_value.bin</code>
*
* The type name, the boolean Strings and the parent version action name are
* case insensitive.
*
* This long identification is needed as a property definition name with its
* type may not be unique within a node type. This is not only the case for
* residual property definitions. The JCR does not specify that there can be
* only one combination of property definition name / required property type
* name. Thats the reason why it is qualified like this.
*
*/
@Component
@Service(Servlet.class)
@Properties({ @Property(name = "service.description", value = "Download Servlet for binary properties"),
@Property(name = "service.vendor", value = "Sandro Boehme"),
@Property(name = "sling.servlet.selectors", value = "default_binary_value"),
@Property(name = "sling.servlet.extensions", value = "bin"),
@Property(name = "sling.servlet.resourceTypes", value = "sling/servlet/default")
})
public class DownloadDefaultBinaryValueServlet extends SlingSafeMethodsServlet {
private static final long serialVersionUID = -1L;
/** default log */
private final Logger log = LoggerFactory.getLogger(DownloadDefaultBinaryValueServlet.class);
@SuppressWarnings("deprecation")
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/octet-stream; charset=UTF-8");
String requestURI = request.getRequestURI();
String[] idFields = requestURI.substring(1).split("/");
try {
NodeTypeManager nodeTypeManager = request.getResourceResolver().getResource("/").adaptTo(Node.class).getSession()
.getWorkspace().getNodeTypeManager();
NodeType nodeType = nodeTypeManager.getNodeType(idFields[0]);
PropertyDefinition[] propertyDefinitions = nodeType.getPropertyDefinitions();
List<PropertyDefinition> propertyDefinitionList = Arrays.asList(propertyDefinitions);
if (propertyDefinitionList != null) {
// Every matcher represents a path element in the URL and is initialized with its value.
// It will try to match the value of a path element with the corresponding property
// element of all property definitions from the node type in findMatchingPropertyDef().
PropertyMatcher[] propertyMatcher = new PropertyMatcher[] { new PropertyNameMatcher(idFields, 1),
new RequiredPropertyTypeMatcher(idFields, 2), new AutoCreatedMatcher(idFields, 3),
new MandatoryMatcher(idFields, 4), new ProtectedMatcher(idFields, 5), new MultipleMatcher(idFields, 6),
new OnParentVersionMatcher(idFields, 7) };
PropertyDefinition propDef = findMatchingPropertyDef(propertyDefinitionList, new LinkedList<PropertyMatcher>(
Arrays.asList(propertyMatcher)));
if (propDef != null) {
Value[] defaultValues = propDef.getDefaultValues();
if (defaultValues != null && defaultValues.length > 0) {
int startIndex = requestURI.lastIndexOf('/') + 1;
int endIndex = requestURI.indexOf("default_binary_value.bin") - 1;
int defaultValueIndex = 0;
if (endIndex - startIndex == 1) {
String indexString = requestURI.substring(startIndex, endIndex);
defaultValueIndex = Integer.parseInt(indexString);
}
try {
if (defaultValueIndex < defaultValues.length) {
Value defaultValue = defaultValues[defaultValueIndex];
InputStream stream = defaultValue.getStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream));
PrintWriter writer = response.getWriter();
String line = null;
while ((line = bufferedReader.readLine()) != null) {
writer.write(line);
}
writer.flush();
writer.close();
response.setStatus(HttpServletResponse.SC_OK);
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} catch (NumberFormatException nfe) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
} catch (RepositoryException e) {
log.error("Could not return the binary file.", e);
throw new ServletException(e);
}
}
/**
* This method pulls the first matcher out of the list and iterates over the list of specified property definitions to find matches.
* Lets say this is a PropertyNameMatcher that has been initialized with the property name from the URL. Than it will match for every
* property definition who's name is equal to the one specified in the URL. The matched property definitions and the rest of the matchers
* will be provided for the next recursive call of the method to work through the other path elements until all matchers are processed or
* until only one property definition matches. In the first case null is returned and in the second case the identified property definition
* is returned.
* @param propertyDefinitions The list of property definitions.
* @param propertyMatcherList The list of matcher in the order of appearance of their type in the URL. A matcher checks if the
* content of a path element it was initialized with matches its corresponding value in the property definition.
* @return Returns the property definition that is identified by the URL or null if no property definition matches the values specified in the URL.
*/
private PropertyDefinition findMatchingPropertyDef(List<PropertyDefinition> propertyDefinitions,
List<PropertyMatcher> propertyMatcherList) {
if (propertyMatcherList.size() > 0) {
// retrieve the matcher to be used for this iteration
PropertyMatcher propertyMatcher = propertyMatcherList.get(0);
// remove the matcher to make the next matcher available for the
// next iteration
propertyMatcherList.remove(0);
List<PropertyDefinition> matchedPropDefs = new LinkedList<PropertyDefinition>();
// try to match all property definitions with the top matcher
for (PropertyDefinition propertyDefinition : propertyDefinitions) {
if (propertyMatcher.match(propertyDefinition)) {
matchedPropDefs.add(propertyDefinition);
}
}
if (matchedPropDefs.size() == 1) {
return matchedPropDefs.get(0);
} else if (matchedPropDefs.size() > 1) {
return findMatchingPropertyDef(matchedPropDefs, propertyMatcherList);
}
}
return null;
}
}