/** * 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.module.base; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Calendar; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionIterator; import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.core.NodeImpl; import org.apache.jackrabbit.core.SessionImpl; import org.apache.jackrabbit.core.security.AccessManager; import org.apache.jackrabbit.spi.Path; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.openkm.bean.Document; import com.openkm.bean.Encryption; import com.openkm.bean.Folder; import com.openkm.bean.Lock; import com.openkm.bean.Note; import com.openkm.bean.Notification; import com.openkm.bean.Permission; import com.openkm.bean.Property; import com.openkm.bean.Version; import com.openkm.cache.UserItemsManager; import com.openkm.core.Config; import com.openkm.core.DatabaseException; import com.openkm.core.UserQuotaExceededException; import com.openkm.dao.UserConfigDAO; import com.openkm.dao.bean.ProfileMisc; import com.openkm.dao.bean.UserConfig; import com.openkm.dao.bean.cache.UserItems; import com.openkm.extractor.RegisteredExtractors; import com.openkm.jcr.JCRUtils; import com.openkm.util.DocConverter; import com.openkm.util.UserActivity; public class BaseDocumentModule { private static Logger log = LoggerFactory.getLogger(BaseDocumentModule.class); /** * Create a new document * * TODO Parameter title to be used in OpenKM 6 */ public static Node create(Session session, Node parentNode, String name, String title, String mimeType, String[] keywords, InputStream is) throws javax.jcr.ItemExistsException, javax.jcr.PathNotFoundException, javax.jcr.AccessDeniedException, javax.jcr.RepositoryException, IOException, DatabaseException, UserQuotaExceededException { log.debug("create({}, {}, {}, {}, {}, {}, {})", new Object[] { session, parentNode, name, title, mimeType, keywords, is }); // Create and add a new file node Node documentNode = parentNode.addNode(name, Document.TYPE); documentNode.setProperty(Property.KEYWORDS, keywords); documentNode.setProperty(Property.CATEGORIES, new String[]{}, PropertyType.REFERENCE); documentNode.setProperty(Document.AUTHOR, session.getUserID()); documentNode.setProperty(Document.NAME, name); // documentNode.setProperty(Document.TITLE, title == null ? "" : title); long size = is.available(); // Check user quota UserConfig uc = UserConfigDAO.findByPk(session, session.getUserID()); ProfileMisc pm = uc.getProfile().getMisc(); // System user don't care quotas if (!Config.SYSTEM_USER.equals(session.getUserID()) && pm.getUserQuota() > 0) { long currentQuota = 0; if (Config.USER_ITEM_CACHE) { UserItems ui = UserItemsManager.get(session.getUserID()); currentQuota = ui.getSize(); } else { currentQuota = JCRUtils.calculateQuota(session); } if (currentQuota + size > pm.getUserQuota()) { throw new UserQuotaExceededException(Long.toString(currentQuota + size)); } } // Get parent node auth info Value[] usersReadParent = parentNode.getProperty(Permission.USERS_READ).getValues(); String[] usersRead = JCRUtils.usrValue2String(usersReadParent, session.getUserID()); Value[] usersWriteParent = parentNode.getProperty(Permission.USERS_WRITE).getValues(); String[] usersWrite = JCRUtils.usrValue2String(usersWriteParent, session.getUserID()); Value[] usersDeleteParent = parentNode.getProperty(Permission.USERS_DELETE).getValues(); String[] usersDelete = JCRUtils.usrValue2String(usersDeleteParent, session.getUserID()); Value[] usersSecurityParent = parentNode.getProperty(Permission.USERS_SECURITY).getValues(); String[] usersSecurity = JCRUtils.usrValue2String(usersSecurityParent, session.getUserID()); Value[] rolesReadParent = parentNode.getProperty(Permission.ROLES_READ).getValues(); String[] rolesRead = JCRUtils.rolValue2String(rolesReadParent); Value[] rolesWriteParent = parentNode.getProperty(Permission.ROLES_WRITE).getValues(); String[] rolesWrite = JCRUtils.rolValue2String(rolesWriteParent); Value[] rolesDeleteParent = parentNode.getProperty(Permission.ROLES_DELETE).getValues(); String[] rolesDelete = JCRUtils.rolValue2String(rolesDeleteParent); Value[] rolesSecurityParent = parentNode.getProperty(Permission.ROLES_SECURITY).getValues(); String[] rolesSecurity = JCRUtils.rolValue2String(rolesSecurityParent); // Set auth info documentNode.setProperty(Permission.USERS_READ, usersRead); documentNode.setProperty(Permission.USERS_WRITE, usersWrite); documentNode.setProperty(Permission.USERS_DELETE, usersDelete); documentNode.setProperty(Permission.USERS_SECURITY, usersSecurity); documentNode.setProperty(Permission.ROLES_READ, rolesRead); documentNode.setProperty(Permission.ROLES_WRITE, rolesWrite); documentNode.setProperty(Permission.ROLES_DELETE, rolesDelete); documentNode.setProperty(Permission.ROLES_SECURITY, rolesSecurity); Node contentNode = documentNode.addNode(Document.CONTENT, Document.CONTENT_TYPE); contentNode.setProperty(Document.SIZE, size); contentNode.setProperty(Document.AUTHOR, session.getUserID()); contentNode.setProperty(Document.VERSION_COMMENT, ""); contentNode.setProperty(JcrConstants.JCR_MIMETYPE, mimeType); contentNode.setProperty(JcrConstants.JCR_DATA, is); // jcr:encoding only have sense for text/* MIME if (mimeType.startsWith("text/")) { contentNode.setProperty(JcrConstants.JCR_ENCODING, "UTF-8"); } if (Config.EXPERIMENTAL_TEXT_EXTRACTION) { RegisteredExtractors.index(documentNode, contentNode, mimeType); } contentNode.setProperty(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance()); parentNode.save(); // Esta línea vale millones!! Resuelve la incidencia del isCkechedOut. // Por lo visto un nuevo nodo se añade con el isCheckedOut a true :/ contentNode.checkin(); // Update user items size if (Config.USER_ITEM_CACHE) { UserItemsManager.incSize(session.getUserID(), size); UserItemsManager.incDocuments(session.getUserID(), 1); } return documentNode; } /** * Get document properties using a given Session. */ public static Document getProperties(Session session, Node docNode) throws javax.jcr.PathNotFoundException, javax.jcr.RepositoryException { log.debug("getProperties({}, {})", session, docNode); Document doc = new Document(); Node contentNode = docNode.getNode(Document.CONTENT); // Properties doc.setAuthor(docNode.getProperty(Document.AUTHOR).getString()); // TODO Remove this check in OpenKM 6 // if (documentNode.hasProperty(Document.TITLE)) { // doc.setTitle(documentNode.getProperty(Document.TITLE).getPath()); // } doc.setPath(docNode.getPath()); doc.setLocked(docNode.isLocked()); doc.setUuid(docNode.getUUID()); if (doc.isLocked()) { doc.setLockInfo(getLock(session, docNode.getPath())); } else { doc.setLockInfo(null); } doc.setCheckedOut(contentNode.isCheckedOut()); doc.setMimeType(contentNode.getProperty(JcrConstants.JCR_MIMETYPE).getString()); doc.setLastModified(contentNode.getProperty(JcrConstants.JCR_LASTMODIFIED).getDate()); // Get actual version if (docNode.isNodeType(Document.TYPE)) { javax.jcr.version.Version ver = contentNode.getBaseVersion(); Version version = new Version(); version.setAuthor(contentNode.getProperty(Document.AUTHOR).getString()); version.setSize(contentNode.getProperty(Document.SIZE).getLong()); version.setComment(contentNode.getProperty(Document.VERSION_COMMENT).getString()); version.setName(ver.getName()); version.setCreated(ver.getCreated()); version.setActual(true); doc.setActualVersion(version); } // If this is a frozen node, we must get create property from // the original referenced node. if (docNode.isNodeType(JcrConstants.NT_FROZENNODE)) { Node node = docNode.getProperty(JcrConstants.JCR_FROZENUUID).getNode(); doc.setCreated(node.getProperty(JcrConstants.JCR_CREATED).getDate()); } else { doc.setCreated(docNode.getProperty(JcrConstants.JCR_CREATED).getDate()); } // Get permissions if (Config.SYSTEM_READONLY) { doc.setPermissions(Permission.NONE); } else { AccessManager am = ((SessionImpl) session).getAccessManager(); Path path = ((NodeImpl)docNode).getPrimaryPath(); //Path path = ((SessionImpl)session).getHierarchyManager().getPath(((NodeImpl)folderNode).getId()); if (am.isGranted(path, org.apache.jackrabbit.core.security.authorization.Permission.READ)) { doc.setPermissions(Permission.READ); } if (am.isGranted(path, org.apache.jackrabbit.core.security.authorization.Permission.ADD_NODE)) { doc.setPermissions((byte) (doc.getPermissions() | Permission.WRITE)); } if (am.isGranted(path, org.apache.jackrabbit.core.security.authorization.Permission.REMOVE_NODE)) { doc.setPermissions((byte) (doc.getPermissions() | Permission.DELETE)); } if (am.isGranted(path, org.apache.jackrabbit.core.security.authorization.Permission.MODIFY_AC)) { doc.setPermissions((byte) (doc.getPermissions() | Permission.SECURITY)); } } // Get user subscription Set<String> subscriptorSet = new HashSet<String>(); if (docNode.isNodeType(Notification.TYPE)) { Value[] subscriptors = docNode.getProperty(Notification.SUBSCRIPTORS).getValues(); for (int i=0; i<subscriptors.length; i++) { subscriptorSet.add(subscriptors[i].getString()); if (session.getUserID().equals(subscriptors[i].getString())) { doc.setSubscribed(true); } } } doc.setSubscriptors(subscriptorSet); // Get document keywords Set<String> keywordsSet = new HashSet<String>(); Value[] keywords = docNode.getProperty(Property.KEYWORDS).getValues(); for (int i=0; i<keywords.length; i++) { keywordsSet.add(keywords[i].getString()); } doc.setKeywords(keywordsSet); // Get document categories Set<Folder> categoriesSet = new HashSet<Folder>(); Value[] categories = docNode.getProperty(Property.CATEGORIES).getValues(); for (int i=0; i<categories.length; i++) { Node node = session.getNodeByUUID(categories[i].getString()); categoriesSet.add(BaseFolderModule.getProperties(session, node)); } doc.setCategories(categoriesSet); DocConverter convert = DocConverter.getInstance(); doc.setConvertibleToPdf(convert.convertibleToPdf(doc.getMimeType())); doc.setConvertibleToSwf(convert.convertibleToSwf(doc.getMimeType())); doc.setConvertibleToDxf(convert.convertibleToDxf(doc.getMimeType())); // Get notes if (docNode.isNodeType(Note.MIX_TYPE)) { List<Note> notes = new ArrayList<Note>(); Node notesNode = docNode.getNode(Note.LIST); for (NodeIterator nit = notesNode.getNodes(); nit.hasNext(); ) { Node noteNode = nit.nextNode(); Note note = new Note(); note.setDate(noteNode.getProperty(Note.DATE).getDate()); note.setUser(noteNode.getProperty(Note.USER).getString()); note.setText(noteNode.getProperty(Note.TEXT).getString()); note.setPath(noteNode.getPath()); notes.add(note); } doc.setNotes(notes); } // Get crypto if (docNode.isNodeType(Encryption.TYPE)) { String cipherName = docNode.getProperty(Encryption.CIPHER_NAME).getString(); doc.setCipherName(cipherName); } log.debug("Permisos: {} => {}", docNode.getPath(), doc.getPermissions()); log.debug("getProperties[session]: {}", doc); return doc; } /** * Retrieve lock info from a document path */ public static Lock getLock(Session session, String docPath) throws UnsupportedRepositoryOperationException, javax.jcr.lock.LockException, javax.jcr.AccessDeniedException, javax.jcr.RepositoryException { log.debug("getLock({}, {})", session, docPath); Lock lock = new Lock(); Node documentNode = session.getRootNode().getNode(docPath.substring(1)); javax.jcr.lock.Lock lck = documentNode.getLock(); lock.setOwner(lck.getLockOwner()); lock.setNodePath(lck.getNode().getPath()); lock.setToken(lck.getLockToken()); log.debug("getLock: {}", lock); return lock; } /** * Set node content */ public static void setContent(Session session, Node docNode, InputStream is) throws PathNotFoundException, RepositoryException, IOException { long size = is.available(); Node contentNode = docNode.getNode(Document.CONTENT); contentNode.setProperty(Document.SIZE, size); contentNode.setProperty(JcrConstants.JCR_DATA, is); if (Config.EXPERIMENTAL_TEXT_EXTRACTION) { String mimeType = contentNode.getProperty(JcrConstants.JCR_MIMETYPE).getString(); RegisteredExtractors.index(docNode, contentNode, mimeType); } contentNode.setProperty(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance()); contentNode.save(); } /** * Retrieve the content input stream from a document path */ public static InputStream getContent(Session session, String docPath, boolean checkout) throws javax.jcr.PathNotFoundException, javax.jcr.RepositoryException, IOException { Node documentNode = session.getRootNode().getNode(docPath.substring(1)); InputStream is = getContent(session, documentNode); // Activity log UserActivity.log(session.getUserID(), (checkout?"GET_DOCUMENT_CONTENT_CHECKOUT":"GET_DOCUMENT_CONTENT"), documentNode.getUUID(), is.available()+", "+docPath); return is; } /** * Retrieve the content InputStream from a given Node. */ public static InputStream getContent(Session session, Node docNode) throws javax.jcr.PathNotFoundException, javax.jcr.RepositoryException, IOException { log.debug("getContent({}, {})", session, docNode); Node contentNode = docNode.getNode(Document.CONTENT); InputStream is = contentNode.getProperty(JcrConstants.JCR_DATA).getStream(); log.debug("getContent: {}", is); return is; } /** * Remove version history, compute free space and remove obsolete files from * PDF and previsualization cache. */ public static void purge(Session session, Node parentNode, Node docNode) throws javax.jcr.PathNotFoundException, javax.jcr.RepositoryException { Node contentNode = docNode.getNode(Document.CONTENT); long size = contentNode.getProperty(Document.SIZE).getLong(); String author = contentNode.getProperty(Document.AUTHOR).getString(); VersionHistory vh = contentNode.getVersionHistory(); log.debug("VersionHistory UUID: {}", vh.getUUID()); // Remove pdf & preview from cache new File(Config.CACHE_DXF + File.separator + docNode.getUUID() + ".dxf").delete(); new File(Config.CACHE_PDF + File.separator + docNode.getUUID() + ".pdf").delete(); new File(Config.CACHE_SWF + File.separator + docNode.getUUID() + ".swf").delete(); // Remove node itself docNode.remove(); parentNode.save(); // Unreferenced VersionHistory should be deleted automatically // after removal of the last Version // https://issues.apache.org/jira/browse/JCR-134 // http://markmail.org/message/7aildokt74yeoar5 // http://markmail.org/message/nhbwe7o3c7pd4sga // // ********** THIS IS ACCORDING WITH JCR-134 for (VersionIterator vi = vh.getAllVersions(); vi.hasNext(); ) { javax.jcr.version.Version ver = vi.nextVersion(); String versionName = ver.getName(); log.debug("Version: {}", versionName); // The rootVersion is not a "real" version node. if (!versionName.equals(JcrConstants.JCR_ROOTVERSION)) { //Node frozenNode = ver.getNode(JcrConstants.JCR_FROZENNODE); //size = frozenNode.getProperty(Document.SIZE).getLong(); //author = frozenNode.getProperty(Document.AUTHOR).getString(); log.debug("vh.removeVersion({})", versionName); vh.removeVersion(versionName); } } if (Config.USER_ITEM_CACHE) { UserItemsManager.decSize(author, size); UserItemsManager.decDocuments(author, 1); } } /** * Is invoked from DirectDocumentNode and DirectFolderNode. */ public static Node copy(Session session, Node srcDocumentNode, Node dstFolderNode) throws ValueFormatException, javax.jcr.PathNotFoundException, javax.jcr.RepositoryException, IOException, DatabaseException, UserQuotaExceededException { log.debug("copy({}, {}, {})", new Object[] { session, srcDocumentNode, dstFolderNode }); Node srcDocumentContentNode = srcDocumentNode.getNode(Document.CONTENT); String mimeType = srcDocumentContentNode.getProperty("jcr:mimeType").getString(); // String title = srcDocumentContentNode.getProperty(Document.TITLE).getString(); InputStream is = srcDocumentContentNode.getProperty("jcr:data").getStream(); Node newDocument = BaseDocumentModule.create(session, dstFolderNode, srcDocumentNode.getName(), null /* title */, mimeType, new String[]{}, is); is.close(); log.debug("copy: {}", newDocument); return newDocument; } /** * Clean preview cache for this document */ public static void cleanPreviewCache(String uuid) { new File(Config.CACHE_DXF + File.separator + uuid + ".dxf").delete(); new File(Config.CACHE_PDF + File.separator + uuid + ".pdf").delete(); new File(Config.CACHE_SWF + File.separator + uuid + ".swf").delete(); } }