blob: 5a02959022e6db3e01c29301517341ffabcb8d80 [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.servlets.post.impl.helper;
import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeManager;
import javax.servlet.ServletContext;
import org.apache.jackrabbit.util.Text;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.servlets.post.Modification;
/**
* Handles file uploads.
* <p/>
*
* Simple example:
* <xmp>
* <form action="/home/admin" method="POST" enctype="multipart/form-data">
* <input type="file" name="./portrait" />
* </form>
* </xmp>
*
* this will create a nt:file node below "/home/admin" if the node type of
* "admin" is (derived from) nt:folder, a nt:resource node otherwise.
* <p/>
*
* Filename example:
* <xmp>
* <form action="/home/admin" method="POST" enctype="multipart/form-data">
* <input type="file" name="./*" />
* </form>
* </xmp>
*
* same as above, but uses the filename of the uploaded file as name for the
* new node.
* <p/>
*
* Type hint example:
* <xmp>
* <form action="/home/admin" method="POST" enctype="multipart/form-data">
* <input type="file" name="./portrait" />
* <input type="hidden" name="./portrait@TypeHint" value="my:file" />
* </form>
* </xmp>
*
* this will create a new node with the type my:file below admin. if the hinted
* type extends from nt:file an intermediate file node is created otherwise
* directly a resource node.
*/
public class SlingFileUploadHandler {
// nodetype name string constants
public static final String NT_FOLDER = "nt:folder";
public static final String NT_FILE = "nt:file";
public static final String NT_RESOURCE = "nt:resource";
public static final String NT_UNSTRUCTURED = "nt:unstructured";
// item name string constants
public static final String JCR_CONTENT = "jcr:content";
public static final String JCR_LASTMODIFIED = "jcr:lastModified";
public static final String JCR_MIMETYPE = "jcr:mimeType";
public static final String JCR_ENCODING = "jcr:encoding";
public static final String JCR_DATA = "jcr:data";
/**
* The servlet context.
*/
private final ServletContext servletContext;
/**
* Constructs file upload handler
* @param servletCtx the post processor
*/
public SlingFileUploadHandler(ServletContext servletCtx) {
this.servletContext = servletCtx;
}
/**
* Uses the file(s) in the request parameter for creation of new nodes.
* if the parent node is a nt:folder a new nt:file is created. otherwise
* just a nt:resource. if the <code>name</code> is '*', the filename of
* the uploaded file is used.
*
* @param parent the parent node
* @param prop the assembled property info
* @throws RepositoryException if an error occurs
*/
public void setFile(Node parent, RequestProperty prop, List<Modification> changes)
throws RepositoryException {
RequestParameter[] values = prop.getValues();
for (RequestParameter requestParameter : values) {
RequestParameter value = requestParameter;
// ignore if a plain form field or empty
if (value.isFormField() || value.getSize() <= 0) {
continue;
}
// get node name
String name = prop.getName();
if (name.equals("*")) {
name = value.getFileName();
// strip of possible path (some browsers include the entire path)
name = name.substring(name.lastIndexOf('/') + 1);
name = name.substring(name.lastIndexOf('\\') + 1);
}
name = Text.escapeIllegalJcrChars(name);
// check type hint. if the type is ok and extends from nt:file,
// create an nt:file with that type. if it's invalid, drop it and let
// the parent node type decide.
boolean createNtFile = parent.isNodeType(NT_FOLDER);
String typeHint = prop.getTypeHint();
if (typeHint != null) {
try {
NodeTypeManager ntMgr = parent.getSession().getWorkspace().getNodeTypeManager();
NodeType nt = ntMgr.getNodeType(typeHint);
createNtFile = nt.isNodeType(NT_FILE);
} catch (RepositoryException e) {
// assuming type not valid.
typeHint = null;
}
}
// also create an nt:file if the name contains an extension
// the rationale is that if the file name is "important" we want
// an nt:file, and an image name with an extension is probably "important"
if(!createNtFile && name.indexOf('.') > 0) {
createNtFile = true;
}
// set empty type
if (typeHint == null) {
typeHint = createNtFile ? NT_FILE : NT_RESOURCE;
}
// remove node
if (parent.hasNode(name)) {
parent.getNode(name).remove();
}
// create nt:file node if needed
Node resParent;
if (createNtFile) {
// create nt:file
resParent = parent.addNode(name, typeHint);
changes.add(Modification.onCreated(resParent.getPath()));
name = JCR_CONTENT;
typeHint = NT_RESOURCE;
} else {
resParent = parent;
}
// create resource node
Node res = resParent.addNode(name, typeHint);
changes.add(Modification.onCreated(res.getPath()));
// get content type
String contentType = value.getContentType();
if (contentType != null) {
int idx = contentType.indexOf(';');
if (idx > 0) {
contentType = contentType.substring(0, idx);
}
}
if (contentType == null || contentType.equals("application/octet-stream")) {
// try to find a better content type
contentType = this.servletContext.getMimeType(value.getFileName());
if (contentType == null || contentType.equals("application/octet-stream")) {
contentType = "application/octet-stream";
}
}
// set properties
changes.add(Modification.onModified(
res.setProperty(JCR_LASTMODIFIED, Calendar.getInstance()).getPath()
));
changes.add(Modification.onModified(
res.setProperty(JCR_MIMETYPE, contentType).getPath()
));
try {
changes.add(Modification.onModified(
res.setProperty(JCR_DATA, value.getInputStream()).getPath()
));
} catch (IOException e) {
throw new RepositoryException("Error while retrieving inputstream from parameter value.", e);
}
}
}
}