001/* 002 * (C) Copyright 2010 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 */ 018package org.nuxeo.ecm.webapp.directory; 019 020import java.io.Serializable; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.Comparator; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Locale; 027import java.util.Map; 028 029import javax.faces.context.FacesContext; 030 031import org.apache.commons.lang.ObjectUtils; 032import org.apache.commons.lang.StringUtils; 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.ecm.core.api.DocumentModel; 036import org.nuxeo.ecm.core.api.DocumentModelList; 037import org.nuxeo.ecm.core.api.PropertyException; 038import org.nuxeo.ecm.core.schema.SchemaManager; 039import org.nuxeo.ecm.core.schema.types.Schema; 040import org.nuxeo.ecm.directory.DirectoryException; 041import org.nuxeo.ecm.directory.Session; 042import org.nuxeo.ecm.directory.api.DirectoryService; 043import org.nuxeo.ecm.platform.ui.web.directory.DirectoryHelper; 044import org.nuxeo.runtime.api.Framework; 045 046/** 047 * A vocabulary tree node based on l10nvocabulary or l10nxvocabulary directory. These schemas store translations in 048 * columns of the form label_xx_XX or label_xx. The label of a node is retrieved from column label_xx_XX (where xx_XX is 049 * the current locale name) if it exists, from column label_xx (where xx is the current locale language) else. If this 050 * one doesn't exist either, the english label (from label_en) is used. 051 * 052 * @since 5.5 053 * @author <a href="mailto:qlamerand@nuxeo.com">Quentin Lamerand</a> 054 */ 055public class VocabularyTreeNode { 056 057 private static final Log log = LogFactory.getLog(VocabularyTreeNode.class); 058 059 public static final String PARENT_FIELD_ID = "parent"; 060 061 public static final String LABEL_FIELD_PREFIX = "label_"; 062 063 public static final String DEFAULT_LANGUAGE = "en"; 064 065 public static final String OBSOLETE_FIELD = "obsolete"; 066 067 protected final String path; 068 069 protected final int level; 070 071 protected String id; 072 073 protected String label; 074 075 protected DirectoryService directoryService; 076 077 protected List<VocabularyTreeNode> children; 078 079 protected String vocabularyName; 080 081 protected DocumentModelList childrenEntries; 082 083 protected boolean displayObsoleteEntries; 084 085 protected String orderingField; 086 087 protected Comparable orderingValue; 088 089 protected char keySeparator; 090 091 public VocabularyTreeNode(int level, String id, String description, String path, String vocabularyName, 092 DirectoryService directoryService) { 093 this(level, id, description, path, vocabularyName, directoryService, false, '/', null); 094 } 095 096 public VocabularyTreeNode(int level, String id, String description, String path, String vocabularyName, 097 DirectoryService directoryService, boolean displayObsoleteEntries, char keySeparator, String orderingField) { 098 this(level, id, description, path, vocabularyName, directoryService, displayObsoleteEntries, keySeparator, 099 orderingField, null); 100 } 101 102 public VocabularyTreeNode(int level, String id, String description, String path, String vocabularyName, 103 DirectoryService directoryService, boolean displayObsoleteEntries, char keySeparator, String orderingField, 104 Comparable orderingValue) { 105 this.level = level; 106 this.id = id; 107 this.label = description; 108 this.path = path; 109 this.vocabularyName = vocabularyName; 110 this.directoryService = directoryService; 111 this.displayObsoleteEntries = displayObsoleteEntries; 112 this.keySeparator = keySeparator; 113 this.orderingField = orderingField; 114 this.orderingValue = orderingValue; 115 } 116 117 public List<VocabularyTreeNode> getChildren() { 118 if (children != null) { 119 return children; 120 } 121 children = new ArrayList<VocabularyTreeNode>(); 122 String schemaName = getDirectorySchema(); 123 DocumentModelList results = getChildrenEntries(); 124 Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale(); 125 for (DocumentModel result : results) { 126 if (result == null) { 127 continue; 128 } 129 String childIdendifier = result.getId(); 130 String childLabel = computeLabel(locale, result, schemaName); 131 String childPath; 132 if ("".equals(path)) { 133 childPath = childIdendifier; 134 } else { 135 childPath = path + keySeparator + childIdendifier; 136 } 137 Comparable orderingValue = null; 138 if (!StringUtils.isBlank(orderingField)) { 139 orderingValue = (Comparable) result.getProperty(schemaName, orderingField); 140 } 141 children.add(new VocabularyTreeNode(level + 1, childIdendifier, childLabel, childPath, vocabularyName, 142 getDirectoryService(), displayObsoleteEntries, keySeparator, orderingField, orderingValue)); 143 } 144 145 // sort children 146 Comparator<? super VocabularyTreeNode> cmp; 147 if (StringUtils.isBlank(orderingField) || "label".equals(orderingField)) { 148 cmp = new LabelComparator(); // sort alphabetically 149 } else { 150 cmp = new OrderingComparator(); 151 } 152 Collections.sort(children, cmp); 153 154 return children; 155 } 156 157 public static String computeLabel(Locale locale, DocumentModel entry, String schemaName) { 158 if (entry == null) { 159 return null; 160 } 161 String fieldName = LABEL_FIELD_PREFIX + locale.toString(); 162 String label = null; 163 try { 164 label = (String) entry.getProperty(schemaName, fieldName); 165 } catch (PropertyException e) { 166 } 167 if (label == null) { 168 fieldName = LABEL_FIELD_PREFIX + locale.getLanguage(); 169 try { 170 label = (String) entry.getProperty(schemaName, fieldName); 171 } catch (PropertyException e) { 172 } 173 } 174 if (label == null) { 175 fieldName = LABEL_FIELD_PREFIX + DEFAULT_LANGUAGE; 176 try { 177 label = (String) entry.getProperty(schemaName, fieldName); 178 } catch (PropertyException e) { 179 } 180 } 181 return label; 182 } 183 184 private class LabelComparator implements Comparator<VocabularyTreeNode> { 185 @Override 186 public int compare(VocabularyTreeNode o1, VocabularyTreeNode o2) { 187 return ObjectUtils.compare(o1.getLabel(), o2.getLabel()); 188 } 189 } 190 191 private class OrderingComparator implements Comparator<VocabularyTreeNode> { 192 @Override 193 public int compare(VocabularyTreeNode o1, VocabularyTreeNode o2) { 194 if (o1.getOrdering() == null && o2.getOrdering() != null) { 195 return -1; 196 } else if (o1.getOrdering() != null && o2.getOrdering() == null) { 197 return 1; 198 } else if (o1.getOrdering() == o2.getOrdering()) { 199 return 0; 200 } else { 201 return o1.getOrdering().compareTo(o2.getOrdering()); 202 } 203 } 204 } 205 206 protected DocumentModelList getChildrenEntries() { 207 if (childrenEntries != null) { 208 // memorized directory lookup since directory content is not 209 // suppose to change 210 // XXX: use the cache manager instead of field caching strategy 211 return childrenEntries; 212 } 213 try (Session session = getDirectorySession()) { 214 Map<String, Serializable> filter = new HashMap<String, Serializable>(); 215 216 String directorySchema = getDirectorySchema(); 217 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 218 Schema schema = schemaManager.getSchema(directorySchema); 219 if (schema == null) { 220 throw new DirectoryException(directorySchema + " is not a registered directory"); 221 } 222 if (level == 0 && schema.hasField(PARENT_FIELD_ID)) { 223 // filter on empty parent 224 filter.put(PARENT_FIELD_ID, ""); 225 } else { 226 String[] bitsOfPath = StringUtils.split(path, keySeparator); 227 filter.put(PARENT_FIELD_ID, bitsOfPath[level - 1]); 228 } 229 230 if (!displayObsoleteEntries) { 231 filter.put(OBSOLETE_FIELD, Long.valueOf(0)); 232 } 233 234 if (filter.isEmpty()) { 235 childrenEntries = session.getEntries(); 236 } else { 237 childrenEntries = session.query(filter); 238 } 239 return childrenEntries; 240 } 241 } 242 243 public String getId() { 244 return id; 245 } 246 247 public String getLabel() { 248 return label; 249 } 250 251 public String getPath() { 252 return path; 253 } 254 255 public Comparable getOrdering() { 256 return orderingValue; 257 } 258 259 protected DirectoryService getDirectoryService() { 260 if (directoryService == null) { 261 directoryService = DirectoryHelper.getDirectoryService(); 262 } 263 return directoryService; 264 } 265 266 protected String getDirectorySchema() { 267 return getDirectoryService().getDirectorySchema(vocabularyName); 268 } 269 270 protected Session getDirectorySession() { 271 return getDirectoryService().open(vocabularyName); 272 } 273 274}