/* * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Nuxeo - initial API and implementation * */ package org.nuxeo.ecm.webapp.contentbrowser; import static org.jboss.seam.ScopeType.CONVERSATION; import static org.jboss.seam.ScopeType.EVENT; import java.io.IOException; import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.faces.context.FacesContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.seam.annotations.Factory; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.remoting.WebRemote; import org.jboss.seam.annotations.web.RequestParameter; import org.jboss.seam.core.Events; import org.jboss.seam.international.StatusMessage; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentLocation; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentRef; import org.nuxeo.ecm.core.api.IdRef; import org.nuxeo.ecm.core.api.blobholder.BlobHolder; import org.nuxeo.ecm.core.api.event.CoreEventConstants; import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService; import org.nuxeo.ecm.core.api.security.SecurityConstants; import org.nuxeo.ecm.core.api.validation.DocumentValidationException; import org.nuxeo.ecm.core.blob.BlobManager; import org.nuxeo.ecm.core.blob.BlobProvider; import org.nuxeo.ecm.core.blob.ManagedBlob; import org.nuxeo.ecm.core.blob.apps.AppLink; import org.nuxeo.ecm.core.io.download.DownloadService; import org.nuxeo.ecm.core.schema.FacetNames; import org.nuxeo.ecm.platform.actions.Action; import org.nuxeo.ecm.platform.actions.ActionContext; import org.nuxeo.ecm.platform.types.Type; import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; import org.nuxeo.ecm.platform.ui.web.api.UserAction; import org.nuxeo.ecm.platform.ui.web.api.WebActions; import org.nuxeo.ecm.platform.ui.web.tag.fn.Functions; import org.nuxeo.ecm.platform.ui.web.util.BaseURL; import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; import org.nuxeo.ecm.platform.url.api.DocumentView; import org.nuxeo.ecm.platform.url.codec.DocumentFileCodec; import org.nuxeo.ecm.platform.util.RepositoryLocation; import org.nuxeo.ecm.webapp.action.ActionContextProvider; import org.nuxeo.ecm.webapp.action.DeleteActions; import org.nuxeo.ecm.webapp.base.InputController; import org.nuxeo.ecm.webapp.documentsLists.DocumentsListsManager; import org.nuxeo.ecm.webapp.helpers.EventManager; import org.nuxeo.ecm.webapp.helpers.EventNames; import org.nuxeo.runtime.api.Framework; /** * Handles creation and edition of a document. * * @author <a href="mailto:rcaraghin@nuxeo.com">Razvan Caraghin</a> * @author M.-A. Darche */ @Name("documentActions") @Scope(CONVERSATION) public class DocumentActionsBean extends InputController implements DocumentActions, Serializable { private static final long serialVersionUID = 1L; private static final Log log = LogFactory.getLog(DocumentActionsBean.class); public static final String LIFE_CYCLE_TRANSITION_KEY = "lifeCycleTransition"; public static final String BLOB_ACTIONS_CATEGORY = "BLOB_ACTIONS"; @In(create = true) protected transient NavigationContext navigationContext; @RequestParameter protected String fileFieldFullName; @RequestParameter protected String filenameFieldFullName; @RequestParameter protected String filename; @In(create = true, required = false) protected transient CoreSession documentManager; @In(required = false, create = true) protected transient DocumentsListsManager documentsListsManager; @In(create = true) protected transient DeleteActions deleteActions; @In(create = true, required = false) protected transient ActionContextProvider actionContextProvider; /** * Boolean request parameter used to restore current tabs (current tab and subtab) after edition. * <p> * This is useful when editing the document from a layout toggled to edit mode from summary-like page. * * @since 5.6 */ @RequestParameter protected Boolean restoreCurrentTabs; @In(create = true) protected transient WebActions webActions; protected String comment; @In(create = true) protected Map<String, String> messages; @Override @Factory(autoCreate = true, value = "currentDocumentType", scope = EVENT) public Type getCurrentType() { DocumentModel doc = navigationContext.getCurrentDocument(); if (doc == null) { return null; } return typeManager.getType(doc.getType()); } @Override public Type getChangeableDocumentType() { DocumentModel changeableDocument = navigationContext.getChangeableDocument(); if (changeableDocument == null) { // should we really do this ??? navigationContext.setChangeableDocument(navigationContext.getCurrentDocument()); changeableDocument = navigationContext.getChangeableDocument(); } if (changeableDocument == null) { return null; } return typeManager.getType(changeableDocument.getType()); } public String getFileName(DocumentModel doc) { String name = null; if (filename != null && !"".equals(filename)) { name = filename; } else { // try to fetch it from given field if (filenameFieldFullName != null) { String[] s = filenameFieldFullName.split(":"); try { name = (String) doc.getProperty(s[0], s[1]); } catch (ArrayIndexOutOfBoundsException err) { // ignore, filename is not really set } } // try to fetch it from title if (name == null || "".equals(name)) { name = (String) doc.getProperty("dublincore", "title"); } } return name; } @Override public void download(DocumentView docView) { if (docView == null) { return; } DocumentLocation docLoc = docView.getDocumentLocation(); // fix for NXP-1799 if (documentManager == null) { RepositoryLocation loc = new RepositoryLocation(docLoc.getServerName()); navigationContext.setCurrentServerLocation(loc); documentManager = navigationContext.getOrCreateDocumentManager(); } DocumentModel doc = documentManager.getDocument(docLoc.getDocRef()); if (doc == null) { return; } String xpath = docView.getParameter(DocumentFileCodec.FILE_PROPERTY_PATH_KEY); DownloadService downloadService = Framework.getService(DownloadService.class); Blob blob = downloadService.resolveBlob(doc, xpath); if (blob == null) { log.warn("No blob for docView: " + docView); return; } // get properties from document view String filename = DocumentFileCodec.getFilename(doc, docView); if (blob.getLength() > Functions.getBigFileSizeLimit()) { FacesContext context = FacesContext.getCurrentInstance(); String bigDownloadURL = BaseURL.getBaseURL() + "/" + downloadService.getDownloadUrl(doc, xpath, filename); try { context.getExternalContext().redirect(bigDownloadURL); } catch (IOException e) { log.error("Error while redirecting for big file downloader", e); } } else { ComponentUtils.download(doc, xpath, blob, filename, "download"); } } @Override public String updateDocument(DocumentModel doc, Boolean restoreCurrentTabs) { String tabId = null; String subTabId = null; boolean restoreTabs = Boolean.TRUE.equals(restoreCurrentTabs); if (restoreTabs) { // save current tabs tabId = webActions.getCurrentTabId(); subTabId = webActions.getCurrentSubTabId(); } Events.instance().raiseEvent(EventNames.BEFORE_DOCUMENT_CHANGED, doc); try { doc = documentManager.saveDocument(doc); } catch (DocumentValidationException e) { facesMessages.add(StatusMessage.Severity.ERROR, messages.get("label.schema.constraint.violation.documentValidation"), e.getMessage()); return null; } throwUpdateComments(doc); documentManager.save(); // some changes (versioning) happened server-side, fetch new one navigationContext.invalidateCurrentDocument(); facesMessages.add(StatusMessage.Severity.INFO, messages.get("document_modified"), messages.get(doc.getType())); EventManager.raiseEventsOnDocumentChange(doc); String res = navigationContext.navigateToDocument(doc, "after-edit"); if (restoreTabs) { // restore previously stored tabs; webActions.setCurrentTabId(tabId); webActions.setCurrentSubTabId(subTabId); } return res; } // kept for BBB protected String updateDocument(DocumentModel doc) { return updateDocument(doc, restoreCurrentTabs); } @Override public String updateCurrentDocument() { DocumentModel currentDocument = navigationContext.getCurrentDocument(); return updateDocument(currentDocument); } @Override public String createDocument() { Type docType = typesTool.getSelectedType(); return createDocument(docType.getId()); } @Override public String createDocument(String typeName) { Type docType = typeManager.getType(typeName); // we cannot use typesTool as intermediary since the DataModel callback // will alter whatever type we set typesTool.setSelectedType(docType); Map<String, Object> context = new HashMap<String, Object>(); context.put(CoreEventConstants.PARENT_PATH, navigationContext.getCurrentDocument().getPathAsString()); DocumentModel changeableDocument = documentManager.createDocumentModel(typeName, context); navigationContext.setChangeableDocument(changeableDocument); return navigationContext.getActionResult(changeableDocument, UserAction.CREATE); } @Override public String saveDocument() { DocumentModel changeableDocument = navigationContext.getChangeableDocument(); return saveDocument(changeableDocument); } @RequestParameter protected String parentDocumentPath; @Override public String saveDocument(DocumentModel newDocument) { // Document has already been created if it has an id. // This will avoid creation of many documents if user hit create button // too many times. if (newDocument.getId() != null) { log.debug("Document " + newDocument.getName() + " already created"); return navigationContext.navigateToDocument(newDocument, "after-create"); } PathSegmentService pss = Framework.getService(PathSegmentService.class); DocumentModel currentDocument = navigationContext.getCurrentDocument(); if (parentDocumentPath == null) { if (currentDocument == null) { // creating item at the root parentDocumentPath = documentManager.getRootDocument().getPathAsString(); } else { parentDocumentPath = navigationContext.getCurrentDocument().getPathAsString(); } } newDocument.setPathInfo(parentDocumentPath, pss.generatePathSegment(newDocument)); try { newDocument = documentManager.createDocument(newDocument); } catch (DocumentValidationException e) { facesMessages.add(StatusMessage.Severity.ERROR, messages.get("label.schema.constraint.violation.documentValidation"), e.getMessage()); return null; } documentManager.save(); logDocumentWithTitle("Created the document: ", newDocument); facesMessages.add(StatusMessage.Severity.INFO, messages.get("document_saved"), messages.get(newDocument.getType())); Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, currentDocument); return navigationContext.navigateToDocument(newDocument, "after-create"); } @Override public boolean getWriteRight() { // TODO: WRITE is a high level compound permission (i.e. more like a // user profile), public methods of the Nuxeo framework should only // check atomic / specific permissions such as WRITE_PROPERTIES, // REMOVE, ADD_CHILDREN depending on the action to execute instead return documentManager.hasPermission(navigationContext.getCurrentDocument().getRef(), SecurityConstants.WRITE); } // Send the comment of the update to the Core private void throwUpdateComments(DocumentModel changeableDocument) { if (comment != null && !"".equals(comment)) { changeableDocument.putContextData("comment", comment); } } @Override public boolean getCanUnpublish() { List<DocumentModel> docList = documentsListsManager.getWorkingList(DocumentsListsManager.CURRENT_DOCUMENT_SECTION_SELECTION); if (!(docList == null || docList.isEmpty()) && deleteActions.checkDeletePermOnParents(docList)) { for (DocumentModel document : docList) { if (document.hasFacet(FacetNames.PUBLISH_SPACE) || document.hasFacet(FacetNames.MASTER_PUBLISH_SPACE)) { return false; } } return true; } return false; } @Override @Observer(EventNames.BEFORE_DOCUMENT_CHANGED) public void followTransition(DocumentModel changedDocument) { String transitionToFollow = (String) changedDocument.getContextData(LIFE_CYCLE_TRANSITION_KEY); if (transitionToFollow != null) { documentManager.followTransition(changedDocument.getRef(), transitionToFollow); documentManager.save(); } } /** * @since 7.3 */ public List<Action> getBlobActions(DocumentModel doc, String blobXPath, Blob blob) { ActionContext ctx = actionContextProvider.createActionContext(); ctx.putLocalVariable("document", doc); ctx.putLocalVariable("blob", blob); ctx.putLocalVariable("blobXPath", blobXPath); return webActions.getActionsList(BLOB_ACTIONS_CATEGORY, ctx, true); } /** * @since 7.3 */ @WebRemote public List<AppLink> getAppLinks(String docId, String blobXPath) { DocumentRef docRef = new IdRef(docId); DocumentModel doc = documentManager.getDocument(docRef); Serializable value = doc.getPropertyValue(blobXPath); if (value == null || !(value instanceof ManagedBlob)) { return null; } ManagedBlob managedBlob = (ManagedBlob) value; BlobManager blobManager = Framework.getService(BlobManager.class); BlobProvider blobProvider = blobManager.getBlobProvider(managedBlob.getProviderId()); if (blobProvider == null) { log.error("No registered blob provider for key: " + managedBlob.getKey()); return null; } String user = documentManager.getPrincipal().getName(); try { return blobProvider.getAppLinks(user, managedBlob); } catch (IOException e) { log.error("Failed to retrieve application links", e); } return null; } /** * Checks if the main blob can be updated by a user-initiated action. * * @since 7.10 */ public boolean getCanUpdateMainBlob() { DocumentModel doc = navigationContext.getCurrentDocument(); if (doc == null) { return false; } BlobHolder blobHolder = doc.getAdapter(BlobHolder.class); if (blobHolder == null) { return false; } Blob blob = blobHolder.getBlob(); if (blob == null) { return true; } BlobProvider blobProvider = Framework.getService(BlobManager.class).getBlobProvider(blob); if (blobProvider == null) { return true; } return blobProvider.supportsUserUpdate(); } }