001/* 002 * (C) Copyright 2014-2016 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 * Maxime Hilaire 018 * Florent Guillaume 019 */ 020package org.nuxeo.ecm.directory.core; 021 022import java.io.Serializable; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import org.apache.commons.logging.Log; 032import org.apache.commons.logging.LogFactory; 033import org.nuxeo.ecm.core.api.CloseableCoreSession; 034import org.nuxeo.ecm.core.api.CoreInstance; 035import org.nuxeo.ecm.core.api.CoreSession; 036import org.nuxeo.ecm.core.api.DataModel; 037import org.nuxeo.ecm.core.api.DocumentModel; 038import org.nuxeo.ecm.core.api.DocumentModelList; 039import org.nuxeo.ecm.core.api.IdRef; 040import org.nuxeo.ecm.core.schema.types.Field; 041import org.nuxeo.ecm.directory.BaseSession; 042import org.nuxeo.ecm.directory.DirectoryException; 043import org.nuxeo.ecm.directory.PasswordHelper; 044import org.nuxeo.ecm.directory.Reference; 045 046import com.google.common.base.Function; 047import com.google.common.base.Joiner; 048import com.google.common.collect.Collections2; 049 050/** 051 * Session class for directory on repository 052 * 053 * @since 8.2 054 */ 055public class CoreDirectorySession extends BaseSession { 056 057 protected final String schemaIdField; 058 059 protected final String schemaPasswordField; 060 061 protected final CoreSession coreSession; 062 063 protected final String createPath; 064 065 protected final String docType; 066 067 protected static final String UUID_FIELD = "ecm:uuid"; 068 069 private final static Log log = LogFactory.getLog(CoreDirectorySession.class); 070 071 public CoreDirectorySession(CoreDirectory directory) { 072 super(directory, null); 073 CoreDirectoryDescriptor descriptor = directory.getDescriptor(); 074 coreSession = CoreInstance.openCoreSession(descriptor.getRepositoryName()); 075 schemaIdField = directory.getFieldMapper().getBackendField(getIdField()); 076 schemaPasswordField = directory.getFieldMapper().getBackendField(getPasswordField()); 077 docType = descriptor.docType; 078 createPath = descriptor.getCreatePath(); 079 } 080 081 @Override 082 public CoreDirectory getDirectory() { 083 return (CoreDirectory) directory; 084 } 085 086 @Override 087 public DocumentModel getEntry(String id, boolean fetchReferences) throws DirectoryException { 088 if (UUID_FIELD.equals(getIdField())) { 089 IdRef ref = new IdRef(id); 090 if (coreSession.exists(ref)) { 091 DocumentModel document = coreSession.getDocument(new IdRef(id)); 092 return docType.equals(document.getType()) ? document : null; 093 } else { 094 return null; 095 } 096 } 097 098 StringBuilder sbQuery = new StringBuilder("SELECT * FROM "); 099 sbQuery.append(docType); 100 sbQuery.append(" WHERE "); 101 sbQuery.append(getDirectory().getField(schemaIdField).getName().getPrefixedName()); 102 sbQuery.append(" = '"); 103 sbQuery.append(id); 104 sbQuery.append("' AND ecm:path STARTSWITH '"); 105 sbQuery.append(createPath); 106 sbQuery.append("'"); 107 108 DocumentModelList listDoc = coreSession.query(sbQuery.toString()); 109 // TODO : deal with references 110 if (!listDoc.isEmpty()) { 111 // Should have only one 112 if (listDoc.size() > 1) { 113 log.warn(String.format( 114 "Found more than one result in getEntry, the first result only will be returned")); 115 } 116 DocumentModel docResult = listDoc.get(0); 117 if (isReadOnly()) { 118 BaseSession.setReadOnlyEntry(docResult); 119 } 120 return docResult; 121 } 122 return null; 123 } 124 125 @Override 126 public DocumentModelList getEntries() throws DirectoryException { 127 throw new UnsupportedOperationException(); 128 } 129 130 private String getPrefixedFieldName(String fieldName) { 131 if (UUID_FIELD.equals(fieldName)) { 132 return fieldName; 133 } 134 Field schemaField = getDirectory().getField(fieldName); 135 return schemaField.getName().getPrefixedName(); 136 } 137 138 @Override 139 public DocumentModel createEntryWithoutReferences(Map<String, Object> fieldMap) throws DirectoryException { 140 // TODO once references are implemented 141 throw new UnsupportedOperationException(); 142 } 143 144 @Override 145 protected List<String> updateEntryWithoutReferences(DocumentModel docModel) throws DirectoryException { 146 // TODO once references are implemented 147 throw new UnsupportedOperationException(); 148 } 149 150 @Override 151 protected void deleteEntryWithoutReferences(String id) throws DirectoryException { 152 // TODO once references are implemented 153 throw new UnsupportedOperationException(); 154 } 155 156 @Override 157 public DocumentModel createEntry(Map<String, Object> fieldMap) throws DirectoryException { 158 if (isReadOnly()) { 159 log.warn(String.format("The directory '%s' is in read-only mode, could not create entry.", 160 directory.getName())); 161 return null; 162 } 163 // TODO : deal with auto-versionning 164 // TODO : deal with encrypted password 165 // TODO : deal with references 166 Map<String, Object> properties = new HashMap<>(); 167 List<String> createdRefs = new LinkedList<>(); 168 for (String fieldId : fieldMap.keySet()) { 169 if (getDirectory().isReference(fieldId)) { 170 createdRefs.add(fieldId); 171 } 172 Object value = fieldMap.get(fieldId); 173 properties.put(getMappedPrefixedFieldName(fieldId), value); 174 } 175 176 String rawid = (String) properties.get(getPrefixedFieldName(schemaIdField)); 177 if (rawid == null && (!UUID_FIELD.equals(getIdField()))) { 178 throw new DirectoryException(String.format("Entry is missing id field '%s'", schemaIdField)); 179 } 180 181 DocumentModel docModel = coreSession.createDocumentModel(createPath, rawid, docType); 182 183 docModel.setProperties(schemaName, properties); 184 DocumentModel createdDoc = coreSession.createDocument(docModel); 185 186 for (String referenceFieldName : createdRefs) { 187 Reference reference = directory.getReference(referenceFieldName); 188 List<String> targetIds = (List<String>) createdDoc.getProperty(schemaName, referenceFieldName); 189 reference.setTargetIdsForSource(docModel.getId(), targetIds); 190 } 191 return docModel; 192 } 193 194 @Override 195 public void updateEntry(DocumentModel docModel) throws DirectoryException { 196 if (isReadOnly()) { 197 log.warn(String.format("The directory '%s' is in read-only mode, could not update entry.", 198 directory.getName())); 199 } else { 200 201 if (!isReadOnlyEntry(docModel)) { 202 203 String id = (String) docModel.getProperty(schemaName, getIdField()); 204 if (id == null) { 205 throw new DirectoryException( 206 "Can not update entry with a null id for document ref " + docModel.getRef()); 207 } else { 208 if (getEntry(id) == null) { 209 throw new DirectoryException( 210 String.format("Update entry failed : Entry with id '%s' not found !", id)); 211 } else { 212 213 DataModel dataModel = docModel.getDataModel(schemaName); 214 Map<String, Object> updatedProps = new HashMap<String, Object>(); 215 List<String> updatedRefs = new LinkedList<String>(); 216 217 for (String field : docModel.getProperties(schemaName).keySet()) { 218 String schemaField = getMappedPrefixedFieldName(field); 219 if (!dataModel.isDirty(schemaField)) { 220 if (getDirectory().isReference(field)) { 221 updatedRefs.add(field); 222 } else { 223 updatedProps.put(schemaField, docModel.getProperties(schemaName).get(field)); 224 } 225 } 226 227 } 228 229 docModel.setProperties(schemaName, updatedProps); 230 231 // update reference fields 232 for (String referenceFieldName : updatedRefs) { 233 Reference reference = directory.getReference(referenceFieldName); 234 List<String> targetIds = (List<String>) docModel.getProperty(schemaName, 235 referenceFieldName); 236 reference.setTargetIdsForSource(docModel.getId(), targetIds); 237 } 238 239 coreSession.saveDocument(docModel); 240 } 241 242 } 243 } 244 } 245 } 246 247 @Override 248 public void deleteEntry(DocumentModel docModel) throws DirectoryException { 249 String id = (String) docModel.getProperty(schemaName, schemaIdField); 250 deleteEntry(id); 251 } 252 253 @Override 254 public void deleteEntry(String id) throws DirectoryException { 255 if (isReadOnly()) { 256 log.warn(String.format("The directory '%s' is in read-only mode, could not delete entry.", 257 directory.getName())); 258 } else { 259 if (id == null) { 260 throw new DirectoryException("Can not update entry with a null id "); 261 } else { 262 checkDeleteConstraints(id); 263 DocumentModel docModel = getEntry(id); 264 if (docModel != null) { 265 coreSession.removeDocument(docModel.getRef()); 266 } 267 } 268 } 269 } 270 271 @Override 272 public void deleteEntry(String id, Map<String, String> map) throws DirectoryException { 273 if (isReadOnly()) { 274 log.warn(String.format("The directory '%s' is in read-only mode, could not delete entry.", 275 directory.getName())); 276 } 277 278 Map<String, Serializable> props = new HashMap<>(map); 279 props.put(schemaIdField, id); 280 281 DocumentModelList docList = query(props); 282 if (!docList.isEmpty()) { 283 if (docList.size() > 1) { 284 log.warn( 285 String.format("Found more than one result in getEntry, the first result only will be deleted")); 286 } 287 deleteEntry(docList.get(0)); 288 } else { 289 throw new DirectoryException(String.format("Delete entry failed : Entry with id '%s' not found !", id)); 290 } 291 292 } 293 294 protected String getMappedPrefixedFieldName(String fieldName) { 295 String backendFieldId = getDirectory().getFieldMapper().getBackendField(fieldName); 296 return getPrefixedFieldName(backendFieldId); 297 } 298 299 @Override 300 public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy, 301 boolean fetchReferences, int limit, int offset) throws DirectoryException { 302 StringBuilder sbQuery = new StringBuilder("SELECT * FROM "); 303 sbQuery.append(docType); 304 // TODO deal with fetch ref 305 if (!filter.isEmpty() || !fulltext.isEmpty() || (createPath != null && !createPath.isEmpty())) { 306 sbQuery.append(" WHERE "); 307 } 308 int i = 1; 309 boolean hasFilter = false; 310 for (String filterKey : filter.keySet()) { 311 if (!fulltext.contains(filterKey)) { 312 sbQuery.append(getMappedPrefixedFieldName(filterKey)); 313 sbQuery.append(" = "); 314 sbQuery.append("'"); 315 sbQuery.append(filter.get(filterKey)); 316 sbQuery.append("'"); 317 if (i < filter.size()) { 318 sbQuery.append(" AND "); 319 i++; 320 } 321 hasFilter = true; 322 } 323 324 } 325 if (hasFilter && filter.size() > 0 && fulltext.size() > 0) { 326 sbQuery.append(" AND "); 327 } 328 if (fulltext.size() > 0) { 329 330 Collection<String> fullTextValues = Collections2.transform(fulltext, new Function<String, String>() { 331 332 @Override 333 public String apply(String key) { 334 return (String) filter.get(key); 335 } 336 337 }); 338 sbQuery.append("ecm:fulltext"); 339 sbQuery.append(" = "); 340 sbQuery.append("'"); 341 sbQuery.append(Joiner.on(" ").join(fullTextValues)); 342 sbQuery.append("'"); 343 } 344 345 if ((createPath != null && !createPath.isEmpty())) { 346 if (filter.size() > 0 || fulltext.size() > 0) { 347 sbQuery.append(" AND "); 348 } 349 sbQuery.append(" ecm:path STARTSWITH '"); 350 sbQuery.append(createPath); 351 sbQuery.append("'"); 352 } 353 354 // Filter facetFilter = new FacetFilter(FacetNames.VERSIONABLE, true); 355 356 DocumentModelList resultsDoc = coreSession.query(sbQuery.toString(), null, limit, offset, false); 357 358 if (isReadOnly()) { 359 for (DocumentModel documentModel : resultsDoc) { 360 BaseSession.setReadOnlyEntry(documentModel); 361 } 362 } 363 return resultsDoc; 364 365 } 366 367 @Override 368 public void close() throws DirectoryException { 369 ((CloseableCoreSession) coreSession).close(); 370 getDirectory().removeSession(this); 371 } 372 373 @Override 374 public List<String> getProjection(Map<String, Serializable> filter, String columnName) throws DirectoryException { 375 throw new UnsupportedOperationException(); 376 } 377 378 @Override 379 public List<String> getProjection(Map<String, Serializable> filter, Set<String> fulltext, String columnName) 380 throws DirectoryException { 381 throw new UnsupportedOperationException(); 382 } 383 384 @Override 385 public boolean authenticate(String username, String password) { 386 DocumentModel entry = getEntry(username); 387 if (entry == null) { 388 return false; 389 } 390 String storedPassword = (String) entry.getProperty(schemaName, schemaPasswordField); 391 return PasswordHelper.verifyPassword(password, storedPassword); 392 } 393 394 @Override 395 public boolean isAuthenticating() { 396 return schemaPasswordField != null; 397 } 398 399 @Override 400 public boolean hasEntry(String id) { 401 return getEntry(id) != null; 402 } 403}