/**
* OpenKM, Open Document Management System (http://www.openkm.com)
* Copyright (c) 2006-2011 Paco Avila & Josep Llort
*
* No bytes were intentionally harmed during the development of this application.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package com.openkm.servlet.admin;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import javax.jcr.LoginException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.Lock;
import javax.jcr.nodetype.PropertyDefinition;
import javax.jcr.util.TraversingItemVisitor;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.core.NodeImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.openkm.api.OKMFolder;
import com.openkm.api.OKMScripting;
import com.openkm.bean.ContentInfo;
import com.openkm.bean.Document;
import com.openkm.bean.Folder;
import com.openkm.bean.Scripting;
import com.openkm.core.AccessDeniedException;
import com.openkm.core.Config;
import com.openkm.core.DatabaseException;
import com.openkm.core.PathNotFoundException;
import com.openkm.dao.LockTokenDAO;
import com.openkm.extractor.RegisteredExtractors;
import com.openkm.jcr.JCRUtils;
import com.openkm.util.FormatUtil;
import com.openkm.util.UserActivity;
import com.openkm.util.WebUtils;
/**
* RepositoryView servlet
*/
public class RepositoryViewServlet extends BaseServlet {
private static final long serialVersionUID = 1L;
private static Logger log = LoggerFactory.getLogger(RepositoryViewServlet.class);
private static final String[] NODE_TYPE = { "UNDEFINED", "STRING", "BINARY", "LONG", "DOUBLE",
"DATE", "BOOLEAN", "NAME", "PATH", "REFERENCE" };
@Override
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException {
String method = request.getMethod();
if (checkMultipleInstancesAccess(request, response)) {
if (method.equals(METHOD_GET)) {
doGet(request, response);
} else if (method.equals(METHOD_POST)) {
doPost(request, response);
}
}
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException {
log.debug("doGet({}, {})", request, response);
request.setCharacterEncoding("UTF-8");
String action = WebUtils.getString(request, "action");
String path = WebUtils.getString(request, "path");
Session session = null;
updateSessionManager(request);
try {
session = JCRUtils.getSession();
if (action.equals("unlock")) {
unlock(session, path, request, response);
} else if (action.equals("checkin")) {
checkin(session, path, request, response);
} else if (action.equals("remove_content")) {
removeContent(session, path, request, response);
} else if (action.equals("remove_current")) {
path = removeCurrent(session, path, request, response);
} else if (action.equals("remove_mixin")) {
removeMixin(session, path, request, response);
} else if (action.equals("edit")) {
edit(session, path, request, response);
} else if (action.equals("set_script")) {
OKMScripting.getInstance().setScript(null, path, Config.DEFAULT_SCRIPT);
} else if (action.equals("remove_script")) {
OKMScripting.getInstance().removeScript(null, path);
} else if (action.equals("textExtraction")) {
textExtraction(session, path, request, response);
}
if (!action.equals("edit")) {
list(session, path, request, response);
}
} catch (LoginException e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request,response, e);
} catch (RepositoryException e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request,response, e);
} catch (PathNotFoundException e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request,response, e);
} catch (AccessDeniedException e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request,response, e);
} catch (com.openkm.core.RepositoryException e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request,response, e);
} catch (DatabaseException e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request,response, e);
} finally {
JCRUtils.logout(session);
}
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException {
log.debug("doPost({}, {})", request, response);
request.setCharacterEncoding("UTF-8");
String action = WebUtils.getString(request, "action");
String path = WebUtils.getString(request, "path");
Session session = null;
updateSessionManager(request);
try {
session = JCRUtils.getSession();
if ("save".equals(action)) {
save(session, path, request, response);
list(session, path, request, response);
}
} catch (LoginException e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request,response, e);
} catch (RepositoryException e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request,response, e);
} catch (DatabaseException e) {
log.error(e.getMessage(), e);
sendErrorRedirect(request,response, e);
} finally {
JCRUtils.logout(session);
}
}
/**
* Unlock node
*/
private void unlock(Session session, String path, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, javax.jcr.PathNotFoundException, RepositoryException,
DatabaseException {
log.debug("unlock({}, {}, {}, {})", new Object[] { session, path, request, response });
Node node = session.getRootNode().getNode(path.substring(1));
Lock lock = node.getLock();
String lt = JCRUtils.getLockToken(node.getUUID());
if (lock.getLockOwner().equals(session.getUserID())) {
JCRUtils.loadLockTokens(session);
// If the session contains the lock token of this locked node
if (Arrays.asList(session.getLockTokens()).contains(lt)) {
node.unlock();
JCRUtils.removeLockToken(session, node);
} else {
session.addLockToken(lt);
node.unlock();
LockTokenDAO.remove(lock.getLockOwner(), lt);
}
} else {
session.addLockToken(lt);
node.unlock();
LockTokenDAO.remove(lock.getLockOwner(), lt);
}
// Activity log
UserActivity.log(session.getUserID(), "ADMIN_REPOSITORY_UNLOCK", node.getUUID(), path);
log.debug("unlock: void");
}
/**
* Node check-in
*/
private void checkin(Session session, String path, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, javax.jcr.PathNotFoundException, RepositoryException {
log.debug("checkin({}, {}, {}, {})", new Object[] { session, path, request, response });
Node node = session.getRootNode().getNode(path.substring(1));
node.checkin();
// Activity log
UserActivity.log(session.getUserID(), "ADMIN_REPOSITORY_CHECKIN", node.getUUID(), path);
log.debug("checkin: void");
}
/**
* Remove children nodes
*/
private void removeContent(Session session, String path, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, javax.jcr.PathNotFoundException, RepositoryException {
log.debug("removeCurrent({}, {}, {}, {})", new Object[] { session, path, request, response });
Node node = session.getRootNode().getNode(path.substring(1));
for (NodeIterator ni = node.getNodes(); ni.hasNext(); ) {
Node child = ni.nextNode();
child.remove();
node.save();
}
// Activity log
UserActivity.log(session.getUserID(), "ADMIN_REPOSITORY_REMOVE_CONTENT", node.getUUID(), path);
log.debug("removeCurrent: void");
}
/**
* Remove current node and its children
*/
private String removeCurrent(Session session, String path, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, javax.jcr.PathNotFoundException, RepositoryException {
log.debug("removeCurrent({}, {}, {}, {})", new Object[] { session, path, request, response });
Node node = session.getRootNode().getNode(path.substring(1));
String uuid = node.getUUID();
Node parent = node.getParent();
String parentPath = parent.getPath();
node.remove();
parent.save();
// Activity log
UserActivity.log(session.getUserID(), "ADMIN_REPOSITORY_REMOVE_CURRENT", uuid, path);
log.debug("removeCurrent: {}", path);
return parentPath;
}
/**
* Remove mixin
*/
private void removeMixin(Session session, String path, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, javax.jcr.PathNotFoundException, RepositoryException {
log.debug("removeMixin({}, {}, {}, {})", new Object[] { session, path, request, response });
Node node = session.getRootNode().getNode(path.substring(1));
String mixin = WebUtils.getString(request, "mixin");
node.removeMixin(mixin);
node.save();
// Activity log
UserActivity.log(session.getUserID(), "ADMIN_REPOSITORY_REMOVE_MIXIN", node.getUUID(), mixin+", "+path);
log.debug("removeMixin: {}", path);
}
/**
* Edit property
*/
private void edit(Session session, String path, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, javax.jcr.PathNotFoundException, RepositoryException {
log.debug("edit({}, {}, {}, {})", new Object[] { session, path, request, response });
String property = WebUtils.getString(request, "property");
ServletContext sc = getServletContext();
Node node = session.getRootNode().getNode(path.substring(1));
Property prop = node.getProperty(property);
boolean multiple = false;
String value;
if (prop.getDefinition().isMultiple()) {
value = toString(prop.getValues(), "\n");
multiple = true;
} else {
value = prop.getValue().getString();
}
// Activity log
UserActivity.log(session.getUserID(), "ADMIN_REPOSITORY_EDIT", node.getUUID(), property+", "+value+", "+path);
sc.setAttribute("node", node);
sc.setAttribute("property", prop);
sc.setAttribute("multiple", multiple || prop.getName().equals(Scripting.SCRIPT_CODE));
sc.setAttribute("value", value);
sc.getRequestDispatcher("/admin/repository_edit.jsp").forward(request, response);
log.debug("edit: void");
}
/**
* Save property
*/
private void save(Session session, String path, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, javax.jcr.PathNotFoundException, RepositoryException {
log.debug("save({}, {}, {}, {})", new Object[] { session, path, request, response });
String value = WebUtils.getString(request, "value");
String property = WebUtils.getString(request, "property");
Node node = session.getRootNode().getNode(path.substring(1));
Property prop = node.getProperty(property);
ValueFactory vf = session.getValueFactory();
if (prop.getDefinition().isMultiple()) {
StringTokenizer st = new StringTokenizer(value, "\n");
Value[] values = new Value[st.countTokens()];
for (int i=0 ; st.hasMoreTokens(); i++) {
values[i] = vf.createValue(st.nextToken().trim());
}
node.setProperty(property, values);
} else {
node.setProperty(property, value);
}
node.save();
// Activity log
UserActivity.log(session.getUserID(), "ADMIN_REPOSITORY_SAVE", node.getUUID(), property+", "+value+", "+path);
log.debug("save: void");
}
/**
* Document text extraction
*/
private void textExtraction(Session session, String path, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, javax.jcr.PathNotFoundException, RepositoryException {
log.debug("textExtraction({}, {}, {}, {})", new Object[] { session, path, request, response });
TraversingItemVisitor tiv = new TraversingItemVisitor.Default() {
@Override
protected void entering(Node node, int level) throws RepositoryException {
if (node.isNodeType(Document.CONTENT_TYPE)) {
Node docNode = node.getParent();
log.info("Document: {}", docNode.getPath());
String mimeType = node.getProperty(JcrConstants.JCR_MIMETYPE).getString();
if (!node.isLocked()) {
try {
node.checkout();
RegisteredExtractors.index(docNode, node, mimeType);
node.setProperty(Document.VERSION_COMMENT, "Text extraction");
node.save();
} catch (IOException e) {
log.error("Error when extracting text: {}", e.getMessage());
} finally {
if (node.isCheckedOut()) {
node.checkin();
}
}
}
}
}
};
Node node = session.getRootNode().getNode(path.substring(1));
session.getItem(node.getPath()).accept(tiv);
// Activity log
UserActivity.log(session.getUserID(), "ADMIN_REPOSITORY_TEXT_EXTRACTION", node.getUUID(), null);
log.debug("textExtraction: void");
}
/**
* List node properties and children
*/
private void list(Session session, String path, HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, javax.jcr.PathNotFoundException, RepositoryException {
log.debug("list({}, {}, {}, {})", new Object[] { session, path, request, response });
String stats = WebUtils.getString(request, "stats");
String uuid = WebUtils.getString(request, "uuid");
ServletContext sc = getServletContext();
ContentInfo ci = null;
Node node = null;
// Respository stats calculation
if (!stats.equals("")) {
if (stats.equals("0")) {
request.getSession().removeAttribute("stats");
} else {
request.getSession().setAttribute("stats", true);
}
}
// Handle path or uuid
if (!path.equals("")) {
if (path.equals("/")) {
node = session.getRootNode();
} else {
node = session.getRootNode().getNode(path.substring(1));
}
} else if (!uuid.equals("")) {
node = session.getNodeByUUID(uuid);
path = node.getPath();
} else {
node = session.getRootNode();
}
if (request.getSession().getAttribute("stats") != null && node.isNodeType(Folder.TYPE)) {
try {
ci = OKMFolder.getInstance().getContentInfo(null, node.getPath());
} catch (AccessDeniedException e) {
log.warn(e.getMessage(), e);
} catch (com.openkm.core.RepositoryException e) {
log.warn(e.getMessage(), e);
} catch (PathNotFoundException e) {
log.warn(e.getMessage(), e);
} catch (DatabaseException e) {
log.warn(e.getMessage(), e);
}
}
// Activity log
if (node.isNodeType(JcrConstants.MIX_REFERENCEABLE)) {
UserActivity.log(session.getUserID(), "ADMIN_REPOSITORY_LIST", node.getUUID(), node.getPath());
} else {
UserActivity.log(session.getUserID(), "ADMIN_REPOSITORY_LIST", ((NodeImpl)node).getId().toString(), node.getPath());
}
sc.setAttribute("contentInfo", ci);
sc.setAttribute("node", node);
sc.setAttribute("isFolder", node.isNodeType(Folder.TYPE));
sc.setAttribute("isDocument", node.isNodeType(Document.TYPE));
sc.setAttribute("isDocumentContent", node.isNodeType(Document.CONTENT_TYPE));
sc.setAttribute("isScripting", node.isNodeType(Scripting.TYPE));
sc.setAttribute("holdsLock", node.holdsLock());
sc.setAttribute("breadcrumb", createBreadcrumb(node.getPath()));
sc.setAttribute("properties", getProperties(node));
sc.setAttribute("children", getChildren(node));
sc.getRequestDispatcher("/admin/repository_list.jsp").forward(request, response);
log.debug("list: void");
}
/**
* Create bread crumb for easy navigation
*/
private String createBreadcrumb(String path) throws UnsupportedEncodingException {
int idx = path.lastIndexOf('/');
if (idx > 0) {
String name = path.substring(idx+1);
String parent = path.substring(0, idx);
return createBreadcrumb(parent)+" / <a href=\"RepositoryView?path="+URLEncoder.encode(path, "UTF-8")+"\">"+name+"</a>";
} else {
if (!path.substring(1).equals("")) {
return "<a href=\"RepositoryView?path=\">ROOT</a> / <a href=\"RepositoryView?path="+URLEncoder.encode(path, "UTF-8")+"\">"+path.substring(1)+"</a>";
} else {
return "<a href=\"RepositoryView?path=\">ROOT</a> /";
}
}
}
/**
* Get children from node
*/
private Collection<Map<String, Object>> getChildren(Node node) throws RepositoryException {
ArrayList<Map<String, Object>> al = new ArrayList<Map<String, Object>>();
Map<String, Object> hm = new HashMap<String, Object>();
for (NodeIterator ni = node.getNodes(); ni.hasNext(); ) {
Node child = ni.nextNode();
hm = new HashMap<String, Object>();
if (child.isNodeType(Document.TYPE)) {
Node contentNode = child.getNode(Document.CONTENT);
contentNode.isCheckedOut();
hm.put("checkedOut", contentNode.isCheckedOut());
} else if (child.isNodeType(Document.CONTENT_TYPE)) {
hm.put("checkedOut", child.isCheckedOut());
}
hm.put("name", child.getName());
hm.put("path", child.getPath());
hm.put("locked", child.isLocked());
hm.put("locked", child.isLocked());
hm.put("primaryNodeType", child.getPrimaryNodeType().getName());
hm.put("isFolder", child.isNodeType(Folder.TYPE));
hm.put("isDocument", child.isNodeType(Document.TYPE));
hm.put("isDocumentContent", child.isNodeType(Document.CONTENT_TYPE));
al.add(hm);
}
Collections.sort(al, new ChildCmp());
return al;
}
/**
* Make child node comparable
*/
protected class ChildCmp implements Comparator<Map<String, Object>> {
@Override
public int compare(Map<String, Object> arg0, Map<String, Object> arg1) {
return ((String) arg0.get("name")).compareTo((String) arg1.get("name"));
}
}
/**
* Get properties from node
*/
private Collection<HashMap<String, String>> getProperties(Node node) throws ValueFormatException, RepositoryException {
ArrayList<HashMap<String, String>> al = new ArrayList<HashMap<String,String>>();
for (PropertyIterator pi = node.getProperties(); pi.hasNext(); ) {
HashMap<String, String> hm = new HashMap<String, String>();
Property p = pi.nextProperty();
PropertyDefinition pd = p.getDefinition();
hm.put("pName", p.getName());
hm.put("pProtected", Boolean.toString(pd.isProtected()));
hm.put("pMultiple", Boolean.toString(pd.isMultiple()));
hm.put("pType", NODE_TYPE[pd.getRequiredType()]);
if (pd.getRequiredType() == PropertyType.BINARY) {
InputStream is = p.getStream();
try {
hm.put("pValue", "DATA: "+FormatUtil.formatSize(is.available()));
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(is);
}
} else {
if (pd.isMultiple()) {
hm.put("pValue", toString(p.getValues(), "<br/>"));
} else {
if (p.getName().equals(Scripting.SCRIPT_CODE)) {
hm.put("pValue", p.getString());
} else {
hm.put("pValue", p.getString());
}
}
}
al.add(hm);
}
// Add universal node id
HashMap<String, String> hm = new HashMap<String, String>();
hm.put("pName", "jcr:aid");
hm.put("pProtected", Boolean.toString(true));
hm.put("pMultiple", Boolean.toString(false));
hm.put("pType", "VIRTUAL");
hm.put("pValue", ((NodeImpl) node).getId().toString());
al.add(hm);
Collections.sort(al, new PropertyCmp());
return al;
}
/**
* Make properties comparable
*/
protected class PropertyCmp implements Comparator<HashMap<String, String>> {
@Override
public int compare(HashMap<String, String> arg0, HashMap<String, String> arg1) {
return arg0.get("pName").compareTo(arg1.get("pName"));
}
}
/**
* Convert multi-value property to string
*/
private String toString(Value[] v, String delim) throws ValueFormatException, IllegalStateException,
RepositoryException {
StringBuilder sb = new StringBuilder();
for (int i=0; i<v.length-1; i++) {
sb.append(v[i].getString());
sb.append(delim);
}
if (v.length > 0) {
sb.append(v[v.length-1].getString());
}
return sb.toString();
}
}