001/* 002 * (C) Copyright 2014-2018 Nuxeo (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 * Nicolas Chapurlat <nchapurlat@nuxeo.com> 018 */ 019 020package org.nuxeo.ecm.directory; 021 022import java.io.Serializable; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.Locale; 026import java.util.Map; 027 028import org.apache.commons.lang3.StringUtils; 029import org.nuxeo.ecm.core.api.DocumentModel; 030import org.nuxeo.ecm.core.schema.types.resolver.AbstractObjectResolver; 031import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver; 032import org.nuxeo.ecm.directory.api.DirectoryEntry; 033import org.nuxeo.ecm.directory.api.DirectoryService; 034import org.nuxeo.runtime.api.Framework; 035 036/** 037 * This {@link ObjectResolver} allows to manage integrity for fields containing references to directory's entry. 038 * <p> 039 * References contains the directory entry id. 040 * <p> 041 * To use it, put the following code in your schema XSD (don't forget the directory name): 042 * 043 * <pre> 044 * {@code 045 * <xs:element name="carBrand"> 046 * <xs:simpleType> 047 * <xs:restriction base="xs:string" ref:resolver="directoryResolver" ref:directory="carBrandsDirectory" /> 048 * </xs:simpleType> 049 * </xs:element> 050 * } 051 * </pre> 052 * <p> 053 * For hierarchical directories, which entries reference other entries. You can manage a specific reference containing 054 * the full entry path. You have to specify the parent field and the separator used to encode the reference. 055 * 056 * <pre> 057 * {@code 058 * <xs:element name="coverage"> 059 * <xs:simpleType> 060 * <xs:restriction base="xs:string" ref:resolver="directoryResolver" ref:directory="l10ncoverage" ref:parentField="parent" ref:separator="/" /> 061 * </xs:simpleType> 062 * </xs:element> 063 * } 064 * </pre> 065 * <p> 066 * It's not necessary to define parentField and separator for directory using schema ending by xvocabulary. The feature 067 * is automatically enable. 068 * 069 * @since 7.1 070 */ 071public class DirectoryEntryResolver extends AbstractObjectResolver implements ObjectResolver { 072 073 private static final long serialVersionUID = 1L; 074 075 public static final String NAME = "directoryResolver"; 076 077 public static final String PARAM_DIRECTORY = "directory"; 078 079 public static final String PARAM_PARENT_FIELD = "parentField"; 080 081 public static final String PARAM_SEPARATOR = "separator"; 082 083 private String idField; 084 085 private String schema; 086 087 private boolean hierarchical = false; 088 089 private String parentField = null; 090 091 private String separator = null; 092 093 private List<Class<?>> managedClasses = null; 094 095 private String directoryName; 096 097 @Override 098 public void configure(Map<String, String> parameters) throws IllegalArgumentException, IllegalStateException { 099 super.configure(parameters); 100 directoryName = parameters.get(PARAM_DIRECTORY); 101 if (directoryName != null) { 102 directoryName = directoryName.trim(); 103 } 104 if (directoryName == null || directoryName.isEmpty()) { 105 throw new IllegalArgumentException("missing directory parameter. A directory name is necessary"); 106 } 107 Directory directory = getDirectory(); 108 idField = directory.getIdField(); 109 schema = directory.getSchema(); 110 if (schema.endsWith("xvocabulary")) { 111 hierarchical = true; 112 parentField = "parent"; 113 separator = "/"; 114 } 115 String parentFieldParam = StringUtils.trim(parameters.get(PARAM_PARENT_FIELD)); 116 String separatorParam = StringUtils.trim(parameters.get(PARAM_SEPARATOR)); 117 if (!StringUtils.isBlank(parentFieldParam) && !StringUtils.isBlank(separatorParam)) { 118 hierarchical = true; 119 parentField = parentFieldParam; 120 separator = separatorParam; 121 } 122 this.parameters.put(PARAM_DIRECTORY, directoryName); 123 } 124 125 @Override 126 public List<Class<?>> getManagedClasses() { 127 if (managedClasses == null) { 128 managedClasses = new ArrayList<>(); 129 managedClasses.add(DirectoryEntry.class); 130 } 131 return managedClasses; 132 } 133 134 public Directory getDirectory() { 135 DirectoryService directoryService = Framework.getService(DirectoryService.class); 136 Directory directory = directoryService.getDirectory(directoryName); 137 if (directory == null) { 138 throw new IllegalArgumentException(String.format("the directory \"%s\" was not found", directoryName)); 139 } 140 return directory; 141 } 142 143 @Override 144 public String getName() { 145 checkConfig(); 146 return NAME; 147 } 148 149 @Override 150 public Object fetch(Object value) throws IllegalStateException { 151 checkConfig(); 152 if (value instanceof String) { 153 String id = (String) value; 154 if (hierarchical) { 155 String[] ids = StringUtils.split(id, separator); 156 if (ids.length > 0) { 157 id = ids[ids.length - 1]; 158 } else { 159 return null; 160 } 161 } 162 try (Session session = getDirectory().getSession()) { 163 String finalId = id; // Effectively final 164 DocumentModel doc = Framework.doPrivileged(() -> session.getEntry(finalId)); 165 if (doc != null) { 166 return new DirectoryEntry(directoryName, doc); 167 } 168 return null; 169 } 170 } 171 return null; 172 } 173 174 @Override 175 public <T> T fetch(Class<T> type, Object value) throws IllegalStateException { 176 checkConfig(); 177 DirectoryEntry doc = (DirectoryEntry) fetch(value); 178 if (doc != null) { 179 if (type.isInstance(doc)) { 180 return type.cast(doc); 181 } 182 if (type.isInstance(doc.getDocumentModel())) { 183 return type.cast(doc.getDocumentModel()); 184 } 185 } 186 return null; 187 } 188 189 @Override 190 public Serializable getReference(Object entity) throws IllegalStateException { 191 checkConfig(); 192 DocumentModel entry = null; 193 if (entity != null) { 194 if (entity instanceof DirectoryEntry) { 195 entry = ((DirectoryEntry) entity).getDocumentModel(); 196 } else if (entity instanceof DocumentModel) { 197 entry = (DocumentModel) entity; 198 } 199 if (entry != null) { 200 if (!entry.hasSchema(schema)) { 201 return null; 202 } 203 String result = (String) entry.getProperty(schema, idField); 204 if (hierarchical) { 205 String parent = (String) entry.getProperty(schema, parentField); 206 try (Session session = getDirectory().getSession()) { 207 while (parent != null) { 208 String finalParent = parent; // Effectively final 209 entry = Framework.doPrivileged(() -> session.getEntry(finalParent)); 210 if (entry == null) { 211 break; 212 } 213 result = parent + separator + result; 214 parent = (String) entry.getProperty(schema, parentField); 215 } 216 } 217 } 218 return result; 219 } 220 } 221 return null; 222 } 223 224 @Override 225 public String getConstraintErrorMessage(Object invalidValue, Locale locale) { 226 checkConfig(); 227 return Helper.getConstraintErrorMessage(this, invalidValue, locale, directoryName); 228 } 229 230}