/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bogdan Stefanescu * Florent Guillaume */ package org.eclipse.ecr.core.api; import static org.eclipse.ecr.core.api.security.SecurityConstants.ADD_CHILDREN; import static org.eclipse.ecr.core.api.security.SecurityConstants.BROWSE; import static org.eclipse.ecr.core.api.security.SecurityConstants.READ; import static org.eclipse.ecr.core.api.security.SecurityConstants.READ_CHILDREN; import static org.eclipse.ecr.core.api.security.SecurityConstants.READ_LIFE_CYCLE; import static org.eclipse.ecr.core.api.security.SecurityConstants.READ_PROPERTIES; import static org.eclipse.ecr.core.api.security.SecurityConstants.READ_SECURITY; import static org.eclipse.ecr.core.api.security.SecurityConstants.READ_VERSION; import static org.eclipse.ecr.core.api.security.SecurityConstants.REMOVE; import static org.eclipse.ecr.core.api.security.SecurityConstants.REMOVE_CHILDREN; import static org.eclipse.ecr.core.api.security.SecurityConstants.SYSTEM_USERNAME; import static org.eclipse.ecr.core.api.security.SecurityConstants.UNLOCK; import static org.eclipse.ecr.core.api.security.SecurityConstants.WRITE; import static org.eclipse.ecr.core.api.security.SecurityConstants.WRITE_LIFE_CYCLE; import static org.eclipse.ecr.core.api.security.SecurityConstants.WRITE_PROPERTIES; import static org.eclipse.ecr.core.api.security.SecurityConstants.WRITE_SECURITY; import static org.eclipse.ecr.core.api.security.SecurityConstants.WRITE_VERSION; import java.io.InputStream; import java.io.Serializable; import java.security.Principal; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.ecr.core.CoreService; import org.eclipse.ecr.core.NXCore; import org.eclipse.ecr.core.api.AlreadyConnectedException; import org.eclipse.ecr.core.api.ClientException; import org.eclipse.ecr.core.api.CoreInstance; import org.eclipse.ecr.core.api.CoreSession; import org.eclipse.ecr.core.api.DataModel; import org.eclipse.ecr.core.api.DetachedAdapter; import org.eclipse.ecr.core.api.DocumentException; import org.eclipse.ecr.core.api.DocumentModel; import org.eclipse.ecr.core.api.DocumentModelIterator; import org.eclipse.ecr.core.api.DocumentModelList; import org.eclipse.ecr.core.api.DocumentModelsChunk; import org.eclipse.ecr.core.api.DocumentRef; import org.eclipse.ecr.core.api.DocumentSecurityException; import org.eclipse.ecr.core.api.Filter; import org.eclipse.ecr.core.api.IdRef; import org.eclipse.ecr.core.api.IterableQueryResult; import org.eclipse.ecr.core.api.Lock; import org.eclipse.ecr.core.api.NuxeoPrincipal; import org.eclipse.ecr.core.api.PathRef; import org.eclipse.ecr.core.api.SerializableInputStream; import org.eclipse.ecr.core.api.SimplePrincipal; import org.eclipse.ecr.core.api.Sorter; import org.eclipse.ecr.core.api.VersionModel; import org.eclipse.ecr.core.api.VersioningChangeNotifier; import org.eclipse.ecr.core.api.VersioningOption; import org.eclipse.ecr.core.api.DocumentModel.DocumentModelRefresh; import org.eclipse.ecr.core.api.event.CoreEventConstants; import org.eclipse.ecr.core.api.event.DocumentEventCategories; import org.eclipse.ecr.core.api.event.DocumentEventTypes; import org.eclipse.ecr.core.api.facet.VersioningDocument; import org.eclipse.ecr.core.api.impl.DocsQueryProviderDef; import org.eclipse.ecr.core.api.impl.DocumentModelImpl; import org.eclipse.ecr.core.api.impl.DocumentModelIteratorImpl; import org.eclipse.ecr.core.api.impl.DocumentModelListImpl; import org.eclipse.ecr.core.api.impl.FacetFilter; import org.eclipse.ecr.core.api.impl.UserPrincipal; import org.eclipse.ecr.core.api.impl.VersionModelImpl; import org.eclipse.ecr.core.api.model.DocumentPart; import org.eclipse.ecr.core.api.operation.Operation; import org.eclipse.ecr.core.api.operation.OperationHandler; import org.eclipse.ecr.core.api.operation.ProgressMonitor; import org.eclipse.ecr.core.api.operation.Status; import org.eclipse.ecr.core.api.security.ACP; import org.eclipse.ecr.core.api.security.SecurityConstants; import org.eclipse.ecr.core.api.security.SecuritySummaryEntry; import org.eclipse.ecr.core.api.security.UserEntry; import org.eclipse.ecr.core.api.security.impl.ACPImpl; import org.eclipse.ecr.core.api.security.impl.UserEntryImpl; import org.eclipse.ecr.core.event.Event; import org.eclipse.ecr.core.event.EventService; import org.eclipse.ecr.core.event.impl.DocumentEventContext; import org.eclipse.ecr.core.event.impl.EventContextImpl; import org.eclipse.ecr.core.lifecycle.LifeCycleConstants; import org.eclipse.ecr.core.lifecycle.LifeCycleException; import org.eclipse.ecr.core.lifecycle.LifeCycleService; import org.eclipse.ecr.core.model.Document; import org.eclipse.ecr.core.model.DocumentIterator; import org.eclipse.ecr.core.model.DocumentProxy; import org.eclipse.ecr.core.model.NoSuchDocumentException; import org.eclipse.ecr.core.model.PathComparator; import org.eclipse.ecr.core.model.Session; import org.eclipse.ecr.core.query.FilterableQuery; import org.eclipse.ecr.core.query.Query; import org.eclipse.ecr.core.query.QueryFilter; import org.eclipse.ecr.core.query.QueryParseException; import org.eclipse.ecr.core.query.QueryResult; import org.eclipse.ecr.core.query.sql.model.SQLQuery.Transformer; import org.eclipse.ecr.core.repository.RepositoryInitializationHandler; import org.eclipse.ecr.core.schema.DocumentType; import org.eclipse.ecr.core.schema.NXSchema; import org.eclipse.ecr.core.schema.SchemaManager; import org.eclipse.ecr.core.schema.types.CompositeType; import org.eclipse.ecr.core.schema.types.Schema; import org.eclipse.ecr.core.security.SecurityService; import org.eclipse.ecr.core.utils.SIDGenerator; import org.eclipse.ecr.core.versioning.VersioningService; import org.eclipse.ecr.runtime.api.Framework; import org.eclipse.ecr.runtime.services.streaming.InputStreamSource; import org.eclipse.ecr.runtime.services.streaming.StreamManager; import org.nuxeo.common.collections.ScopeType; import org.nuxeo.common.collections.ScopedMap; import org.nuxeo.common.utils.IdUtils; /** * Abstract implementation of the client interface. * <p> * This handles all the aspects that are independent on the final implementation * (like running inside a J2EE platform or not). * <p> * The only aspect not implemented is the session management that should be * handled by subclasses. * * @author Bogdan Stefanescu * @author Florent Guillaume */ public abstract class AbstractSession implements CoreSession, OperationHandler, Serializable { public static final NuxeoPrincipal ANONYMOUS = new UserPrincipal( "anonymous", new ArrayList<String>(), true, false); private static final Log log = LogFactory.getLog(CoreSession.class); private static final long serialVersionUID = 6585443198474361876L; private static final Comparator<? super Document> pathComparator = new PathComparator(); // the repository name protected String repositoryName; protected Map<String, Serializable> sessionContext; /** * Private access to protected it again direct access since this field is * lazy loaded. You must use {@link #getEventService()} to get the service */ private transient EventService eventService; /** * Used to check permissions. */ private transient SecurityService securityService; protected SecurityService getSecurityService() { if (securityService == null) { securityService = NXCore.getSecurityService(); } return securityService; } private transient VersioningService versioningService; protected VersioningService getVersioningService() { if (versioningService == null) { try { versioningService = Framework.getService(VersioningService.class); } catch (Exception e) { throw new RuntimeException("VersioningService not found", e); } } return versioningService; } /** * Used to resolve core documents based on session. */ protected final DocumentResolver documentResolver = new DocumentResolver(); private String sessionId; /** * Internal method: Gets the current session based on the client session id. * * @return the repository session */ public abstract Session getSession() throws ClientException; @Override public String connect(String repositoryName, Map<String, Serializable> context) throws ClientException { if (null == context) { context = new HashMap<String, Serializable>(); } if (sessionId != null) { throw new AlreadyConnectedException(); } this.repositoryName = repositoryName; sessionContext = context; // this session is valid until the disconnect method is called sessionId = createSessionId(); sessionContext.put("SESSION_ID", sessionId); // register this session locally -> this way document models can // retrieve their session on the server side CoreInstance.getInstance().registerSession(sessionId, this); // <------------ begin repository initialization // we need to initialize the repository if this is the first time it is // accessed in this JVM session. // For this we get the session and test if the // "REPOSITORY_FIRST_ACCESS" is set after the session is created. We // need to synchronize the call to be sure we initialize it only once. synchronized (AbstractSession.class) { Session session = getSession(); // force the creation of the // underlying session if (sessionContext.remove("REPOSITORY_FIRST_ACCESS") != null) { // this is the first time we access the repository in this JVM // notify the InitializationHandler if any. RepositoryInitializationHandler handler = RepositoryInitializationHandler.getInstance(); if (handler != null) { // change principal to give all rights Principal ctxPrincipal = (Principal) sessionContext.get("principal"); try { // change current principal to give all right to the // handler // FIXME: this should be fixed by using // SystemPrincipal => we must synchronize this with // SecurityService check sessionContext.put("principal", new SimplePrincipal( SYSTEM_USERNAME)); try { handler.initializeRepository(this); session.save(); } catch (ClientException e) { // shouldn't remove the root? ... to restart with // an empty repository log.error( "Failed to initialize repository content", e); } catch (DocumentException e) { log.error("Unable to save session after repository init : " + e.getMessage()); } } finally { sessionContext.remove("principal"); if (ctxPrincipal != null) { // restore principal sessionContext.put("principal", (Serializable) ctxPrincipal); } } } } } // <------------- end repository initialization return sessionId; } /** * Default implementation for session ID generation. * <p> * The ID has the following format: * <repository-name>-<JVM-Unique-ID> where the JVM-Unique-ID is * an unique ID on a running JVM and repository-name is a used to avoid name * clashes with sessions on different machines (the repository name should * be unique in the system) * <ul> * <li>A is the repository name (which uniquely identifies the repository in * the system) * <li>B is the time of the session creation in milliseconds * </ul> */ protected String createSessionId() { return repositoryName + '-' + SIDGenerator.next(); } @Override public DocumentType getDocumentType(String type) { return NXSchema.getSchemaManager().getDocumentType(type); } /** * Utility method to generate VersionModel labels. * * @return the String representation of an auto-incremented counter that not * used in any label of docRef */ @Override public String generateVersionLabelFor(DocumentRef docRef) throws ClientException { // find the biggest label that is castable to an int int max = 0; for (VersionModel version : getVersionsForDocument(docRef)) { try { int current = Integer.parseInt(version.getLabel()); max = current > max ? current : max; } catch (NumberFormatException e) { // ignore labels that are not parsable as int } } return Integer.toString(max + 1); } @Override public String getSessionId() { return sessionId; } @Override public Principal getPrincipal() { return ANONYMOUS; } protected final void checkPermission(Document doc, String permission) throws DocumentSecurityException, DocumentException { if (isAdministrator()) { return; } if (!hasPermission(doc, permission)) { throw new DocumentSecurityException("Privilege '" + permission + "' is not granted to '" + getPrincipal().getName() + "'"); } } protected Map<String, Serializable> getContextMapEventInfo(DocumentModel doc) { Map<String, Serializable> options = new HashMap<String, Serializable>(); if (doc != null) { ScopedMap ctxData = doc.getContextData(); if (ctxData != null) { options.putAll(ctxData.getDefaultScopeValues()); options.putAll(ctxData.getScopeValues(ScopeType.REQUEST)); } } return options; } public DocumentEventContext newEventContext(DocumentModel source) { DocumentEventContext ctx = new DocumentEventContext(this, getPrincipal(), source); ctx.setProperty(CoreEventConstants.REPOSITORY_NAME, repositoryName); ctx.setProperty(CoreEventConstants.SESSION_ID, sessionId); return ctx; } public EventService getEventService() { if (eventService == null) { try { eventService = Framework.getLocalService(EventService.class); } catch (Exception e) { throw new RuntimeException("Core Event Service not found", e); } } return eventService; } @Override public void afterBegin() { if (log.isTraceEnabled()) { log.trace("Transaction started"); } try { getEventService().transactionStarted(); } catch (Exception e) { log.error("Error while notifying transaction start", e); } } @Override public void beforeCompletion() { } @Override public void afterCompletion(boolean committed) { if (log.isTraceEnabled()) { log.trace("Transaction " + (committed ? "committed" : "rolled back")); } try { if (committed) { getEventService().transactionCommitted(); } else { getEventService().transactionRolledback(); } } catch (Exception e) { log.error("Error while notifying transaction completion", e); } } public void fireEvent(Event event) throws ClientException { getEventService().fireEvent(event); } protected void notifyEvent(String eventId, DocumentModel source, Map<String, Serializable> options, String category, String comment, boolean withLifeCycle, boolean inline) throws ClientException { DocumentEventContext ctx = new DocumentEventContext(this, getPrincipal(), source); // compatibility with old code (< 5.2.M4) - import info from old event // model if (options != null) { ctx.setProperties(options); } ctx.setProperty(CoreEventConstants.REPOSITORY_NAME, repositoryName); ctx.setProperty(CoreEventConstants.SESSION_ID, sessionId); // Document life cycle if (source != null && withLifeCycle) { String currentLifeCycleState = null; try { currentLifeCycleState = source.getCurrentLifeCycleState(); } catch (ClientException err) { // FIXME no lifecycle -- this shouldn't generated an // exception (and ClientException logs the spurious error) } ctx.setProperty(CoreEventConstants.DOC_LIFE_CYCLE, currentLifeCycleState); } if (comment != null) { ctx.setProperty("comment", comment); } ctx.setProperty( "category", category == null ? DocumentEventCategories.EVENT_DOCUMENT_CATEGORY : category); // compat code: mark SAVE event as a commit event Event event = ctx.newEvent(eventId); if (DocumentEventTypes.SESSION_SAVED.equals(eventId)) { event.setIsCommitEvent(true); } if (inline) { event.setInline(true); } // compat code: set isLocal on event if JMS is blocked if (source != null) { Boolean blockJms = (Boolean) source.getContextData("BLOCK_JMS_PRODUCING"); if (blockJms != null && blockJms) { event.setLocal(true); event.setInline(true); } } fireEvent(event); } /** * Copied from obsolete VersionChangeNotifier. * <p> * Sends change notifications to core event listeners. The event contains * info with older document (before version change) and newer doc (current * document). * * @param oldDocument * @param newDocument * @param options additional info to pass to the event */ protected void notifyVersionChange(DocumentModel oldDocument, DocumentModel newDocument, Map<String, Serializable> options) throws ClientException { final Map<String, Serializable> info = new HashMap<String, Serializable>(); if (options != null) { info.putAll(options); } info.put(VersioningChangeNotifier.EVT_INFO_NEW_DOC_KEY, newDocument); info.put(VersioningChangeNotifier.EVT_INFO_OLD_DOC_KEY, oldDocument); notifyEvent(VersioningChangeNotifier.CORE_EVENT_ID_VERSIONING_CHANGE, newDocument, info, DocumentEventCategories.EVENT_CLIENT_NOTIF_CATEGORY, null, false, false); } @Override public boolean hasPermission(Principal principal, DocumentRef docRef, String permission) throws ClientException { try { Session session = getSession(); Document doc = DocumentResolver.resolveReference(session, docRef); return hasPermission(principal, doc, permission); } catch (DocumentException e) { throw new ClientException("Failed to resolve document ref: " + docRef.toString(), e); } } protected final boolean hasPermission(Principal principal, Document doc, String permission) throws DocumentException { return getSecurityService().checkPermission(doc, principal, permission); } @Override public boolean hasPermission(DocumentRef docRef, String permission) throws ClientException { try { Session session = getSession(); Document doc = DocumentResolver.resolveReference(session, docRef); return hasPermission(doc, permission); } catch (DocumentException e) { throw new ClientException("Failed to resolve document ref: " + docRef.toString(), e); } } protected final boolean hasPermission(Document doc, String permission) throws DocumentException { // TODO: optimize this - usually ACP is already available when calling // this method. // -> cache ACP at securitymanager level or try to reuse the ACP when // it is known return getSecurityService().checkPermission(doc, getPrincipal(), permission); // return doc.getSession().getSecurityManager().checkPermission(doc, // getPrincipal().getName(), permission); } protected final Document resolveReference(DocumentRef docRef) throws DocumentException, ClientException { return DocumentResolver.resolveReference(getSession(), docRef); } /** * Gets the document model for the given core document. * * @param doc the document * @return the document model */ protected DocumentModel readModel(Document doc) throws ClientException { try { return DocumentModelFactory.createDocumentModel(doc, null); } catch (DocumentException e) { throw new ClientException("Failed to create document model", e); } } /** * Gets the document model for the given core document, preserving the contextData. * * @param doc the document * @return the document model */ protected DocumentModel readModel(Document doc, DocumentModel docModel) throws ClientException { DocumentModel newModel = readModel(doc); newModel.copyContextData(docModel); return newModel; } /** * @deprecated unused */ @Deprecated protected DocumentModel readModel(Document doc, String[] schemas) throws ClientException { try { return DocumentModelFactory.createDocumentModel(doc, schemas); } catch (DocumentException e) { throw new ClientException("Failed to create document model", e); } } protected DocumentModel writeModel(Document doc, DocumentModel docModel) throws DocumentException, ClientException { return DocumentModelFactory.writeDocumentModel(docModel, doc); } @Override public DocumentModel copy(DocumentRef src, DocumentRef dst, String name) throws ClientException { try { Document srcDoc = resolveReference(src); Document dstDoc = resolveReference(dst); checkPermission(dstDoc, ADD_CHILDREN); DocumentModel srcDocModel = readModel(srcDoc); notifyEvent(DocumentEventTypes.ABOUT_TO_COPY, srcDocModel, null, null, null, true, true); Document doc = getSession().copy(srcDoc, dstDoc, name); // no need to clear lock, locks table is not copied Map<String, Serializable> options = new HashMap<String, Serializable>(); // notify document created by copy DocumentModel docModel = readModel(doc); String comment = srcDoc.getRepository().getName() + ':' + src.toString(); notifyEvent(DocumentEventTypes.DOCUMENT_CREATED_BY_COPY, docModel, options, null, comment, true, false); docModel = writeModel(doc, docModel); // notify document copied comment = doc.getRepository().getName() + ':' + docModel.getRef().toString(); notifyEvent(DocumentEventTypes.DOCUMENT_DUPLICATED, srcDocModel, options, null, comment, true, false); return docModel; } catch (DocumentException e) { throw new ClientException("Failed to copy document: " + e.getMessage(), e); } } @Override public List<DocumentModel> copy(List<DocumentRef> src, DocumentRef dst) throws ClientException { List<DocumentModel> newDocuments = new ArrayList<DocumentModel>(); for (DocumentRef ref : src) { newDocuments.add(copy(ref, dst, null)); } return newDocuments; } @Override public DocumentModel copyProxyAsDocument(DocumentRef src, DocumentRef dst, String name) throws ClientException { try { Document srcDoc = resolveReference(src); if (!srcDoc.isProxy()) { return copy(src, dst, name); } Document dstDoc = resolveReference(dst); checkPermission(dstDoc, WRITE); // create a new document using the expanded proxy DocumentModel srcDocModel = readModel(srcDoc); String docName = (name != null) ? name : srcDocModel.getName(); DocumentModel docModel = createDocumentModel(dstDoc.getPath(), docName, srcDocModel.getType()); docModel.copyContent(srcDocModel); notifyEvent(DocumentEventTypes.ABOUT_TO_COPY, srcDocModel, null, null, null, true, true); docModel = createDocument(docModel); Document doc = resolveReference(docModel.getRef()); Map<String, Serializable> options = new HashMap<String, Serializable>(); // notify document created by copy String comment = srcDoc.getRepository().getName() + ':' + src.toString(); notifyEvent(DocumentEventTypes.DOCUMENT_CREATED_BY_COPY, docModel, options, null, comment, true, false); // notify document copied comment = doc.getRepository().getName() + ':' + docModel.getRef().toString(); notifyEvent(DocumentEventTypes.DOCUMENT_DUPLICATED, srcDocModel, options, null, comment, true, false); return docModel; } catch (DocumentException e) { throw new ClientException("Failed to copy document: " + e.getMessage(), e); } } @Override public List<DocumentModel> copyProxyAsDocument(List<DocumentRef> src, DocumentRef dst) throws ClientException { List<DocumentModel> newDocuments = new ArrayList<DocumentModel>(); for (DocumentRef ref : src) { newDocuments.add(copyProxyAsDocument(ref, dst, null)); } return newDocuments; } @Override public DocumentModel move(DocumentRef src, DocumentRef dst, String name) throws ClientException { try { Document srcDoc = resolveReference(src); Document dstDoc = resolveReference(dst); checkPermission(dstDoc, ADD_CHILDREN); checkPermission(srcDoc.getParent(), REMOVE_CHILDREN); checkPermission(srcDoc, REMOVE); DocumentModel srcDocModel = readModel(srcDoc); notifyEvent(DocumentEventTypes.ABOUT_TO_MOVE, srcDocModel, null, null, null, true, true); String comment = srcDoc.getRepository().getName() + ':' + srcDoc.getParent().getUUID(); if (name == null) { name = srcDoc.getName(); } name = generateDocumentName(dstDoc, name); Document doc = getSession().move(srcDoc, dstDoc, name); // notify document moved DocumentModel docModel = readModel(doc); Map<String, Serializable> options = new HashMap<String, Serializable>(); options.put(CoreEventConstants.PARENT_PATH, srcDocModel.getParentRef()); notifyEvent(DocumentEventTypes.DOCUMENT_MOVED, docModel, options, null, comment, true, false); return docModel; } catch (DocumentException e) { throw new ClientException("Failed to move document: " + e.getMessage(), e); } } @Override public void move(List<DocumentRef> src, DocumentRef dst) throws ClientException { for (DocumentRef ref : src) { move(ref, dst, null); } } @Override public ACP getACP(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ_SECURITY); return getSession().getSecurityManager().getMergedACP(doc); } catch (DocumentException e) { throw new ClientException("Failed to get acp", e); } } @Override public void setACP(DocumentRef docRef, ACP newAcp, boolean overwrite) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, WRITE_SECURITY); DocumentModel docModel = readModel(doc); Map<String, Serializable> options = new HashMap<String, Serializable>(); options.put(CoreEventConstants.OLD_ACP, (Serializable) docModel.getACP().clone()); options.put(CoreEventConstants.NEW_ACP, (Serializable) newAcp.clone()); notifyEvent(DocumentEventTypes.BEFORE_DOC_SECU_UPDATE, docModel, options, null, null, true, true); getSession().getSecurityManager().setACP(doc, newAcp, overwrite); docModel = readModel(doc); notifyEvent(DocumentEventTypes.DOCUMENT_SECURITY_UPDATED, docModel, options, null, null, true, false); } catch (DocumentException e) { throw new ClientException("Failed to set acp", e); } } @Override public void cancel() throws ClientException { try { getSession().cancel(); } catch (DocumentException e) { throw new ClientException("Failed to cancel session", e); } } private DocumentModel createDocumentModelFromTypeName(String typeName, Map<String, Serializable> options) throws ClientException { try { DocumentType docType = getSession().getTypeManager().getDocumentType( typeName); if (docType == null) { throw new ClientException(typeName + " is not a registered core type"); } DocumentModel docModel = DocumentModelFactory.createDocumentModel( sessionId, docType); if (options == null) { options = new HashMap<String, Serializable>(); } // do not forward this event on the JMS Bus options.put("BLOCK_JMS_PRODUCING", true); notifyEvent(DocumentEventTypes.EMPTY_DOCUMENTMODEL_CREATED, docModel, options, null, null, false, true); return docModel; } catch (DocumentException e) { throw new ClientException("Failed to create document model", e); } } @Override public DocumentModel createDocumentModel(String typeName) throws ClientException { Map<String, Serializable> options = new HashMap<String, Serializable>(); return createDocumentModelFromTypeName(typeName, options); } @Override public DocumentModel createDocumentModel(String parentPath, String id, String typeName) throws ClientException { Map<String, Serializable> options = new HashMap<String, Serializable>(); options.put(CoreEventConstants.PARENT_PATH, parentPath); options.put(CoreEventConstants.DOCUMENT_MODEL_ID, id); DocumentModel model = createDocumentModelFromTypeName(typeName, options); model.setPathInfo(parentPath, id); return model; } @Override public DocumentModel createDocumentModel(String typeName, Map<String, Object> options) throws ClientException { Map<String, Serializable> serializableOptions = new HashMap<String, Serializable>(); for (Entry<String, Object> entry : options.entrySet()) { serializableOptions.put(entry.getKey(), (Serializable) entry.getValue()); } return createDocumentModelFromTypeName(typeName, serializableOptions); } @Override public DocumentModel createDocument(DocumentModel docModel) throws ClientException { String typeName = docModel.getType(); DocumentRef parentRef = docModel.getParentRef(); if (typeName == null) { throw new ClientException(String.format( "cannot create document '%s' with undefined type name", docModel.getTitle())); } if (parentRef == null && !isAdministrator()) { throw new ClientException( "Only Administrators can create placeless documents"); } try { Document folder = parentRef == null ? null : resolveReference(parentRef); if (folder != null) { checkPermission(folder, ADD_CHILDREN); } // get initial life cycle state info String initialLifecycleState = null; Object lifecycleStateInfo = docModel.getContextData(LifeCycleConstants.INITIAL_LIFECYCLE_STATE_OPTION_NAME); if (lifecycleStateInfo instanceof String) { initialLifecycleState = (String) lifecycleStateInfo; } Map<String, Serializable> options = getContextMapEventInfo(docModel); notifyEvent(DocumentEventTypes.ABOUT_TO_CREATE, docModel, options, null, null, false, true); // no lifecycle yet String name = docModel.getName(); name = generateDocumentName(folder, name); if (folder == null) { folder = getSession().getNullDocument(); } Document doc = folder.addChild(name, typeName); // init document life cycle LifeCycleService service = NXCore.getLifeCycleService(); if (service != null) { try { service.initialize(doc, initialLifecycleState); } catch (Exception e) { throw new ClientException( "Failed to initialize document lifecycle", e); } } else { log.debug("No lifecycle service registered"); } // init document with data from doc model docModel = writeModel(doc, docModel); if (!Boolean.TRUE.equals(docModel.getContextData(ScopeType.REQUEST, VersioningService.SKIP_VERSIONING))) { // during remote publishing we want to skip versioning // to avoid overwriting the version number getVersioningService().doPostCreate(doc, options); docModel = readModel(doc, docModel); } notifyEvent(DocumentEventTypes.DOCUMENT_CREATED, docModel, options, null, null, true, false); docModel = writeModel(doc, docModel); return docModel; } catch (DocumentException e) { throw new ClientException("Failed to create document: " + docModel.getName(), e); } } @Override public void importDocuments(List<DocumentModel> docModels) throws ClientException { try { for (DocumentModel docModel : docModels) { importDocument(docModel); } } catch (DocumentException e) { throw new ClientException("Failed to import documents", e); } } protected static final PathRef EMPTY_PATH = new PathRef(""); protected void importDocument(DocumentModel docModel) throws DocumentException, ClientException { if (!isAdministrator()) { throw new DocumentSecurityException("Only Administrator can import"); } String name = docModel.getName(); if (name == null || name.length() == 0) { throw new IllegalArgumentException("Invalid empty name"); } String typeName = docModel.getType(); if (typeName == null || typeName.length() == 0) { throw new IllegalArgumentException("Invalid empty type"); } String id = docModel.getId(); if (id == null || id.length() == 0) { throw new IllegalArgumentException("Invalid empty id"); } DocumentRef parentRef = docModel.getParentRef(); Document parent = parentRef == null || EMPTY_PATH.equals(parentRef) ? null : resolveReference(parentRef); Map<String, Serializable> props = docModel.getContextData().getDefaultScopeValues(); if (parent != null) { name = generateDocumentName(parent, name); } // create the document Document doc = getSession().importDocument(id, parent, name, typeName, props); if (typeName.equals(CoreSession.IMPORT_PROXY_TYPE)) { // just reread the final document docModel = readModel(doc); } else { // init document with data from doc model docModel = writeModel(doc, docModel); } // send an event about the import notifyEvent(DocumentEventTypes.DOCUMENT_IMPORTED, docModel, null, null, null, true, false); } /** * Generate a non-null unique name within given parent's children. * <p> * If name is null, a name is generated. If name is already used, a random * suffix is appended to it. * * @return a unique name within given parent's children */ public String generateDocumentName(Document parent, String name) throws DocumentException { if (name == null || name.length() == 0) { name = IdUtils.generateStringId(); } if (parent != null && parent.hasChild(name)) { name += '.' + String.valueOf(System.currentTimeMillis()); } return name; } @Override public DocumentModel[] createDocument(DocumentModel[] docModels) throws ClientException { DocumentModel[] models = new DocumentModel[docModels.length]; int i = 0; // TODO: optimize this (do not call at each iteration createDocument()) for (DocumentModel docModel : docModels) { models[i++] = createDocument(docModel); } return models; } public abstract boolean isSessionAlive(); @Override public void disconnect() throws ClientException { if (isSessionAlive()) { getSession().dispose(); } if (sessionId != null) { CoreInstance.getInstance().unregisterSession(sessionId); } sessionContext = null; sessionId = null; repositoryName = null; } @Override public boolean exists(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); return hasPermission(doc, BROWSE); } catch (NoSuchDocumentException e) { return false; } catch (DocumentException e) { throw new ClientException("Failed to check existence of " + docRef, e); } } @Override public DocumentModel getChild(DocumentRef parent, String name) throws ClientException { try { Document doc = resolveReference(parent); checkPermission(doc, READ_CHILDREN); Document child = doc.getChild(name); checkPermission(child, READ); return readModel(child); } catch (DocumentException e) { throw new ClientException("Failed to get child " + name, e); } } @Override public DocumentModelList getChildren(DocumentRef parent) throws ClientException { return getChildren(parent, null, READ, null, null); } @Override public DocumentModelIterator getChildrenIterator(DocumentRef parent) throws ClientException { DocsQueryProviderDef def = new DocsQueryProviderDef( DocsQueryProviderDef.DefType.TYPE_CHILDREN); def.setParent(parent); return new DocumentModelIteratorImpl(this, 15, def, null, READ, null); } @Override public DocumentModelList getChildren(DocumentRef parent, String type) throws ClientException { return getChildren(parent, type, READ, null, null); } @Override public DocumentModelIterator getChildrenIterator(DocumentRef parent, String type) throws ClientException { DocsQueryProviderDef def = new DocsQueryProviderDef( DocsQueryProviderDef.DefType.TYPE_CHILDREN); def.setParent(parent); return new DocumentModelIteratorImpl(this, 15, def, type, null, null); } @Override public DocumentModelList getChildren(DocumentRef parent, String type, String perm) throws ClientException { return getChildren(parent, type, perm, null, null); } @Override public DocumentModelList getChildren(DocumentRef parent, String type, Filter filter, Sorter sorter) throws ClientException { return getChildren(parent, type, null, filter, sorter); } @Override public DocumentModelList getChildren(DocumentRef parent, String type, String perm, Filter filter, Sorter sorter) throws ClientException { try { if (perm == null) { perm = READ; } Document doc = resolveReference(parent); checkPermission(doc, READ_CHILDREN); Iterator<Document> children = doc.getChildren(); DocumentModelList docs = new DocumentModelListImpl(); while (children.hasNext()) { Document child = children.next(); if (hasPermission(child, perm)) { if (child.getType() != null && (type == null || type.equals(child.getType().getName()))) { DocumentModel childModel = readModel(child); if (filter == null || filter.accept(childModel)) { docs.add(childModel); } } } } if (sorter != null) { Collections.sort(docs, sorter); } return docs; } catch (DocumentException e) { throw new ClientException("Failed to get children for " + parent.toString(), e); } } @Override public List<DocumentRef> getChildrenRefs(DocumentRef parentRef, String perm) throws ClientException { if (perm != null) { // XXX TODO throw new ClientException("perm != null not implemented"); } try { Document parent = resolveReference(parentRef); checkPermission(parent, READ_CHILDREN); List<String> ids = parent.getChildrenIds(); List<DocumentRef> refs = new ArrayList<DocumentRef>(ids.size()); for (String id : ids) { refs.add(new IdRef(id)); } return refs; } catch (DocumentException e) { throw new ClientException(e); } } /** * Method used internally to retrieve frames of a long result. */ @Override public DocumentModelsChunk getDocsResultChunk(DocsQueryProviderDef def, String type, String perm, Filter filter, final int start, final int max) throws ClientException { // convention: if count == 0 return all results to the end if (max < 0) { throw new IllegalArgumentException("invalid count=" + max); } int count = max; DocsQueryProviderFactory dqpFactory = new DocsQueryProviderFactory(this); try { if (perm == null) { perm = READ; } DocsQueryProvider dqp = dqpFactory.getDQLbyType(def); // Document doc = resolveReference(parent); // checkPermission(doc, READ_CHILDREN); // Iterator<Document> children = doc.getChildren(start); final DocumentIterator children = dqp.getDocs(start); DocumentModelList docs = new DocumentModelListImpl(); int lastIndex = start; boolean hasMore = false; while (children.hasNext()) { lastIndex++; Document child = children.next(); // 1st filter: if (!dqp.accept(child)) { continue; } if (hasPermission(child, perm)) { if (child.getType() != null && (type == null || type.equals(child.getType().getName()))) { DocumentModel childModel = readModel(child); if (filter == null || filter.accept(childModel)) { if (count == 0) { // end of page hasMore = true; break; } count--; docs.add(childModel); } } } } final long total = children.getSize(); return new DocumentModelsChunk(docs, lastIndex - 1, hasMore, total); } catch (DocumentException e) { if (def.getParent() != null) { throw new ClientException("Failed to get children for " + def.getParent().toString(), e); } else { throw new ClientException("Failed to get documents for query: " + def.getQuery(), e); } } } @Override public DocumentModelIterator getChildrenIterator(DocumentRef parent, String type, String perm, Filter filter) throws ClientException { DocsQueryProviderDef def = new DocsQueryProviderDef( DocsQueryProviderDef.DefType.TYPE_CHILDREN); def.setParent(parent); return new DocumentModelIteratorImpl(this, 15, def, type, perm, filter); } @Override public DocumentModel getDocument(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ); return readModel(doc); } catch (DocumentException e) { throw new ClientException("Failed to get document " + docRef.toString(), e); } } /** * @deprecated unused */ @Override @Deprecated public DocumentModel getDocument(DocumentRef docRef, String[] schemas) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ); return readModel(doc, schemas); } catch (DocumentException e) { throw new ClientException("Failed to get document " + docRef, e); } } @Override public DocumentModelList getDocuments(DocumentRef[] docRefs) throws ClientException { List<DocumentModel> docs = new ArrayList<DocumentModel>(docRefs.length); for (DocumentRef docRef : docRefs) { Document doc; try { doc = resolveReference(docRef); checkPermission(doc, READ); } catch (DocumentException e) { // no permission, or other low-level error continue; } docs.add(readModel(doc)); } return new DocumentModelListImpl(docs); } @Override public DocumentModelList getFiles(DocumentRef parent) throws ClientException { try { Document doc = resolveReference(parent); checkPermission(doc, READ_CHILDREN); Iterator<Document> children = doc.getChildren(); DocumentModelList docs = new DocumentModelListImpl(); while (children.hasNext()) { Document child = children.next(); if (!child.isFolder() && hasPermission(child, READ)) { docs.add(readModel(child)); } } return docs; } catch (DocumentException e) { throw new ClientException("Failed to get leaf children for " + parent.toString(), e); } } @Override public DocumentModelIterator getFilesIterator(DocumentRef parent) throws ClientException { DocsQueryProviderDef def = new DocsQueryProviderDef( DocsQueryProviderDef.DefType.TYPE_CHILDREN_NON_FOLDER); def.setParent(parent); return new DocumentModelIteratorImpl(this, 15, def, null, null, null); } @Override public DocumentModelList getFiles(DocumentRef parent, Filter filter, Sorter sorter) throws ClientException { try { Document doc = resolveReference(parent); checkPermission(doc, READ_CHILDREN); Iterator<Document> children = doc.getChildren(); DocumentModelList docs = new DocumentModelListImpl(); while (children.hasNext()) { Document child = children.next(); if (!child.isFolder() && hasPermission(child, READ)) { DocumentModel docModel = readModel(doc); if (filter == null || filter.accept(docModel)) { docs.add(readModel(child)); } } } if (sorter != null) { Collections.sort(docs, sorter); } return docs; } catch (DocumentException e) { throw new ClientException("Failed to get files for " + parent.toString(), e); } } @Override public DocumentModelList getFolders(DocumentRef parent) throws ClientException { try { Document doc = resolveReference(parent); checkPermission(doc, READ_CHILDREN); Iterator<Document> children = doc.getChildren(); DocumentModelList docs = new DocumentModelListImpl(); while (children.hasNext()) { Document child = children.next(); if (child.isFolder() && hasPermission(child, READ)) { docs.add(readModel(child)); } } return docs; } catch (DocumentException e) { throw new ClientException("Failed to get folders " + parent, e); } } @Override public DocumentModelIterator getFoldersIterator(DocumentRef parent) throws ClientException { DocsQueryProviderDef def = new DocsQueryProviderDef( DocsQueryProviderDef.DefType.TYPE_CHILDREN_FOLDERS); def.setParent(parent); return new DocumentModelIteratorImpl(this, 15, def, null, null, null); } @Override public DocumentModelList getFolders(DocumentRef parent, Filter filter, Sorter sorter) throws ClientException { try { Document doc = resolveReference(parent); checkPermission(doc, READ_CHILDREN); Iterator<Document> children = doc.getChildren(); DocumentModelList docs = new DocumentModelListImpl(); while (children.hasNext()) { Document child = children.next(); if (child.isFolder() && hasPermission(child, READ)) { DocumentModel docModel = readModel(doc); if (filter == null || filter.accept(docModel)) { docs.add(readModel(child)); } } } if (sorter != null) { Collections.sort(docs, sorter); } return docs; } catch (DocumentException e) { throw new ClientException("Failed to get folders " + parent.toString(), e); } } @Override public DocumentRef getParentDocumentRef(DocumentRef docRef) throws ClientException { try { final Document doc = resolveReference(docRef); Document parentDoc = doc.getParent(); return parentDoc != null ? new IdRef(parentDoc.getUUID()) : null; } catch (DocumentException e) { throw new ClientException("Failed to get parent document ref for: " + docRef, e); } } @Override public DocumentModel getParentDocument(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); Document parentDoc = doc.getParent(); if (parentDoc == null) { return null; } if (!hasPermission(parentDoc, READ)) { throw new DocumentSecurityException( "Privilege READ is not granted to " + getPrincipal().getName()); } return readModel(parentDoc); } catch (DocumentException e) { throw new ClientException("Failed to get parent document of " + docRef, e); } } @Override public List<DocumentModel> getParentDocuments(final DocumentRef docRef) throws ClientException { if (null == docRef) { throw new IllegalArgumentException("null docRef"); } final List<DocumentModel> docsList = new ArrayList<DocumentModel>(); try { Document doc = resolveReference(docRef); String rootPath = getSession().getRootDocument().getPath(); while (!doc.getPath().equals(rootPath)) { // XXX OG: shouldn't we check BROWSE and READ_PROPERTIES // instead? if (!hasPermission(doc, READ)) { break; } docsList.add(readModel(doc)); doc = doc.getParent(); } } catch (DocumentException e) { throw new ClientException("Failed to get parent documents: " + docRef, e); } Collections.reverse(docsList); return docsList; } @Override public DocumentModel getRootDocument() throws ClientException { try { return readModel(getSession().getRootDocument()); } catch (DocumentException e) { throw new ClientException("Failed to get the root document", e); } } @Override public boolean hasChildren(DocumentRef docRef) throws ClientException { try { // TODO: validate permission check with td Document doc = resolveReference(docRef); checkPermission(doc, BROWSE); return doc.hasChildren(); } catch (DocumentException e) { throw new ClientException("Failed to check for children for " + docRef, e); } } @Override public DocumentModelList query(String query) throws ClientException { return query(query, null, 0, 0, false); } @Override public DocumentModelList query(String query, int max) throws ClientException { return query(query, null, max, 0, false); } @Override public DocumentModelList query(String query, Filter filter) throws ClientException { return query(query, filter, 0, 0, false); } @Override public DocumentModelList query(String query, Filter filter, int max) throws ClientException { return query(query, filter, max, 0, false); } @Override @SuppressWarnings("null") public DocumentModelList query(String query, Filter filter, long limit, long offset, boolean countTotal) throws ClientException { SecurityService securityService = getSecurityService(); Principal principal = getPrincipal(); try { Query compiledQuery = getSession().createQuery(query, Query.Type.NXQL); QueryResult results; boolean postFilterPermission; boolean postFilterFilter; boolean postFilterPolicies; boolean postFilter; String permission = BROWSE; if (compiledQuery instanceof FilterableQuery) { postFilterPermission = false; String repoName = getRepositoryName(); postFilterPolicies = !securityService.arePoliciesExpressibleInQuery(repoName); postFilterFilter = filter != null && !(filter instanceof FacetFilter); postFilter = postFilterPolicies || postFilterFilter; String[] principals; if (isAdministrator()) { principals = null; // means: no security check needed } else { principals = SecurityService.getPrincipalsToCheck(principal); } String[] permissions = securityService.getPermissionsToCheck(permission); QueryFilter queryFilter = new QueryFilter(principal, principals, permissions, filter instanceof FacetFilter ? (FacetFilter) filter : null, securityService.getPoliciesQueryTransformers(repoName), postFilter ? 0 : limit, postFilter ? 0 : offset); results = ((FilterableQuery) compiledQuery).execute( queryFilter, countTotal && !postFilter); } else { postFilterPermission = true; postFilterPolicies = securityService.arePoliciesRestrictingPermission(permission); postFilterFilter = filter != null; postFilter = true; results = compiledQuery.execute(); } DocumentModelList dms = results.getDocumentModels(); if (!postFilter) { // the backend has done all the needed filtering return dms; } // post-filter the results "by hand", the backend couldn't do it long start = limit == 0 || offset < 0 ? 0 : offset; long stop = start + (limit == 0 ? dms.size() : limit); int n = 0; DocumentModelListImpl docs = new DocumentModelListImpl(); for (DocumentModel model : dms) { if (postFilterPermission || postFilterPolicies) { if (!hasPermission(model.getRef(), permission)) { continue; } } if (postFilterFilter) { if (!filter.accept(model)) { continue; } } if (n < start) { n++; continue; } if (n >= stop) { if (!countTotal) { // can break early break; } n++; continue; } n++; docs.add(model); } if (countTotal) { docs.setTotalSize(n); } return docs; } catch (Exception e) { throw new ClientException("Failed to execute query: " + tryToExtractMeaningfulErrMsg(e), e); } } @Override public IterableQueryResult queryAndFetch(String query, String queryType, Object... params) throws ClientException { try { SecurityService securityService = getSecurityService(); Principal principal = getPrincipal(); String[] principals; if (isAdministrator()) { principals = null; // means: no security check needed } else { principals = SecurityService.getPrincipalsToCheck(principal); } String permission = BROWSE; String[] permissions = securityService.getPermissionsToCheck(permission); Collection<Transformer> transformers; if ("NXQL".equals(queryType)) { String repoName = getRepositoryName(); if (!securityService.arePoliciesExpressibleInQuery(repoName)) { log.warn("Security policy cannot be expressed in query"); } transformers = securityService.getPoliciesQueryTransformers(repoName); } else { transformers = Collections.emptyList(); } QueryFilter queryFilter = new QueryFilter(principal, principals, permissions, null, transformers, 0, 0); return getSession().queryAndFetch(query, queryType, queryFilter, params); } catch (Exception e) { throw new ClientException("Failed to execute query: " + queryType + ": " + query + ": " + tryToExtractMeaningfulErrMsg(e), e); } } @Override public DocumentModelIterator queryIt(String query, Filter filter, int max) throws ClientException { DocsQueryProviderDef def = new DocsQueryProviderDef( DocsQueryProviderDef.DefType.TYPE_QUERY); def.setQuery(query); return new DocumentModelIteratorImpl(this, 15, def, null, BROWSE, filter); } private String tryToExtractMeaningfulErrMsg(Throwable t) { if (t instanceof QueryParseException) { return t.getMessage(); } if (t.getCause() != null) { return tryToExtractMeaningfulErrMsg(t.getCause()); } return t.getMessage(); } @Override @Deprecated public DocumentModelList querySimpleFts(String keywords) throws ClientException { return querySimpleFts(keywords, null); } @Override @Deprecated public DocumentModelList querySimpleFts(String keywords, Filter filter) throws ClientException { try { // TODO this is hardcoded query : need to add support for CONTAINS // in NXQL // TODO check (repair) for keywords sanity to avoid xpath injection final String xpathQ = "//element(*, ecmnt:document)[jcr:contains(.,'*" + keywords + "*')]"; final Query compiledQuery = getSession().createQuery(xpathQ, Query.Type.XPATH); final QueryResult qr = compiledQuery.execute(); final DocumentModelList retrievedDocs = qr.getDocumentModels(); final DocumentModelList docs = new DocumentModelListImpl(); for (DocumentModel model : retrievedDocs) { if (hasPermission(model.getRef(), READ)) { if (filter == null || filter.accept(model)) { docs.add(model); } } } return docs; } catch (Exception e) { log.error("failed to execute query", e); throw new ClientException("Failed to get the root document", e); } } @Override @Deprecated public DocumentModelIterator querySimpleFtsIt(String query, Filter filter, int pageSize) throws ClientException { return querySimpleFtsIt(query, null, filter, pageSize); } @Override @Deprecated public DocumentModelIterator querySimpleFtsIt(String query, String startingPath, Filter filter, int pageSize) throws ClientException { DocsQueryProviderDef def = new DocsQueryProviderDef( DocsQueryProviderDef.DefType.TYPE_QUERY_FTS); def.setQuery(query); def.setStartingPath(startingPath); return new DocumentModelIteratorImpl(this, pageSize, def, null, BROWSE, filter); } @Override public void removeChildren(DocumentRef docRef) throws ClientException { // TODO: check req permissions with td try { Document doc = resolveReference(docRef); checkPermission(doc, REMOVE_CHILDREN); Iterator<Document> children = doc.getChildren(); while (children.hasNext()) { // iterator remove method is not supported by jcr Document child = children.next(); if (hasPermission(child, REMOVE)) { removeNotifyOneDoc(child); } } } catch (DocumentException e) { throw new ClientException( "Failed to remove children for " + docRef, e); } } @Override public boolean canRemoveDocument(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); return canRemoveDocument(doc); } catch (DocumentException e) { throw new ClientException(e); } } protected boolean canRemoveDocument(Document doc) throws ClientException, DocumentException { if (isAdministrator()) { return true; } if (doc.isVersion()) { // TODO a hasProxies method would be more efficient Collection<Document> proxies = getSession().getProxies(doc, null); if (!proxies.isEmpty()) { return false; } // find a working document to check security Document working; try { working = doc.getSourceDocument(); } catch (DocumentException e) { working = null; } if (working != null) { return hasPermission(working, WRITE_VERSION); } else { // no working document, only admins can remove return false; } } else { if (!hasPermission(doc, REMOVE)) { return false; } Document parent = doc.getParent(); return parent == null || hasPermission(parent, REMOVE_CHILDREN); } } @Override public void removeDocument(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); removeDocument(doc); } catch (DocumentException e) { throw new ClientException("Failed to fetch document " + docRef + " before removal", e); } } protected void removeDocument(Document doc) throws ClientException { try { if (!canRemoveDocument(doc)) { throw new DocumentSecurityException( "Permission denied: cannot remove document " + doc.getUUID()); } removeNotifyOneDoc(doc); } catch (DocumentException e) { throw new ClientException("Failed to remove document " + doc.getUUID(), e); } } protected void removeNotifyOneDoc(Document doc) throws ClientException, DocumentException { // XXX notify with options if needed DocumentModel docModel = readModel(doc); Map<String, Serializable> options = new HashMap<String, Serializable>(); if (docModel != null) { options.put("docTitle", docModel.getTitle()); } String versionLabel = ""; Document sourceDoc = null; // notify different events depending on wether the document is a // version or not if (!doc.isVersion()) { notifyEvent(DocumentEventTypes.ABOUT_TO_REMOVE, docModel, options, null, null, true, true); CoreService coreService = Framework.getLocalService(CoreService.class); coreService.getVersionRemovalPolicy().removeVersions(getSession(), doc, this); } else { versionLabel = docModel.getVersionLabel(); try { sourceDoc = doc.getSourceDocument(); } catch (DocumentException e) { sourceDoc = null; } notifyEvent(DocumentEventTypes.ABOUT_TO_REMOVE_VERSION, docModel, options, null, null, true, true); } doc.remove(); if (doc.isVersion()) { if (sourceDoc != null) { DocumentModel sourceDocModel = readModel(sourceDoc); if (sourceDocModel != null) { options.put("comment", versionLabel); // to be used by audit // service notifyEvent(DocumentEventTypes.VERSION_REMOVED, sourceDocModel, options, null, null, false, false); options.remove("comment"); } options.put("docSource", sourceDoc.getUUID()); } } notifyEvent(DocumentEventTypes.DOCUMENT_REMOVED, docModel, options, null, null, false, false); } /** * Implementation uses the fact that the lexicographic ordering of paths is * a refinement of the "contains" partial ordering. */ @Override public void removeDocuments(DocumentRef[] docRefs) throws ClientException { Document[] docs = new Document[docRefs.length]; for (int i = 0; i < docs.length; i++) { try { docs[i] = resolveReference(docRefs[i]); } catch (DocumentException e) { throw new ClientException("Failed to resolve reference " + docRefs[i], e); } } // TODO OPTIM: it's not guaranteed that getPath is cheap and // we call it a lot. Should use an object for pairs (document, path) // to call it just once per doc. Arrays.sort(docs, pathComparator); String[] paths = new String[docs.length]; try { for (int i = 0; i < docs.length; i++) { paths[i] = docs[i].getPath(); } String latestRemoved = null; for (int i = 0; i < docs.length; i++) { if (i == 0 || !paths[i].startsWith(latestRemoved + "/")) { removeDocument(docs[i]); latestRemoved = paths[i]; } } } catch (DocumentException e) { throw new ClientException("Failed to get path of document", e); } } @Override public void save() throws ClientException { try { final Map<String, Serializable> options = new HashMap<String, Serializable>(); getSession().save(); notifyEvent(DocumentEventTypes.SESSION_SAVED, null, options, null, null, true, false); } catch (DocumentException e) { throw new ClientException("Failed to save session", e); } } @Override public DocumentModel saveDocument(DocumentModel docModel) throws ClientException { try { if (docModel.getRef() == null) { throw new ClientException(String.format( "cannot save document '%s' with null reference: " + "document has probably not yet been created " + "in the repository with " + "'CoreSession.createDocument(docModel)'", docModel.getTitle())); } Document doc = resolveReference(docModel.getRef()); checkPermission(doc, WRITE_PROPERTIES); Map<String, Serializable> options = getContextMapEventInfo(docModel); if (!docModel.isImmutable()) { // regular event, last chance to modify docModel notifyEvent(DocumentEventTypes.BEFORE_DOC_UPDATE, docModel, options, null, null, true, true); } VersioningOption versioningOption = (VersioningOption) docModel.getContextData(VersioningService.VERSIONING_OPTION); docModel.putContextData(VersioningService.VERSIONING_OPTION, null); String checkinComment = (String) docModel.getContextData(VersioningService.CHECKIN_COMMENT); docModel.putContextData(VersioningService.CHECKIN_COMMENT, null); // compat boolean snapshot = Boolean.TRUE.equals(docModel.getContextData( ScopeType.REQUEST, VersioningDocument.CREATE_SNAPSHOT_ON_SAVE_KEY)); docModel.putContextData(ScopeType.REQUEST, VersioningDocument.CREATE_SNAPSHOT_ON_SAVE_KEY, null); boolean dirty = isDirty(docModel); if (versioningOption == null && snapshot && dirty) { String key = String.valueOf(docModel.getContextData( ScopeType.REQUEST, VersioningDocument.KEY_FOR_INC_OPTION)); docModel.putContextData(ScopeType.REQUEST, VersioningDocument.KEY_FOR_INC_OPTION, null); versioningOption = "inc_major".equals(key) ? VersioningOption.MAJOR : VersioningOption.MINOR; } if (!docModel.isImmutable()) { // pre-save versioning versioningOption = getVersioningService().doPreSave(doc, dirty, versioningOption, checkinComment, options); } // actual save docModel = writeModel(doc, docModel); Document checkedInDoc = null; if (!docModel.isImmutable()) { // post-save versioning checkedInDoc = getVersioningService().doPostSave(doc, versioningOption, checkinComment, options); } // post-save events docModel = readModel(doc); if (checkedInDoc != null) { DocumentRef checkedInVersionRef = new IdRef( checkedInDoc.getUUID()); notifyCheckedInVersion(docModel, checkedInVersionRef, options, checkinComment); } notifyEvent(DocumentEventTypes.DOCUMENT_UPDATED, docModel, options, null, null, true, false); return docModel; } catch (DocumentException e) { throw new ClientException("Failed to save document " + docModel, e); } } @Override @Deprecated public boolean isDirty(DocumentRef docRef) throws ClientException { try { return resolveReference(docRef).isCheckedOut(); } catch (DocumentException e) { throw new ClientException(e); } } /** * Checks if the document has actual data to write (dirty parts). * <p> * Used to avoid doing an auto-checkout if there's nothing to update. */ protected boolean isDirty(DocumentModel doc) throws ClientException { // get loaded data models for (DocumentPart part : doc.getParts()) { if (part.isDirty()) { return true; } } return false; } @Override public void saveDocuments(DocumentModel[] docModels) throws ClientException { // TODO: optimize this - avoid calling at each iteration saveDoc... for (DocumentModel docModel : docModels) { saveDocument(docModel); } } @Override public DocumentModel getSourceDocument(DocumentRef docRef) throws ClientException { assert null != docRef; try { Document doc = resolveReference(docRef); checkPermission(doc, READ_VERSION); Document headDocument = doc.getSourceDocument(); if (headDocument == null) { throw new DocumentException("Source document has been deleted"); } return readModel(headDocument); } catch (DocumentException e) { throw new ClientException("Failed to get head document for " + docRef, e); } } protected VersionModel getVersionModel(Document version) throws DocumentException { VersionModel versionModel = new VersionModelImpl(); versionModel.setId(version.getUUID()); versionModel.setCreated(version.getVersionCreationDate()); versionModel.setDescription(version.getCheckinComment()); versionModel.setLabel(version.getVersionLabel()); return versionModel; } @Override public VersionModel getLastVersion(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ_VERSION); Document version = doc.getLastVersion(); return version == null ? null : getVersionModel(version); } catch (DocumentException e) { throw new ClientException("Failed to get versions for " + docRef, e); } } @Override public DocumentModel getLastDocumentVersion(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ_VERSION); Document version = doc.getLastVersion(); return version == null ? null : readModel(version); } catch (DocumentException e) { throw new ClientException("Failed to get versions for " + docRef, e); } } @Override public DocumentRef getLastDocumentVersionRef(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ_VERSION); Document version = doc.getLastVersion(); return version == null ? null : new IdRef(version.getUUID()); } catch (DocumentException e) { throw new ClientException("Failed to get versions for " + docRef, e); } } @Override public List<DocumentRef> getVersionsRefs(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ_VERSION); List<String> ids = doc.getVersionsIds(); List<DocumentRef> refs = new ArrayList<DocumentRef>(ids.size()); for (String id : ids) { refs.add(new IdRef(id)); } return refs; } catch (DocumentException e) { throw new ClientException(e); } } @Override public List<DocumentModel> getVersions(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ_VERSION); List<Document> docVersions = doc.getVersions(); List<DocumentModel> versions = new ArrayList<DocumentModel>( docVersions.size()); for (Document version : docVersions) { versions.add(readModel(version)); } return versions; } catch (DocumentException e) { throw new ClientException("Failed to get versions for " + docRef, e); } } @Override public List<VersionModel> getVersionsForDocument(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ_VERSION); List<Document> docVersions = doc.getVersions(); List<VersionModel> versions = new ArrayList<VersionModel>( docVersions.size()); for (Document version : docVersions) { versions.add(getVersionModel(version)); } return versions; } catch (DocumentException e) { throw new ClientException("Failed to get versions for " + docRef, e); } } @Override public DocumentModel restoreToVersion(DocumentRef docRef, DocumentRef versionRef) throws ClientException { try { Document doc = resolveReference(docRef); Document ver = resolveReference(versionRef); return restoreToVersion(doc, ver, false, true); } catch (DocumentException e) { throw new ClientException("Failed to restore document", e); } } @Override @Deprecated public DocumentModel restoreToVersion(DocumentRef docRef, VersionModel version) throws ClientException { return restoreToVersion(docRef, version, false); } @Override @Deprecated public DocumentModel restoreToVersion(DocumentRef docRef, VersionModel version, boolean skipSnapshotCreation) throws ClientException { try { Document doc = resolveReference(docRef); Document ver = doc.getVersion(version.getLabel()); return restoreToVersion(doc, ver, skipSnapshotCreation, false); } catch (DocumentException e) { throw new ClientException("Failed to restore document", e); } } @Override public DocumentModel restoreToVersion(DocumentRef docRef, DocumentRef versionRef, boolean skipSnapshotCreation, boolean skipCheckout) throws ClientException { try { Document doc = resolveReference(docRef); Document ver = resolveReference(versionRef); return restoreToVersion(doc, ver, skipSnapshotCreation, skipCheckout); } catch (DocumentException e) { throw new ClientException("Failed to restore document", e); } } protected DocumentModel restoreToVersion(Document doc, Document version, boolean skipSnapshotCreation, boolean skipCheckout) throws ClientException { try { checkPermission(doc, WRITE_VERSION); DocumentModel docModel = readModel(doc); // we're about to overwrite the document, make sure it's archived if (!skipSnapshotCreation && doc.isCheckedOut()) { String checkinComment = (String) docModel.getContextData(VersioningService.CHECKIN_COMMENT); docModel.putContextData(VersioningService.CHECKIN_COMMENT, null); getVersioningService().doCheckIn(doc, null, checkinComment); } final Map<String, Serializable> options = new HashMap<String, Serializable>(); // FIXME: the fields are hardcoded. should be moved in versioning // component // HOW? final Long majorVer = doc.getLong("major_version"); final Long minorVer = doc.getLong("minor_version"); if (majorVer != null || minorVer != null) { options.put( VersioningDocument.CURRENT_DOCUMENT_MAJOR_VERSION_KEY, majorVer); options.put( VersioningDocument.CURRENT_DOCUMENT_MINOR_VERSION_KEY, minorVer); } // add the uuid of the version being restored String versionUUID = version.getUUID(); options.put(VersioningDocument.RESTORED_VERSION_UUID_KEY, versionUUID); notifyEvent(DocumentEventTypes.BEFORE_DOC_RESTORE, docModel, options, null, null, true, true); writeModel(doc, docModel); doc.restore(version); if (!skipCheckout) { // restore gives us a checked in document, so do a checkout getVersioningService().doCheckOut(doc); } // re-read doc model after restoration docModel = readModel(doc); notifyEvent(DocumentEventTypes.DOCUMENT_RESTORED, docModel, options, null, null, true, false); docModel = writeModel(doc, docModel); log.debug("Document restored to version:" + version.getUUID()); return docModel; } catch (DocumentException e) { throw new ClientException("Failed to restore document " + doc, e); } } @Override public DocumentRef getBaseVersion(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ); Document ver = doc.getBaseVersion(); if (ver == null) { return null; } checkPermission(ver, READ); return new IdRef(ver.getUUID()); } catch (DocumentException e) { throw new ClientException(e); } } @Override @Deprecated public DocumentModel checkIn(DocumentRef docRef, VersionModel ver) throws ClientException { try { DocumentRef verRef = checkIn(docRef, VersioningOption.MINOR, ver == null ? null : ver.getDescription()); return readModel(resolveReference(verRef)); } catch (DocumentException e) { throw new ClientException("Failed to check in document " + docRef, e); } } @Override public DocumentRef checkIn(DocumentRef docRef, VersioningOption option, String checkinComment) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, WRITE_PROPERTIES); DocumentModel docModel = readModel(doc); Map<String, Serializable> options = new HashMap<String, Serializable>(); notifyEvent(DocumentEventTypes.ABOUT_TO_CHECKIN, docModel, options, null, null, true, true); writeModel(doc, docModel); Document version = getVersioningService().doCheckIn(doc, option, checkinComment); DocumentModel versionModel = readModel(version); notifyEvent(DocumentEventTypes.DOCUMENT_CREATED, versionModel, options, null, null, true, false); docModel = readModel(doc); DocumentRef checkedInVersionRef = versionModel.getRef(); notifyCheckedInVersion(docModel, checkedInVersionRef, options, checkinComment); writeModel(doc, docModel); return versionModel.getRef(); } catch (DocumentException e) { throw new ClientException("Failed to check in document " + docRef, e); } } /** * Send a core event for the creation of a new check in version. The source * document is the live document model used as the source for the checkin, * not the archived version it-self. * * @param docModel work document that has been checked-in as a version * @param checkedInVersionRef document ref of the new checked-in version * @param options initial option map, or null * @param checkinComment * @throws ClientException */ protected void notifyCheckedInVersion(DocumentModel docModel, DocumentRef checkedInVersionRef, Map<String, Serializable> options, String checkinComment) throws ClientException { String label = getVersioningService().getVersionLabel(docModel); if (options == null) { options = new HashMap<String, Serializable>(); } options.put("versionLabel", label); options.put("checkInComment", checkinComment); options.put("checkedInVersionRef", checkedInVersionRef); String comment = checkinComment == null ? label : label + ' ' + checkinComment; options.put("comment", comment); // compat, used in audit notifyEvent(DocumentEventTypes.DOCUMENT_CHECKEDIN, docModel, options, null, null, true, false); } @Override public void checkOut(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); // TODO: add a new permission names CHECKOUT and use it instead of // WRITE_PROPERTIES checkPermission(doc, WRITE_PROPERTIES); DocumentModel docModel = readModel(doc); Map<String, Serializable> options = new HashMap<String, Serializable>(); notifyEvent(DocumentEventTypes.ABOUT_TO_CHECKOUT, docModel, options, null, null, true, true); getVersioningService().doCheckOut(doc); docModel = readModel(doc); notifyEvent(DocumentEventTypes.DOCUMENT_CHECKEDOUT, docModel, options, null, null, true, false); writeModel(doc, docModel); } catch (DocumentException e) { throw new ClientException("Failed to check out document " + docRef, e); } } public void internalCheckOut(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); } catch (DocumentException e) { throw new ClientException("Failed to check out document " + docRef, e); } } @Override public boolean isCheckedOut(DocumentRef docRef) throws ClientException { assert null != docRef; try { Document doc = resolveReference(docRef); checkPermission(doc, BROWSE); return doc.isCheckedOut(); } catch (DocumentException e) { throw new ClientException("Failed to check out document " + docRef, e); } } @Override public String getVersionSeriesId(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ); return doc.getVersionSeriesId(); } catch (DocumentException e) { throw new ClientException(e); } } @Override public DocumentModel getWorkingCopy(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ_VERSION); Document pwc = doc.getWorkingCopy(); checkPermission(pwc, READ); return pwc == null ? null : readModel(pwc); } catch (DocumentException e) { throw new ClientException("Failed to get versions for " + docRef, e); } } @Override public DocumentModel getVersion(String versionableId, VersionModel versionModel) throws ClientException { String id = versionModel.getId(); if (id != null) { return getDocument(new IdRef(id)); } try { Document doc = getSession().getVersion(versionableId, versionModel); if (doc == null) { return null; } checkPermission(doc, READ_PROPERTIES); checkPermission(doc, READ_VERSION); return readModel(doc); } catch (DocumentException e) { throw new ClientException("Failed to get version " + versionModel.getLabel() + " for " + versionableId, e); } } @Override public String getVersionLabel(DocumentModel docModel) throws ClientException { return getVersioningService().getVersionLabel(docModel); } @Override public DocumentModel getDocumentWithVersion(DocumentRef docRef, VersionModel version) throws ClientException { String id = version.getId(); if (id != null) { return getDocument(new IdRef(id)); } try { Document doc = resolveReference(docRef); checkPermission(doc, READ_PROPERTIES); checkPermission(doc, READ_VERSION); String docPath = doc.getPath(); doc = doc.getVersion(version.getLabel()); if (doc == null) { // SQL Storage uses to return null if version not found log.debug("Version " + version.getLabel() + " does not exist for " + docPath); return null; } log.debug("Retrieved the version " + version.getLabel() + " of the document " + docPath); return readModel(doc); } catch (DocumentException e) { throw new ClientException("Failed to get version for " + docRef, e); } } @Override public DocumentModel createProxy(DocumentRef docRef, DocumentRef folderRef) throws ClientException { try { Document doc = resolveReference(docRef); Document fold = resolveReference(folderRef); checkPermission(doc, READ); checkPermission(fold, ADD_CHILDREN); return createProxyInternal(doc, fold, new HashMap<String, Serializable>()); } catch (DocumentException e) { throw new ClientException(e); } } protected DocumentModel createProxyInternal(Document doc, Document folder, Map<String, Serializable> options) throws ClientException { try { // create the new proxy Document proxy = getSession().createProxy(doc, folder); DocumentModel proxyModel = readModel(proxy); notifyEvent(DocumentEventTypes.DOCUMENT_CREATED, proxyModel, options, null, null, true, false); notifyEvent(DocumentEventTypes.DOCUMENT_PROXY_PUBLISHED, proxyModel, options, null, null, true, false); DocumentModel folderModel = readModel(folder); notifyEvent(DocumentEventTypes.SECTION_CONTENT_PUBLISHED, folderModel, options, null, null, true, false); return proxyModel; } catch (DocumentException e) { throw new ClientException("Failed to create proxy for doc: " + doc, e); } } @Override @Deprecated public DocumentModel createProxy(DocumentRef parentRef, DocumentRef docRef, VersionModel version, boolean overwriteExistingProxy) throws ClientException { try { Document doc = resolveReference(docRef); Document sec = resolveReference(parentRef); checkPermission(doc, READ); checkPermission(sec, ADD_CHILDREN); DocumentModel proxyModel = null; Map<String, Serializable> options = new HashMap<String, Serializable>(); String vlabel = version.getLabel(); if (overwriteExistingProxy) { Document target = getSession().getVersion(doc.getUUID(), version); if (target == null) { throw new ClientException("Document " + docRef + " has no version " + vlabel); } proxyModel = updateExistingProxies(doc, sec, target); // proxyModel is null is update fails if (proxyModel != null) { // notify for proxy updates notifyEvent(DocumentEventTypes.DOCUMENT_PROXY_UPDATED, proxyModel, options, null, null, true, false); } else { List<String> removedProxyIds = Collections.emptyList(); removedProxyIds = removeExistingProxies(doc, sec); options.put(CoreEventConstants.REPLACED_PROXY_IDS, (Serializable) removedProxyIds); } } if (proxyModel == null) { // create the new proxy Document proxy = getSession().createProxyForVersion(sec, doc, vlabel); log.debug("Created proxy for version " + vlabel + " of the document " + doc.getPath()); // notify for reindexing proxyModel = readModel(proxy); // notify for document creation (proxy) notifyEvent(DocumentEventTypes.DOCUMENT_CREATED, proxyModel, options, null, null, true, false); } notifyEvent(DocumentEventTypes.DOCUMENT_PROXY_PUBLISHED, proxyModel, options, null, null, true, false); DocumentModel sectionModel = readModel(sec); notifyEvent(DocumentEventTypes.SECTION_CONTENT_PUBLISHED, sectionModel, options, null, null, true, false); return proxyModel; } catch (DocumentException e) { throw new ClientException("Failed to create proxy for doc " + docRef + " , version: " + version.getLabel(), e); } } /** * Remove proxies for the same base document in the folder. doc may be a * normal document or a proxy. */ protected List<String> removeExistingProxies(Document doc, Document folder) throws DocumentException, ClientException { Collection<Document> otherProxies = getSession().getProxies(doc, folder); List<String> removedProxyIds = new ArrayList<String>( otherProxies.size()); for (Document otherProxy : otherProxies) { removedProxyIds.add(otherProxy.getUUID()); removeNotifyOneDoc(otherProxy); } return removedProxyIds; } /** * Update the proxy for doc in the given section to point to the new target. * Do nothing if there are several proxies. * * @return the proxy if it was updated, or {@code null} if none or several * were found */ protected DocumentModel updateExistingProxies(Document doc, Document folder, Document target) throws DocumentException, ClientException { Collection<Document> proxies = getSession().getProxies(doc, folder); try { if (proxies.size() == 1) { for (Document proxy : proxies) { if (proxy instanceof DocumentProxy) { ((DocumentProxy) proxy).setTargetDocument(target); return readModel(proxy); } } } } catch (UnsupportedOperationException e) { log.error("Cannot update proxy, try to remove"); } return null; } @Override public DocumentModelList getProxies(DocumentRef docRef, DocumentRef folderRef) throws ClientException { try { Document folder = null; if (folderRef != null) { folder = resolveReference(folderRef); checkPermission(folder, READ_CHILDREN); } Document doc = resolveReference(docRef); Collection<Document> children = getSession().getProxies(doc, folder); DocumentModelList docs = new DocumentModelListImpl(); for (Document child : children) { if (hasPermission(child, READ)) { docs.add(readModel(child)); } } return docs; } catch (DocumentException e) { throw new ClientException( "Failed to get children for " + folderRef, e); } } @Override public String[] getProxyVersions(DocumentRef docRef, DocumentRef folderRef) throws ClientException { try { Document folder = resolveReference(folderRef); Document doc = resolveReference(docRef); checkPermission(folder, READ_CHILDREN); Collection<Document> children = getSession().getProxies(doc, folder); if (children.isEmpty()) { return null; } List<String> versions = new ArrayList<String>(); for (Document child : children) { if (hasPermission(child, READ)) { Document target = ((DocumentProxy) child).getTargetDocument(); if (target.isVersion()) { versions.add(target.getVersionLabel()); } else { // live proxy versions.add(""); } } } return versions.toArray(new String[versions.size()]); } catch (DocumentException e) { throw new ClientException("Failed to get children for " + folderRef.toString(), e); } } @Override public List<String> getAvailableSecurityPermissions() throws ClientException { // XXX: add security check? return Arrays.asList(getSecurityService().getPermissionProvider().getPermissions()); } @Override @Deprecated public DataModel getDataModel(DocumentRef docRef, String schema) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ); Schema docSchema = doc.getType().getSchema(schema); return DocumentModelFactory.createDataModel(doc, docSchema); } catch (DocumentException e) { throw new ClientException("Failed to get data model for " + docRef + ':' + schema, e); } } @Override public DataModel getDataModel(DocumentRef docRef, Schema schema) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ); return DocumentModelFactory.createDataModel(doc, schema); } catch (DocumentException e) { throw new ClientException("Failed to get data model for " + docRef + ':' + schema, e); } } @Override @Deprecated public Object getDataModelField(DocumentRef docRef, String schema, String field) throws ClientException { try { Document doc = resolveReference(docRef); if (doc != null) { checkPermission(doc, READ); Schema docSchema = doc.getType().getSchema(schema); if (docSchema != null) { String prefix = docSchema.getNamespace().prefix; if (prefix != null && prefix.length() > 0) { field = prefix + ':' + field; } return doc.getPropertyValue(field); } else { log.warn("Cannot find schema with name=" + schema); } } else { log.warn("Cannot resolve docRef=" + docRef); } return null; } catch (DocumentException e) { throw new ClientException("Failed to get data model field " + schema + ':' + field, e); } } @Override @Deprecated public Object[] getDataModelFields(DocumentRef docRef, String schema, String[] fields) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ); Schema docSchema = doc.getType().getSchema(schema); String prefix = docSchema.getNamespace().prefix; if (prefix != null && prefix.length() > 0) { prefix += ':'; } // prefix is not used for the moment // else { // prefix = null; // } Object[] values = new Object[fields.length]; for (int i = 0; i < fields.length; i++) { if (prefix != null) { values[i] = doc.getPropertyValue(fields[i]); } } return values; } catch (DocumentException e) { throw new ClientException("Failed to check out document " + docRef, e); } } @Override public SerializableInputStream getContentData(String key) throws ClientException { try { InputStream in = getSession().getDataStream(key); return new SerializableInputStream(in); } catch (Exception e) { throw new ClientException("Failed to get data stream for " + key, e); } } @Override public String getStreamURI(String blobPropertyId) throws ClientException { String uri; try { InputStream in = getContentData(blobPropertyId); StreamManager sm = Framework.getLocalService(StreamManager.class); if (sm == null) { throw new ClientException("No Streaming Service was registered"); } uri = sm.addStream(new InputStreamSource(in)); } catch (ClientException e) { throw e; } catch (Exception e) { throw new ClientException("Failed to register blob stream: " + blobPropertyId, e); } return uri; } @Override public String getCurrentLifeCycleState(DocumentRef docRef) throws ClientException { String lifeCycleState; try { Document doc = resolveReference(docRef); checkPermission(doc, READ_LIFE_CYCLE); lifeCycleState = doc.getLifeCycleState(); } catch (LifeCycleException e) { ClientException ce = new ClientException( "Failed to get life cycle " + docRef, e); ce.fillInStackTrace(); throw ce; } catch (DocumentException e) { throw new ClientException("Failed to get content data " + docRef, e); } return lifeCycleState; } @Override public String getLifeCyclePolicy(DocumentRef docRef) throws ClientException { String lifecyclePolicy; try { Document doc = resolveReference(docRef); checkPermission(doc, READ_LIFE_CYCLE); lifecyclePolicy = doc.getLifeCyclePolicy(); } catch (LifeCycleException e) { ClientException ce = new ClientException( "Failed to get life cycle policy" + docRef, e); ce.fillInStackTrace(); throw ce; } catch (DocumentException e) { throw new ClientException("Failed to get content data " + docRef, e); } return lifecyclePolicy; } @Override public boolean followTransition(DocumentRef docRef, String transition) throws ClientException { boolean operationResult; try { Document doc = resolveReference(docRef); checkPermission(doc, WRITE_LIFE_CYCLE); String formerStateName = doc.getLifeCycleState(); operationResult = doc.followTransition(transition); if (operationResult) { // Construct a map holding meta information about the event. Map<String, Serializable> options = new HashMap<String, Serializable>(); options.put( org.eclipse.ecr.core.api.LifeCycleConstants.TRANSTION_EVENT_OPTION_FROM, formerStateName); options.put( org.eclipse.ecr.core.api.LifeCycleConstants.TRANSTION_EVENT_OPTION_TO, doc.getLifeCycleState()); options.put( org.eclipse.ecr.core.api.LifeCycleConstants.TRANSTION_EVENT_OPTION_TRANSITION, transition); DocumentModel docModel = readModel(doc); notifyEvent( org.eclipse.ecr.core.api.LifeCycleConstants.TRANSITION_EVENT, docModel, options, DocumentEventCategories.EVENT_LIFE_CYCLE_CATEGORY, null, true, false); } } catch (LifeCycleException e) { ClientException ce = new ClientException( "Unable to follow transition <" + transition + "> for document : " + docRef, e); ce.fillInStackTrace(); throw ce; } catch (DocumentException e) { throw new ClientException("Failed to get content data " + docRef, e); } return operationResult; } @Override public Collection<String> getAllowedStateTransitions(DocumentRef docRef) throws ClientException { Collection<String> allowedStateTransitions; try { Document doc = resolveReference(docRef); checkPermission(doc, READ_LIFE_CYCLE); allowedStateTransitions = doc.getAllowedStateTransitions(); } catch (LifeCycleException e) { ClientException ce = new ClientException( "Unable to get allowed state transitions for document : " + docRef, e); ce.fillInStackTrace(); throw ce; } catch (DocumentException e) { throw new ClientException("Failed to get content data " + docRef, e); } return allowedStateTransitions; } @Override public Object[] getDataModelsField(DocumentRef[] docRefs, String schema, String field) throws ClientException { assert docRefs != null; assert schema != null; assert field != null; final Object[] values = new Object[docRefs.length]; int i = 0; for (DocumentRef docRef : docRefs) { final Object value = getDataModelField(docRef, schema, field); values[i++] = value; } return values; } @Override public DocumentRef[] getParentDocumentRefs(DocumentRef docRef) throws ClientException { final List<DocumentRef> docRefs = new ArrayList<DocumentRef>(); try { final Document doc = resolveReference(docRef); Document parentDoc = doc.getParent(); while (parentDoc != null) { final DocumentRef parentDocRef = new IdRef(parentDoc.getUUID()); docRefs.add(parentDocRef); parentDoc = parentDoc.getParent(); } } catch (DocumentException e) { throw new ClientException("Failed to get all parent documents: " + docRef, e); } DocumentRef[] refs = new DocumentRef[docRefs.size()]; return docRefs.toArray(refs); } @Override public Object[] getDataModelsFieldUp(DocumentRef docRef, String schema, String field) throws ClientException { final DocumentRef[] parentRefs = getParentDocumentRefs(docRef); final DocumentRef[] allRefs = new DocumentRef[parentRefs.length + 1]; allRefs[0] = docRef; System.arraycopy(parentRefs, 0, allRefs, 1, parentRefs.length); return getDataModelsField(allRefs, schema, field); } protected String oldLockKey(Lock lock) { if (lock == null) { return null; } // return deprecated format, like "someuser:Nov 29, 2010" return lock.getOwner() + ':' + DateFormat.getDateInstance(DateFormat.MEDIUM).format( new Date(lock.getCreated().getTimeInMillis())); } @Override @Deprecated public String getLock(DocumentRef docRef) throws ClientException { Lock lock = getLockInfo(docRef); return oldLockKey(lock); } @Override @Deprecated public void setLock(DocumentRef docRef, String key) throws ClientException { setLock(docRef); } @Override @Deprecated public String unlock(DocumentRef docRef) throws ClientException { Lock lock = removeLock(docRef); return oldLockKey(lock); } @Override public Lock setLock(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); // TODO: add a new permission named LOCK and use it instead of // WRITE_PROPERTIES checkPermission(doc, WRITE_PROPERTIES); Lock lock = new Lock(getPrincipal().getName(), new GregorianCalendar()); Lock oldLock = doc.setLock(lock); if (oldLock != null) { throw new ClientException("Document already locked by " + oldLock.getOwner() + ": " + docRef); } DocumentModel docModel = readModel(doc); Map<String, Serializable> options = new HashMap<String, Serializable>(); options.put("lock", lock); notifyEvent(DocumentEventTypes.DOCUMENT_LOCKED, docModel, options, null, null, true, false); return lock; } catch (DocumentException e) { throw new ClientException("Failed to set lock on " + docRef, e); } } @Override public Lock getLockInfo(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); checkPermission(doc, READ); return doc.getLock(); } catch (DocumentException e) { throw new ClientException("Failed to get lock info on " + docRef, e); } } @Override public Lock removeLock(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); String owner; if (hasPermission(docRef, UNLOCK)) { // always unlock owner = null; } else { owner = getPrincipal().getName(); } Lock lock = doc.removeLock(owner); if (lock == null) { // there was no lock, we're done return null; } if (lock.getFailed()) { // lock removal failed due to owner check throw new ClientException("Document already locked by " + lock.getOwner() + ": " + docRef); } DocumentModel docModel = readModel(doc); Map<String, Serializable> options = new HashMap<String, Serializable>(); options.put("lock", lock); notifyEvent(DocumentEventTypes.DOCUMENT_UNLOCKED, docModel, options, null, null, true, false); return lock; } catch (DocumentException e) { throw new ClientException("Failed to set lock on " + docRef, e); } } protected boolean isAdministrator() { Principal principal = getPrincipal(); // FIXME: this is inconsistent with NuxeoPrincipal#isAdministrator // method because it allows hardcoded Administrator user if (Framework.isTestModeSet()) { if (SecurityConstants.ADMINISTRATOR.equals(principal.getName())) { return true; } } if (SYSTEM_USERNAME.equals(principal.getName())) { return true; } if (principal instanceof NuxeoPrincipal) { return ((NuxeoPrincipal) principal).isAdministrator(); } return false; } @Override public void applyDefaultPermissions(String userOrGroupName) throws ClientException { if (null == userOrGroupName) { throw new ClientException("Null parameters received."); } if (!isAdministrator()) { throw new DocumentSecurityException( "You need to be an Administrator to do this."); } DocumentModel rootDocument = getRootDocument(); ACP acp = new ACPImpl(); UserEntry userEntry = new UserEntryImpl(userOrGroupName); userEntry.addPrivilege(READ, true, false); acp.setRules(new UserEntry[] { userEntry }); setACP(rootDocument.getRef(), acp, false); } @Override public void destroy() { log.debug("Destroying core session ..."); try { disconnect(); } catch (Exception e) { log.error("Failed to destroy core session", e); } } @Override public DocumentModel publishDocument(DocumentModel docToPublish, DocumentModel section) throws ClientException { return publishDocument(docToPublish, section, true); } @Override public DocumentModel publishDocument(DocumentModel docModel, DocumentModel section, boolean overwriteExistingProxy) throws ClientException { try { Document doc = resolveReference(docModel.getRef()); Document sec = resolveReference(section.getRef()); checkPermission(doc, READ); checkPermission(sec, ADD_CHILDREN); Map<String, Serializable> options = new HashMap<String, Serializable>(); DocumentModel proxy = null; Document target; if (docModel.isProxy()) { if (overwriteExistingProxy) { // remove previous List<String> removedProxyIds = removeExistingProxies(doc, sec); options.put(CoreEventConstants.REPLACED_PROXY_IDS, (Serializable) removedProxyIds); } target = doc; } else { String checkinComment = (String) docModel.getContextData(VersioningService.CHECKIN_COMMENT); docModel.putContextData(VersioningService.CHECKIN_COMMENT, null); if (doc.isCheckedOut() || doc.getLastVersion() == null) { if (!doc.isCheckedOut()) { // last version was deleted while leaving a checked in // doc. recreate a version getVersioningService().doCheckOut(doc); } getVersioningService().doCheckIn(doc, null, checkinComment); docModel.refresh(DocumentModel.REFRESH_STATE, null); } target = doc.getLastVersion(); if (overwriteExistingProxy) { proxy = updateExistingProxies(doc, sec, target); if (proxy == null) { // no or several proxies, remove them List<String> removedProxyIds = removeExistingProxies( doc, sec); options.put(CoreEventConstants.REPLACED_PROXY_IDS, (Serializable) removedProxyIds); } else { // notify proxy updates notifyEvent(DocumentEventTypes.DOCUMENT_PROXY_UPDATED, proxy, options, null, null, true, false); notifyEvent( DocumentEventTypes.DOCUMENT_PROXY_PUBLISHED, proxy, options, null, null, true, false); notifyEvent( DocumentEventTypes.SECTION_CONTENT_PUBLISHED, section, options, null, null, true, false); } } } if (proxy == null) { proxy = createProxyInternal(target, sec, options); } return proxy; } catch (DocumentException e) { throw new ClientException(e); } } @Override public String getSuperParentType(DocumentModel doc) throws ClientException { DocumentModel superSpace = getSuperSpace(doc); if (superSpace == null) { return null; } else { return superSpace.getType(); } } @Override public DocumentModel getSuperSpace(DocumentModel doc) throws ClientException { if (doc == null) { throw new ClientException("getSuperSpace: document is null"); } if (doc.hasFacet("SuperSpace")) { return doc; } else { DocumentModel parent = getDirectAccessibleParent(doc.getRef()); if (parent == null || "/".equals(parent.getPathAsString())) { // return Root instead of null return getRootDocument(); } else { return getSuperSpace(parent); } } } // walk the tree up until a accessible doc is found private DocumentModel getDirectAccessibleParent(DocumentRef docRef) throws ClientException { try { Document doc = resolveReference(docRef); Document parentDoc = doc.getParent(); if (parentDoc == null) { return readModel(doc); } if (!hasPermission(parentDoc, READ)) { String parentPath = parentDoc.getPath(); if ("/".equals(parentPath)) { return getRootDocument(); } else { // try on parent return getDirectAccessibleParent(new PathRef( parentDoc.getPath())); } } return readModel(parentDoc); } catch (DocumentException e) { throw new ClientException(e); } } @Override public List<SecuritySummaryEntry> getSecuritySummary( DocumentModel docModel, Boolean includeParents) throws ClientException { if (docModel == null) { docModel = getRootDocument(); } Document doc; try { doc = resolveReference(docModel.getRef()); } catch (DocumentException e) { throw new ClientException("Failed to get document " + docModel.getRef().toString(), e); } return getSecurityService().getSecuritySummary(doc, includeParents); } @Override public String getRepositoryName() { return repositoryName; } @Override public <T extends Serializable> T getDocumentSystemProp(DocumentRef ref, String systemProperty, Class<T> type) throws ClientException, DocumentException { Document doc; try { doc = resolveReference(ref); } catch (DocumentException e) { throw new ClientException("Failed to get document " + ref, e); } return doc.getSystemProp(systemProperty, type); } @Override public <T extends Serializable> void setDocumentSystemProp(DocumentRef ref, String systemProperty, T value) throws ClientException, DocumentException { Document doc; try { doc = resolveReference(ref); } catch (DocumentException e) { throw new ClientException("Failed to get document " + ref, e); } doc.setSystemProp(systemProperty, value); } @Override public void orderBefore(DocumentRef parent, String src, String dest) throws ClientException { try { if ((src == null) || (src.equals(dest))) { return; } Document doc = resolveReference(parent); doc.orderBefore(src, dest); Map<String, Serializable> options = new HashMap<String, Serializable>(); // send event on container passing the reordered child as parameter DocumentModel docModel = readModel(doc); String comment = src; options.put(CoreEventConstants.REORDERED_CHILD, src); notifyEvent(DocumentEventTypes.DOCUMENT_CHILDREN_ORDER_CHANGED, docModel, options, null, comment, true, false); } catch (DocumentException e) { throw new ClientException("Failed to resolve documents: " + src + ", " + dest, e); } } @Override public <T> T run(Operation<T> op) throws ClientException { return run(op, null); } @Override public <T> T run(Operation<T> op, ProgressMonitor monitor) throws ClientException { // double s = System.currentTimeMillis(); T result = op.run(this, this, monitor); // System.out.println(">>>>> OPERATION "+op.getName()+" took: "+ // ((System.currentTimeMillis()-s)/1000)); Status status = op.getStatus(); if (status.isOk()) { return result; } else { Throwable t = status.getException(); if (t != null) { if (t instanceof ClientException) { throw (ClientException) t; } else { throw new ClientException(status.getMessage(), t); } } else { String msg = status.getMessage(); if (msg == null) { msg = "Unknown Error"; } throw new ClientException(msg); } } } /** * This method is for compatibility reasons to notify an operation start. * Operations must be reworked to use the new event model. In order for * operation notification to work the event compatibility bundle must be * deployed. * * @see org.eclipse.ecr.core.event.compat.CompatibilityListener in * nuxeo-core-event-compat */ @Override public void startOperation(Operation<?> operation) { EventContextImpl ctx = new EventContextImpl(this, getPrincipal(), operation); Event event = ctx.newEvent("!OPERATION_START!"); try { fireEvent(event); } catch (ClientException e) { log.error("Failed to notify operation start for: " + operation, e); } // old code was: // CoreEventListenerService service = // NXCore.getCoreEventListenerService(); // service.fireOperationStarted(operation); } /** * This method is for compatibility reasons to notify an operation end. * Operations must be reworked to use the new event model. In order for * operation notification to work the event compatibility bundle must be * deployed. * * @see org.eclipse.ecr.core.event.compat.CompatibilityListener in * nuxeo-core-event-compat */ @Override public void endOperation(Operation<?> operation) { EventContextImpl ctx = new EventContextImpl(this, getPrincipal(), operation); Event event = ctx.newEvent("!OPERATION_END!"); try { fireEvent(event); } catch (ClientException e) { log.error("Failed to notify operation end for: " + operation, e); } // old code was: // CoreEventListenerService service = // NXCore.getCoreEventListenerService(); // service.fireOperationTerminated(operation); } @Override public DocumentModelRefresh refreshDocument(DocumentRef ref, int refreshFlags, String[] schemas) throws ClientException { try { Document doc = resolveReference(ref); if (doc == null) { throw new ClientException("No Such Document: " + ref); } // permission checks if ((refreshFlags & (DocumentModel.REFRESH_PREFETCH | DocumentModel.REFRESH_STATE | DocumentModel.REFRESH_CONTENT)) != 0) { checkPermission(doc, READ); } if ((refreshFlags & DocumentModel.REFRESH_ACP) != 0) { checkPermission(doc, READ_SECURITY); } DocumentModelRefresh refresh = DocumentModelFactory.refreshDocumentModel( doc, refreshFlags, schemas); // ACPs need the session, so aren't done in the factory method if ((refreshFlags & DocumentModel.REFRESH_ACP) != 0) { refresh.acp = getSession().getSecurityManager().getMergedACP( doc); } return refresh; } catch (ClientException e) { throw e; } catch (Exception e) { throw new ClientException("Failed to get refresh data", e); } } @Override public String[] getPermissionsToCheck(String permission) { return getSecurityService().getPermissionsToCheck(permission); } @Override public <T extends DetachedAdapter> T adaptFirstMatchingDocumentWithFacet( DocumentRef docRef, String facet, Class<T> adapterClass) throws ClientException { Document doc = getFirstParentDocumentWithFacet(docRef, facet); if (doc != null) { DocumentModel docModel = readModel(doc); loadDataModelsForFacet(docModel, doc, facet); // detach the DocumentModel ((DocumentModelImpl) docModel).detach(false); return docModel.getAdapter(adapterClass); } return null; } protected void loadDataModelsForFacet(DocumentModel docModel, Document doc, String facetName) throws ClientException { // Load all the data related to facet's schemas SchemaManager schemaManager = NXSchema.getSchemaManager(); CompositeType facet = schemaManager.getFacet(facetName); if (facet == null) { return; } String[] facetSchemas = facet.getSchemaNames(); for (String schema : facetSchemas) { try { DataModel dm = DocumentModelFactory.createDataModel(doc, schemaManager.getSchema(schema)); docModel.getDataModels().put(schema, dm); } catch (DocumentException e) { throw new ClientException(e); } } } /** * Returns the first {@code Document} with the given {@code facet}, * recursively going up the parent hierarchy. Returns {@code null} if there * is no more parent. * <p> * This method does not check security rights. */ protected Document getFirstParentDocumentWithFacet(DocumentRef docRef, String facet) throws ClientException { try { Document doc = resolveReference(docRef); while (doc != null && !doc.hasFacet(facet)) { doc = doc.getParent(); } return doc; } catch (DocumentException e) { throw new ClientException(e); } } }