FileUploadHandler.java
/*
* Copyright (C) 2003-2009 eXo Platform SAS.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see<http://www.gnu.org/licenses/>.
*/
package org.exoplatform.wcm.connector;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import javax.jcr.ItemExistsException;
import javax.jcr.Node;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.dom.DOMSource;
import com.ibm.icu.text.Transliterator;
import org.apache.commons.lang.StringUtils;
import org.exoplatform.common.http.HTTPStatus;
import org.exoplatform.ecm.connector.fckeditor.FCKMessage;
import org.exoplatform.ecm.connector.fckeditor.FCKUtils;
import org.exoplatform.ecm.utils.lock.LockUtil;
import org.exoplatform.ecm.utils.text.Text;
import org.exoplatform.services.cms.documents.AutoVersionService;
import org.exoplatform.services.cms.impl.Utils;
import org.exoplatform.services.cms.jcrext.activity.ActivityCommonService;
import org.exoplatform.services.cms.mimetype.DMSMimeTypeResolver;
import org.exoplatform.services.cms.templates.TemplateService;
import org.exoplatform.services.listener.ListenerService;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.resources.ResourceBundleService;
import org.exoplatform.services.wcm.core.NodetypeConstant;
import org.exoplatform.services.wcm.publication.WCMPublicationService;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;
import org.exoplatform.upload.UploadResource;
import org.exoplatform.upload.UploadService;
import org.exoplatform.upload.UploadService.UploadLimit;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Created by The eXo Platform SAS
* Author : Tran Nguyen Ngoc
* ngoc.tran@exoplatform.com
* Sep 4, 2009
*/
public class FileUploadHandler {
/** Logger */
private static final Log LOG = ExoLogger.getLogger(FileUploadHandler.class.getName());
/** The Constant UPLOAD_ACTION. */
public final static String UPLOAD_ACTION = "upload";
/** The Constant PROGRESS_ACTION. */
public final static String PROGRESS_ACTION = "progress";
/** The Constant ABORT_ACTION. */
public final static String ABORT_ACTION = "abort";
/** The Constant DELETE_ACTION. */
public final static String DELETE_ACTION = "delete";
/** The Constant SAVE_ACTION. */
public final static String SAVE_ACTION = "save";
/** The Constant SAVE_NEW_VERSION_ACTION. */
public final static String SAVE_NEW_VERSION_ACTION = "saveNewVersion";
/** The Constant CHECK_EXIST. */
public final static String CHECK_EXIST= "exist";
/** The Constant REPLACE. */
public final static String REPLACE= "replace";
public final static String CREATE_VERSION = "createVersion";
/** The Constant KEEP_BOTH. */
public final static String KEEP_BOTH= "keepBoth";
/** The Constant LAST_MODIFIED_PROPERTY. */
private static final String LAST_MODIFIED_PROPERTY = "Last-Modified";
/** The Constant IF_MODIFIED_SINCE_DATE_FORMAT. */
private static final String IF_MODIFIED_SINCE_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
public final static String POST_CREATE_CONTENT_EVENT = "CmsService.event.postCreate";
private final String CONNECTOR_BUNDLE_LOCATION = "locale.wcm.resources.WCMResourceBundleConnector";
private final String AUTOVERSION_ERROR_MIME_TYPE = "DocumentAutoVersion.msg.WrongMimeType";
/** The upload service. */
private UploadService uploadService;
/** The listener service. */
ListenerService listenerService;
private ActivityCommonService activityService;
/** The fck message. */
private FCKMessage fckMessage;
/** The uploadIds - time Map */
private Map<String, Long> uploadIdTimeMap;
/** The maximal life time for an upload */
private long UPLOAD_LIFE_TIME;
/**
* Instantiates a new file upload handler.
*/
public FileUploadHandler() {
uploadService = WCMCoreUtils.getService(UploadService.class);
listenerService = WCMCoreUtils.getService(ListenerService.class);
activityService = WCMCoreUtils.getService(ActivityCommonService.class);
fckMessage = new FCKMessage();
uploadIdTimeMap = new Hashtable<String, Long>();
UPLOAD_LIFE_TIME = System.getProperty("MULTI_UPLOAD_LIFE_TIME") == null ? 600 ://10 minutes
Long.parseLong(System.getProperty("MULTI_UPLOAD_LIFE_TIME"));
}
/**
* Upload
* @param servletRequest The request to upload file
* @param uploadId Upload Id
* @param limit Limit size of upload file
* @return
* @throws Exception
*/
public Response upload(HttpServletRequest servletRequest, String uploadId, Integer limit) throws Exception{
uploadService.addUploadLimit(uploadId, limit);
uploadService.createUploadResource(servletRequest);
uploadIdTimeMap.put(uploadId, System.currentTimeMillis());
CacheControl cacheControl = new CacheControl();
cacheControl.setNoCache(true);
cacheControl.setNoStore(true);
DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
//create ret
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
Element rootElement = doc.createElement("html");
Element head = doc.createElement("head");
Element body = doc.createElement("body");
rootElement.appendChild(head);
rootElement.appendChild(body);
doc.appendChild(rootElement);
return Response.ok(new DOMSource(doc), MediaType.TEXT_XML)
.cacheControl(cacheControl)
.header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
.build();
}
/**
* Check status of uploaded file.
* If any problem while uploading, error message is returned.
* Returning null means no problem happen.
*
* @param uploadId upload ID
* @param language language for getting message
* @return Response message is returned if any problem while uploading.
* @throws Exception
*/
public Response checkStatus(String uploadId, String language) throws Exception {
CacheControl cacheControl = new CacheControl();
cacheControl.setNoCache(true);
cacheControl.setNoStore(true);
DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
if ((StringUtils.isEmpty(uploadId)) || (uploadService.getUploadResource(uploadId) == null)) return null;
// If file size exceed limit, return message
if (UploadResource.FAILED_STATUS == uploadService.getUploadResource(uploadId).getStatus()) {
// Remove upload Id
uploadService.removeUploadResource(uploadId);
uploadIdTimeMap.remove(uploadId);
// Get message warning upload exceed limit
String uploadLimit = String.valueOf(uploadService.getUploadLimits().get(uploadId).getLimit());
Document fileExceedLimit =
fckMessage.createMessage(FCKMessage.FILE_EXCEED_LIMIT,
FCKMessage.ERROR,
language,
new String[]{uploadLimit});
return Response.ok(new DOMSource(fileExceedLimit), MediaType.TEXT_XML)
.cacheControl(cacheControl)
.header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
.build();
}
return null;
}
/**
* Control.
*
* @param uploadId the upload id
* @param action the action
*
* @return the response
*
* @throws Exception the exception
*/
public Response control(String uploadId, String action) throws Exception {
CacheControl cacheControl = new CacheControl();
cacheControl.setNoCache(true);
cacheControl.setNoStore(true);
DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
if (FileUploadHandler.PROGRESS_ACTION.equals(action)) {
Document currentProgress = getProgress(uploadId);
return Response.ok(new DOMSource(currentProgress), MediaType.TEXT_XML)
.cacheControl(cacheControl)
.header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
.build();
} else if (FileUploadHandler.ABORT_ACTION.equals(action)) {
uploadService.removeUploadResource(uploadId);
uploadIdTimeMap.remove(uploadId);
return Response.ok(null, MediaType.TEXT_XML)
.cacheControl(cacheControl)
.header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
.build();
} else if (FileUploadHandler.DELETE_ACTION.equals(action)) {
uploadService.removeUploadResource(uploadId);
uploadIdTimeMap.remove(uploadId);
return Response.ok(null, MediaType.TEXT_XML)
.cacheControl(cacheControl)
.header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
.build();
}
return Response.status(HTTPStatus.BAD_REQUEST)
.cacheControl(cacheControl)
.header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
.build();
}
/**
* checks if file already existed in parent folder
*
* @param parent the parent
* @param fileName the file name
* @return the response
*
* @throws Exception the exception
*/
public Response checkExistence(Node parent, String fileName) throws Exception {
DMSMimeTypeResolver resolver = DMSMimeTypeResolver.getInstance();
CacheControl cacheControl = new CacheControl();
cacheControl.setNoCache(true);
DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
//create ret
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document fileExistence = builder.newDocument();
fileName = Utils.cleanNameWithAccents(fileName);
Element rootElement = fileExistence.createElement(
parent.hasNode(fileName) ? "Existed" : "NotExisted");
if(parent.hasNode(fileName)){
Node existNode = parent.getNode(fileName);
if(existNode.isNodeType(NodetypeConstant.MIX_VERSIONABLE)){
rootElement.appendChild(fileExistence.createElement("Versioned"));
}
}
if(parent.isNodeType(NodetypeConstant.NT_FILE) &&
resolver.getMimeType(parent.getName()).equals(resolver.getMimeType(fileName))){
rootElement.appendChild(fileExistence.createElement("CanVersioning"));
}
fileExistence.appendChild(rootElement);
//return ret;
return Response.ok(new DOMSource(fileExistence), MediaType.TEXT_XML)
.cacheControl(cacheControl)
.header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
.build();
}
/**
* Clean name using Transliterator
* @param fileName original file name
*
* @return Response
*/
public Response cleanName(String fileName) throws Exception {
CacheControl cacheControl = new CacheControl();
cacheControl.setNoCache(true);
DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document cleanedFilename = builder.newDocument();
fileName = Utils.cleanNameWithAccents(fileName);
Element rootElement = cleanedFilename.createElement("name");
cleanedFilename.appendChild(rootElement);
rootElement.setTextContent(fileName);
return Response.ok(new DOMSource(cleanedFilename), MediaType.TEXT_XML)
.cacheControl(cacheControl)
.header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
.build();
}
/**
* Save as nt file.
*
* @param parent the parent
* @param uploadId the upload id
* @param fileName the file name
* @param language the language
*
* @return the response
*
* @throws Exception the exception
*/
public Response saveAsNTFile(Node parent,
String uploadId,
String fileName,
String language,
String siteName,
String userId) throws Exception {
return saveAsNTFile(parent, uploadId, fileName, language, siteName, userId, KEEP_BOTH);
}
/**
* Save as nt file.
*
* @param parent the parent
* @param uploadId the upload id
* @param fileName the file name
* @param language the language
*
* @return the response
*
* @throws Exception the exception
*/
public Response saveAsNTFile(Node parent,
String uploadId,
String fileName,
String language,
String siteName,
String userId,
String existenceAction) throws Exception {
return saveAsNTFile(parent, uploadId, fileName, language, siteName, userId, existenceAction,false);
}
/**
* Save as nt file.
*
* @param parent the parent
* @param uploadId the upload id
* @param fileName the file name
* @param language the language
*
* @return the response
*
* @throws Exception the exception
*/
public Response saveAsNTFile(Node parent,
String uploadId,
String fileName,
String language,
String siteName,
String userId,
String existenceAction,
boolean isNewVersion) throws Exception {
try {
CacheControl cacheControl = new CacheControl();
cacheControl.setNoCache(true);
UploadResource resource = uploadService.getUploadResource(uploadId);
DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
if (parent == null) {
Document fileNotUploaded = fckMessage.createMessage(FCKMessage.FILE_NOT_UPLOADED,
FCKMessage.ERROR,
language,
null);
return Response.ok(new DOMSource(fileNotUploaded), MediaType.TEXT_XML)
.cacheControl(cacheControl)
.header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
.build();
}
if (!FCKUtils.hasAddNodePermission(parent)) {
Object[] args = { parent.getPath() };
Document message = fckMessage.createMessage(FCKMessage.FILE_UPLOAD_RESTRICTION,
FCKMessage.ERROR,
language,
args);
return Response.ok(new DOMSource(message), MediaType.TEXT_XML)
.cacheControl(cacheControl)
.header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
.build();
}
if ((fileName == null) || (fileName.length() == 0)) {
fileName = resource.getFileName();
}
//add lock token
if(parent.isLocked()) {
parent.getSession().addLockToken(LockUtil.getLockToken(parent));
}
if (parent.hasNode(fileName)) {
// Object args[] = { fileName, parent.getPath() };
// Document fileExisted = fckMessage.createMessage(FCKMessage.FILE_EXISTED,
// FCKMessage.ERROR,
// language,
// args);
// return Response.ok(new DOMSource(fileExisted), MediaType.TEXT_XML)
// .cacheControl(cacheControl)
// .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
// .build();
if (REPLACE.equals(existenceAction)) {
//Broadcast the event when user move node to Trash
ListenerService listenerService = WCMCoreUtils.getService(ListenerService.class);
listenerService.broadcast(ActivityCommonService.FILE_REMOVE_ACTIVITY, parent, parent.getNode(fileName));
parent.getNode(fileName).remove();
parent.save();
}
}
AutoVersionService autoVersionService = WCMCoreUtils.getService(AutoVersionService.class);
String location = resource.getStoreLocation();
//save node with name=fileName
Node file = null;
Node jcrContent=null;
boolean fileCreated = false;
String exoTitle = fileName;
fileName = Utils.cleanNameWithAccents(fileName);
DMSMimeTypeResolver mimeTypeResolver = DMSMimeTypeResolver.getInstance();
String mimetype = mimeTypeResolver.getMimeType(resource.getFileName());
String nodeName = fileName;
int count = 0;
if(!CREATE_VERSION.equals(existenceAction) ||
(!parent.hasNode(fileName) && !CREATE_VERSION.equals(existenceAction))) {
if(parent.isNodeType(NodetypeConstant.NT_FILE)){
String mimeTypeParent = mimeTypeResolver.getMimeType(parent.getName());
if(mimetype != mimeTypeParent){
ResourceBundleService resourceBundleService = WCMCoreUtils.getService(ResourceBundleService.class);
ResourceBundle resourceBundle = resourceBundleService.getResourceBundle(CONNECTOR_BUNDLE_LOCATION, new Locale(language));
String errorMsg = resourceBundle.getString(AUTOVERSION_ERROR_MIME_TYPE);
errorMsg = errorMsg.replace("{0}", StringUtils.escape("<span style='font-weight:bold;'>" + parent.getName() + "</span>"));
JSONObject jsonObject = new JSONObject();
jsonObject.put("error_type", "ERROR_MIMETYPE");
jsonObject.put("error_message", errorMsg);
return Response.serverError().entity(jsonObject.toString()).build();
}
parent = parent.getParent();
}
do {
try {
file = parent.addNode(nodeName, FCKUtils.NT_FILE);
fileCreated = true;
} catch (ItemExistsException e) {//sameNameSibling is not allowed
nodeName = increaseName(fileName, ++count);
}
} while (!fileCreated);
//--------------------------------------------------------
if(!file.isNodeType(NodetypeConstant.MIX_REFERENCEABLE)) {
file.addMixin(NodetypeConstant.MIX_REFERENCEABLE);
}
if(!file.isNodeType(NodetypeConstant.MIX_COMMENTABLE))
file.addMixin(NodetypeConstant.MIX_COMMENTABLE);
if(!file.isNodeType(NodetypeConstant.MIX_VOTABLE))
file.addMixin(NodetypeConstant.MIX_VOTABLE);
if(!file.isNodeType(NodetypeConstant.MIX_I18N))
file.addMixin(NodetypeConstant.MIX_I18N);
if(!file.hasProperty(NodetypeConstant.EXO_TITLE)) {
file.setProperty(NodetypeConstant.EXO_TITLE, exoTitle);
}
jcrContent = file.addNode("jcr:content","nt:resource");
}else if(parent.hasNode(nodeName)){
file = parent.getNode(nodeName);
autoVersionService.autoVersion(file,isNewVersion);
jcrContent = file.hasNode("jcr:content")?file.getNode("jcr:content"):file.addNode("jcr:content","nt:resource");
}else if(parent.isNodeType(NodetypeConstant.NT_FILE)){
file = parent;
autoVersionService.autoVersion(file,isNewVersion);
jcrContent = file.hasNode("jcr:content")?file.getNode("jcr:content"):file.addNode("jcr:content","nt:resource");
}
jcrContent.setProperty("jcr:lastModified", new GregorianCalendar());
jcrContent.setProperty("jcr:data", new BufferedInputStream(new FileInputStream(new File(location))));
jcrContent.setProperty("jcr:mimeType", mimetype);
if(fileCreated) {
file.getParent().save();
autoVersionService.autoVersion(file,isNewVersion);
}
//parent.getSession().refresh(true); // Make refreshing data
//parent.save();
uploadService.removeUploadResource(uploadId);
uploadIdTimeMap.remove(uploadId);
WCMPublicationService wcmPublicationService = WCMCoreUtils.getService(WCMPublicationService.class);
wcmPublicationService.updateLifecyleOnChangeContent(file, siteName, userId);
if (activityService.isBroadcastNTFileEvents(file) && !CREATE_VERSION.equals(existenceAction)) {
listenerService.broadcast(ActivityCommonService.FILE_CREATED_ACTIVITY, null, file);
}
file.save();
return Response.ok(createDOMResponse("Result", mimetype), MediaType.TEXT_XML)
.cacheControl(cacheControl)
.header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
.build();
} catch (Exception exc) {
LOG.error(exc.getMessage(), exc);
return Response.serverError().entity(exc.getMessage()).build();
}
}
public boolean isDocumentNodeType(Node node) throws Exception {
TemplateService templateService = WCMCoreUtils.getService(TemplateService.class);
return templateService.isManagedNodeType(node.getPrimaryNodeType().getName());
}
/**
* increase the file name (not extension).
* @param origin the original name
* @param count the number add to file name
* @return the new increased file name
*/
private String increaseName(String origin, int count) {
int index = origin.indexOf('.');
if (index == -1) return origin + count;
return origin.substring(0, index) + count + origin.substring(index);
}
/**
* get number of files uploading
* @return number of files uploading
*/
public long getUploadingFileCount() {
removeDeadUploads();
return uploadIdTimeMap.size();
}
/**
* removes dead uploads
*/
private void removeDeadUploads() {
Set<String> removedIds = new HashSet<String>();
for (String id : uploadIdTimeMap.keySet()) {
if ((System.currentTimeMillis() - uploadIdTimeMap.get(id)) > UPLOAD_LIFE_TIME * 1000) {
removedIds.add(id);
}
}
for (String id : removedIds) {
uploadIdTimeMap.remove(id);
}
}
/**
* Gets the progress.
*
* @param uploadId the upload id
*
* @return the progress
*
* @throws Exception the exception
*/
private Document getProgress(String uploadId) throws Exception {
UploadResource resource = uploadService.getUploadResource(uploadId);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
Double percent = 0.0;
if(resource != null) {
if (resource.getStatus() == UploadResource.UPLOADING_STATUS) {
percent = (resource.getUploadedSize() * 100) / resource.getEstimatedSize();
} else {
percent = 100.0;
}
}
Element rootElement = doc.createElement("UploadProgress");
rootElement.setAttribute("uploadId", uploadId);
rootElement.setAttribute("fileName", resource == null ? "" : resource.getFileName());
rootElement.setAttribute("percent", percent.intValue() + "");
rootElement.setAttribute("uploadedSize", resource == null ? "0" : resource.getUploadedSize() + "");
rootElement.setAttribute("totalSize", resource == null ? "0" : resource.getEstimatedSize() + "");
rootElement.setAttribute("fileType", resource == null ? "null" : resource.getMimeType() + "");
UploadLimit limit = uploadService.getUploadLimits().get(uploadId);
if (limit != null) {
rootElement.setAttribute("limit", limit.getLimit() + "");
rootElement.setAttribute("unit", limit.getUnit() + "");
}
doc.appendChild(rootElement);
return doc;
}
/**
* returns a DOMSource object containing given message
* @param name the message
* @return DOMSource object
* @throws Exception
*/
private DOMSource createDOMResponse(String name, String mimeType) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
Element rootElement = doc.createElement(name);
rootElement.setAttribute("mimetype", mimeType);
doc.appendChild(rootElement);
return new DOMSource(doc);
}
}