/*
* (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.navigation;
import static org.jboss.seam.ScopeType.CONVERSATION;
import static org.jboss.seam.ScopeType.EVENT;
import static org.jboss.seam.annotations.Install.FRAMEWORK;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.faces.context.FacesContext;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.navigation.Pages;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.security.SecurityConstants;
import org.nuxeo.ecm.platform.query.api.PageProvider;
import org.nuxeo.ecm.platform.query.api.PageProviderService;
import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
import org.nuxeo.ecm.platform.ui.web.pathelements.ArchivedVersionsPathElement;
import org.nuxeo.ecm.platform.ui.web.pathelements.DocumentPathElement;
import org.nuxeo.ecm.platform.ui.web.pathelements.PathElement;
import org.nuxeo.ecm.platform.ui.web.pathelements.TextPathElement;
import org.nuxeo.ecm.platform.ui.web.pathelements.VersionDocumentPathElement;
import org.nuxeo.ecm.webapp.helpers.EventNames;
import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor;
import org.nuxeo.ecm.webapp.helpers.StartupHelper;
import org.nuxeo.runtime.api.Framework;
/**
* The new approach: keep all selected documents into a list. Add new document to the list each time a new document is
* selected, after rebuilding the path.
* <p>
* Algorithm for rebuilding the path:
* <p>
* d1 -> d2 -> d3 -> d4
* <p>
* A new document is selected, which is a child of d2, named d2.5. We need to add d2.5 to the list after all unneeded
* documents have been removed to the list. In the end the list should look like this: d1 -> d2 -> d2.5. We need to
* remove all the documents in the list after d2, and add d2.5 to the list. TODO: fix bug when selecting an item located
* on a different branch than the current one so that its parent is not found in the current branch
*
* @author <a href="mailto:rcaraghin@nuxeo.com">Razvan Caraghin</a>
*/
@Name("breadcrumbActions")
@Scope(CONVERSATION)
@Install(precedence = FRAMEWORK)
public class BreadcrumbActionsBean implements BreadcrumbActions, Serializable {
private static final long serialVersionUID = 1L;
public static final String BREADCRUMB_USER_DOMAINS_PROVIDER = "breadcrumb_user_domains";
@In(create = true)
protected NavigationContext navigationContext;
@In(create = true, required = false)
protected CoreSession documentManager;
@In(create = true)
protected ResourcesAccessor resourcesAccessor;
protected List<DocumentModel> userDomains = null;
protected boolean isPathShrinked = false;
/** View id description prefix for message label (followed by "="). */
protected static final String BREADCRUMB_PREFIX = "breadcrumb";
/**
* Minimum path segments that must be displayed without shrinking.
*/
protected int getMinPathSegmentsLen() {
return 4;
}
/**
* Maximum length path that can be displayed without shrinking.
*/
protected int getMaxPathCharLen() {
return 80;
}
public String getPathEllipsis() {
return "…";
}
public boolean isGoToParentButtonShown() {
return this.isPathShrinked
&& !FacesContext.getCurrentInstance()
.getViewRoot()
.getViewId()
.equals("/" + StartupHelper.SERVERS_VIEW + ".xhtml");
}
protected String getViewDomainsOutcome() {
return StartupHelper.DOMAINS_VIEW;
}
@Override
public String navigateToParent() {
List<PathElement> documentsFormingPath = getBackendPath();
int nbDocInList = documentsFormingPath.size();
// if there is the case, remove the starting
if (nbDocInList > 0 && documentsFormingPath.get(0).getName().equals(getPathEllipsis())) {
documentsFormingPath.remove(0);
}
nbDocInList = documentsFormingPath.size();
if (nbDocInList == 0) {
return StartupHelper.SERVERS_VIEW;
}
String outcome;
if (nbDocInList > 1) {
PathElement parentPathElement = documentsFormingPath.get(nbDocInList - 2);
outcome = navigateToPathElement(parentPathElement);
} else {
PathElement pathElement = documentsFormingPath.get(0);
if (pathElement instanceof TextPathElement) {
DocumentModel currentDocument = navigationContext.getCurrentDocument();
if (currentDocument == null) {
return StartupHelper.SERVERS_VIEW;
} else {
return navigationContext.navigateToDocument(currentDocument);
}
}
DocumentPathElement currentPathELement = (DocumentPathElement) pathElement;
DocumentModel doc = currentPathELement.getDocumentModel();
if (documentManager.hasPermission(doc.getParentRef(), SecurityConstants.READ)) {
outcome = navigationContext.navigateToRef(doc.getParentRef());
} else {
outcome = navigateToPathElement(currentPathELement);
}
if (navigationContext.getCurrentDocument().getType().equals("CoreRoot")) {
outcome = getViewDomainsOutcome();
}
}
return outcome;
}
protected String navigateToPathElement(PathElement pathElement) {
// the bijection is not dynamic, i.e. the variables are injected
// before the action listener code is called.
String elementType = pathElement.getType();
DocumentModel currentDoc;
if (elementType == DocumentPathElement.TYPE) {
DocumentPathElement docPathElement = (DocumentPathElement) pathElement;
currentDoc = docPathElement.getDocumentModel();
return navigationContext.navigateToDocument(currentDoc);
} else if (elementType == ArchivedVersionsPathElement.TYPE) {
ArchivedVersionsPathElement docPathElement = (ArchivedVersionsPathElement) pathElement;
currentDoc = docPathElement.getDocumentModel();
return navigationContext.navigateToDocument(currentDoc, "TAB_CONTENT_HISTORY");
} else if (elementType == VersionDocumentPathElement.TYPE) {
VersionDocumentPathElement element = (VersionDocumentPathElement) pathElement;
currentDoc = element.getDocumentModel();
return navigationContext.navigateToDocument(currentDoc);
}
return null;
}
/**
* Computes the current path by making calls to backend. TODO: need to change to compute the path from the seam
* context state.
* <p>
* GR: removed the Factory annotation because it made the method be called too early in case of processing that
* involves changing the current document. Multiple invocation of this method is anyway very cheap.
*
* @return
*/
@Override
@Factory(value = "backendPath", scope = EVENT)
public List<PathElement> getBackendPath() {
String viewId = FacesContext.getCurrentInstance().getViewRoot().getViewId();
String viewIdLabel = Pages.instance().getPage(viewId).getDescription();
if (viewIdLabel != null && viewIdLabel.startsWith(BREADCRUMB_PREFIX)) {
return makeBackendPathFromLabel(viewIdLabel.substring(BREADCRUMB_PREFIX.length() + 1));
} else {
return shrinkPathIfNeeded(navigationContext.getCurrentPathList());
}
}
@Factory(value = "isNavigationBreadcrumb", scope = EVENT)
public boolean isNavigationBreadcrumb() {
String viewId = FacesContext.getCurrentInstance().getViewRoot().getViewId();
String viewIdLabel = Pages.instance().getPage(viewId).getDescription();
return !((viewIdLabel != null) && viewIdLabel.startsWith(BREADCRUMB_PREFIX));
}
protected List<PathElement> shrinkPathIfNeeded(List<PathElement> paths) {
if (paths == null || paths.size() <= getMinPathSegmentsLen()) {
this.isPathShrinked = false;
return paths;
}
StringBuffer sb = new StringBuffer();
for (PathElement pe : paths) {
sb.append(pe.getName());
}
String completePath = sb.toString();
if (completePath.length() <= getMaxPathCharLen()) {
this.isPathShrinked = false;
return paths;
}
// shrink path
sb = new StringBuffer();
List<PathElement> shrinkedPath = new ArrayList<PathElement>();
for (int i = paths.size() - 1; i >= 0; i--) {
PathElement pe = paths.get(i);
sb.append(pe.getName());
if (sb.length() < getMaxPathCharLen()) {
shrinkedPath.add(0, pe);
} else {
break;
}
}
// be sure we have at least one item in the breadcrumb otherwise the upnavigation will fail
if (shrinkedPath.size() == 0) {
// this means the current document has a title longer than MAX_PATH_CHAR_LEN !
shrinkedPath.add(0, paths.get(paths.size() - 1));
}
this.isPathShrinked = true;
return shrinkedPath;
}
protected List<PathElement> makeBackendPathFromLabel(String label) {
List<PathElement> pathElements = new ArrayList<PathElement>();
label = resourcesAccessor.getMessages().get(label);
PathElement pathLabel = new TextPathElement(label);
// add the label of the viewId to the path
pathElements.add(pathLabel);
return pathElements;
}
@SuppressWarnings("unchecked")
public List<DocumentModel> getUserDomains() {
if (userDomains == null) {
PageProviderService pageProviderService = Framework.getLocalService(PageProviderService.class);
Map<String, Serializable> properties = new HashMap<>();
properties.put("coreSession", (Serializable) documentManager);
userDomains = ((PageProvider<DocumentModel>) pageProviderService.getPageProvider(
BREADCRUMB_USER_DOMAINS_PROVIDER, null, null, null, properties)).getCurrentPage();
}
return userDomains;
}
public boolean isUserDomain(DocumentModel doc) {
List<DocumentModel> userDomains = getUserDomains();
for (DocumentModel userDomain : userDomains) {
if (doc.getRef().equals(userDomain.getRef())) {
return true;
}
}
return false;
}
@Observer({ EventNames.LOCATION_SELECTION_CHANGED, EventNames.DOCUMENT_CHILDREN_CHANGED,
EventNames.DOCUMENT_CHANGED })
public void resetUserDomains() {
userDomains = null;
}
}