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