/*
* (C) Copyright 2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* Nuxeo - initial API and implementation
*
* $Id: DirectoryTreeNode.java 29611 2008-01-24 16:51:03Z gracinet $
*/
package org.nuxeo.ecm.webapp.directory;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.faces.context.FacesContext;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.seam.Component;
import org.jboss.seam.core.Events;
import org.nuxeo.common.utils.i18n.I18NUtils;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.ClientRuntimeException;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelList;
import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
import org.nuxeo.ecm.core.schema.SchemaManager;
import org.nuxeo.ecm.core.schema.types.Schema;
import org.nuxeo.ecm.directory.DirectoryException;
import org.nuxeo.ecm.directory.Session;
import org.nuxeo.ecm.directory.api.DirectoryService;
import org.nuxeo.ecm.platform.contentview.jsf.ContentView;
import org.nuxeo.ecm.platform.contentview.seam.ContentViewActions;
import org.nuxeo.ecm.platform.ui.web.directory.DirectoryHelper;
import org.nuxeo.ecm.platform.ui.web.util.SeamContextHelper;
import org.nuxeo.ecm.webapp.helpers.EventNames;
import org.nuxeo.ecm.webapp.tree.TreeActions;
import org.nuxeo.ecm.webapp.tree.TreeActionsBean;
import org.nuxeo.runtime.api.Framework;
/**
* Register directory tree configurations to make them available to the
* DirectoryTreeManagerBean to build DirectoryTreeNode instances.
*
* @author <a href="mailto:ogrisel@nuxeo.com">Olivier Grisel</a>
*/
public class DirectoryTreeNode {
private static final Log log = LogFactory.getLog(DirectoryTreeNode.class);
public static final String PARENT_FIELD_ID = "parent";
private static final String LABEL_FIELD_ID = "label";
protected final String path;
protected final int level;
protected Boolean open = null;
protected final DirectoryTreeDescriptor config;
protected String identifier;
protected String description;
protected boolean leaf = false;
protected String type = "defaultDirectoryTreeNode";
protected DirectoryService directoryService;
protected ContentView contentView;
protected DocumentModelList childrenEntries;
protected List<DirectoryTreeNode> children;
public DirectoryTreeNode(int level, DirectoryTreeDescriptor config,
String identifier, String description, String path,
DirectoryService directoryService) {
this.level = level;
this.config = config;
this.identifier = identifier;
this.description = description;
this.path = path;
this.directoryService = directoryService;
}
protected List<String> processSelectedValuesOnMultiSelect(String value,
List<String> values) {
if (values.contains(value)) {
values.remove(value);
} else {
// unselect all previous selection that are either more
// generic or more specific
List<String> valuesToRemove = new ArrayList<String>();
String valueSlash = value + "/";
for (String existingSelection : values) {
String existingSelectionSlash = existingSelection + "/";
if (existingSelectionSlash.startsWith(valueSlash)
|| valueSlash.startsWith(existingSelectionSlash)) {
valuesToRemove.add(existingSelection);
}
}
values.removeAll(valuesToRemove);
// add the new selection
values.add(value);
}
return values;
}
@SuppressWarnings("unchecked")
public String selectNode() throws ClientException {
if (config.hasContentViewSupport()) {
DocumentModel searchDoc = getContentViewSearchDocumentModel();
if (searchDoc != null) {
String fieldName = config.getFieldName();
String schemaName = config.getSchemaName();
if (config.isMultiselect()) {
List<String> values = (List<String>) searchDoc.getProperty(
schemaName, fieldName);
values = processSelectedValuesOnMultiSelect(path, values);
searchDoc.setProperty(schemaName, fieldName, values);
} else {
searchDoc.setProperty(schemaName, fieldName, path);
}
if (contentView != null) {
contentView.refreshPageProvider();
}
} else {
log.error("Cannot select node: search document model is null");
}
} else {
log.error(String.format(
"Cannot select node on tree '%s': no content view available",
identifier));
}
// raise this event in order to reset the documents lists from
// 'conversationDocumentsListsManager'
Events.instance().raiseEvent(
EventNames.FOLDERISHDOCUMENT_SELECTION_CHANGED,
new DocumentModelImpl("Folder"));
pathProcessing();
return config.getOutcome();
}
@SuppressWarnings("unchecked")
public boolean isSelected() throws ClientException {
if (config.hasContentViewSupport()) {
DocumentModel searchDoc = getContentViewSearchDocumentModel();
if (searchDoc != null) {
String fieldName = config.getFieldName();
String schemaName = config.getSchemaName();
if (config.isMultiselect()) {
List<Object> values = (List<Object>) searchDoc.getProperty(
schemaName, fieldName);
return values.contains(path);
} else {
return path.equals(searchDoc.getProperty(schemaName,
fieldName));
}
} else {
log.error("Cannot check if node is selected: "
+ "search document model is null");
}
} else {
log.error(String.format(
"Cannot check if node is selected on tree '%s': no "
+ "content view available", identifier));
}
return false;
}
public int getChildCount() {
if (isLastLevel()) {
return 0;
}
try {
return getChildrenEntries().size();
} catch (ClientException e) {
log.error(e);
return 0;
}
}
public List<DirectoryTreeNode> getChildren() {
if (children != null) {
// return last computed state
return children;
}
children = new ArrayList<DirectoryTreeNode>();
if (isLastLevel()) {
return children;
}
try {
String schema = getDirectorySchema();
DocumentModelList results = getChildrenEntries();
FacesContext context = FacesContext.getCurrentInstance();
for (DocumentModel result : results) {
String childIdendifier = result.getId();
String childDescription = translate(context,
(String) result.getProperty(schema, LABEL_FIELD_ID));
String childPath;
if ("".equals(path)) {
childPath = childIdendifier;
} else {
childPath = path + '/' + childIdendifier;
}
children.add(new DirectoryTreeNode(level + 1, config,
childIdendifier, childDescription, childPath,
getDirectoryService()));
}
// sort children
Comparator<? super DirectoryTreeNode> cmp = new FieldComparator();
Collections.sort(children, cmp);
return children;
} catch (ClientException e) {
log.error(e);
return children;
}
}
private class FieldComparator implements Comparator<DirectoryTreeNode> {
@Override
public int compare(DirectoryTreeNode o1, DirectoryTreeNode o2) {
return ObjectUtils.compare(o1.getDescription(), o2.getDescription());
}
}
protected static String translate(FacesContext context, String label) {
String bundleName = context.getApplication().getMessageBundle();
Locale locale = context.getViewRoot().getLocale();
label = I18NUtils.getMessageString(bundleName, label, null, locale);
return label;
}
protected DocumentModelList getChildrenEntries() throws ClientException {
if (childrenEntries != null) {
// memorized directory lookup since directory content is not
// suppose to change
// XXX: use the cache manager instead of field caching strategy
return childrenEntries;
}
Session session = getDirectorySession();
try {
if (level == 0) {
String schemaName = getDirectorySchema();
SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class);
Schema schema = schemaManager.getSchema(schemaName);
if (schema.hasField(PARENT_FIELD_ID)) {
// filter on empty parent
Map<String, Serializable> filter = new HashMap<String, Serializable>();
filter.put(PARENT_FIELD_ID, "");
childrenEntries = session.query(filter);
} else {
childrenEntries = session.getEntries();
}
} else {
Map<String, Serializable> filter = new HashMap<String, Serializable>();
String[] bitsOfPath = path.split("/");
filter.put(PARENT_FIELD_ID, bitsOfPath[level - 1]);
childrenEntries = session.query(filter);
}
return childrenEntries;
} finally {
session.close();
}
}
public String getDescription() {
if (level == 0) {
return translate(FacesContext.getCurrentInstance(), description);
}
return description;
}
public String getIdentifier() {
return identifier;
}
public String getPath() {
return path;
}
public String getType() {
return type;
}
public boolean isLeaf() {
return leaf || isLastLevel() || getChildCount() == 0;
}
public void setDescription(String description) {
this.description = description;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public void setLeaf(boolean leaf) {
this.leaf = leaf;
}
public void setType(String type) {
this.type = type;
}
protected DirectoryService getDirectoryService() {
if (directoryService == null) {
directoryService = DirectoryHelper.getDirectoryService();
}
return directoryService;
}
protected String getDirectoryName() throws ClientException {
String name = config.getDirectories()[level];
if (name == null) {
throw new ClientException(
"could not find directory name for level=" + level);
}
return name;
}
protected String getDirectorySchema() throws ClientException {
return getDirectoryService().getDirectorySchema(getDirectoryName());
}
protected Session getDirectorySession() throws ClientException {
return getDirectoryService().open(getDirectoryName());
}
protected void lookupContentView() throws ClientException {
if (contentView != null) {
return;
}
SeamContextHelper seamContextHelper = new SeamContextHelper();
ContentViewActions cva = (ContentViewActions) seamContextHelper.get("contentViewActions");
contentView = cva.getContentView(config.getContentView());
if (contentView == null) {
throw new ClientException("no content view registered as "
+ config.getContentView());
}
}
protected DocumentModel getContentViewSearchDocumentModel()
throws ClientException {
lookupContentView();
if (contentView != null) {
return contentView.getSearchDocumentModel();
}
return null;
}
protected boolean isLastLevel() {
return config.getDirectories().length == level;
}
public void pathProcessing() throws DirectoryException {
if (config.isMultiselect()) {
// no breadcrumbs management with multiselect
return;
}
String aPath = null;
if (config.hasContentViewSupport()) {
try {
DocumentModel searchDoc = getContentViewSearchDocumentModel();
if (searchDoc != null) {
aPath = (String) searchDoc.getProperty(
config.getSchemaName(), config.getFieldName());
} else {
log.error("Cannot perform path preprocessing: "
+ "search document model is null");
}
} catch (ClientException e) {
throw new ClientRuntimeException(e);
}
}
if (aPath != null && aPath != "") {
String[] bitsOfPath = aPath.split("/");
String myPath = "";
String property = "";
for (int b = 0; b < bitsOfPath.length; b++) {
String dirName = config.getDirectories()[b];
if (dirName == null) {
throw new DirectoryException(
"Could not find directory name for key=" + b);
}
Session session = getDirectoryService().open(dirName);
DocumentModel docMod = session.getEntry(bitsOfPath[b]);
try {
// take first schema: directory entries only have one
final String schemaName = docMod.getSchemas()[0];
property = (String) docMod.getProperty(schemaName,
LABEL_FIELD_ID);
} catch (ClientException e) {
throw new DirectoryException(e);
}
myPath = myPath + property + '/';
session.close();
}
Events.instance().raiseEvent("PATH_PROCESSED", myPath);
} else {
Events.instance().raiseEvent("PATH_PROCESSED", "");
}
}
/**
* @deprecated since 6.0, use {@link #isOpen()} instead
*/
@Deprecated
public boolean isOpened() {
return isOpen();
}
public boolean isOpen() {
if (open == null) {
final TreeActions treeActionBean = (TreeActionsBean) Component.getInstance("treeActions");
if (!treeActionBean.isNodeExpandEvent()) {
if (!config.isMultiselect() && config.hasContentViewSupport()) {
DocumentModel searchDoc = getContentViewSearchDocumentModel();
if (searchDoc != null) {
String fieldName = config.getFieldName();
String schemaName = config.getSchemaName();
Object value = searchDoc.getProperty(schemaName,
fieldName);
if (value instanceof String) {
open = Boolean.valueOf(((String) value).startsWith(path));
}
} else {
log.error("Cannot check if node is opened: "
+ "search document model is null");
}
} else {
log.error(String.format(
"Cannot check if node is opened on tree '%s': no "
+ "content view available", identifier));
}
}
}
return Boolean.TRUE.equals(open);
}
public void setOpen(boolean open) {
this.open = Boolean.valueOf(open);
}
}