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