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