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.query.sql.model.QueryBuilder; 041import org.nuxeo.ecm.core.schema.types.Field; 042import org.nuxeo.ecm.directory.BaseSession; 043import org.nuxeo.ecm.directory.DirectoryException; 044import org.nuxeo.ecm.directory.PasswordHelper; 045import org.nuxeo.ecm.directory.Reference; 046 047import com.google.common.base.Function; 048import com.google.common.base.Joiner; 049import com.google.common.collect.Collections2; 050 051/** 052 * Session class for directory on repository 053 * 054 * @since 8.2 055 */ 056public class CoreDirectorySession extends BaseSession { 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, null); 074 CoreDirectoryDescriptor descriptor = directory.getDescriptor(); 075 coreSession = CoreInstance.openCoreSession(descriptor.getRepositoryName()); 076 schemaIdField = directory.getFieldMapper().getBackendField(getIdField()); 077 schemaPasswordField = directory.getFieldMapper().getBackendField(getPasswordField()); 078 docType = descriptor.docType; 079 createPath = descriptor.getCreatePath(); 080 } 081 082 @Override 083 public CoreDirectory getDirectory() { 084 return (CoreDirectory) directory; 085 } 086 087 @Override 088 public DocumentModel getEntry(String id, boolean fetchReferences) { 089 if (UUID_FIELD.equals(getIdField())) { 090 IdRef ref = new IdRef(id); 091 if (coreSession.exists(ref)) { 092 DocumentModel document = coreSession.getDocument(new IdRef(id)); 093 return docType.equals(document.getType()) ? document : null; 094 } else { 095 return null; 096 } 097 } 098 099 StringBuilder sbQuery = new StringBuilder("SELECT * FROM "); 100 sbQuery.append(docType); 101 sbQuery.append(" WHERE "); 102 sbQuery.append(getDirectory().getField(schemaIdField).getName().getPrefixedName()); 103 sbQuery.append(" = '"); 104 sbQuery.append(id); 105 sbQuery.append("' AND ecm:path STARTSWITH '"); 106 sbQuery.append(createPath); 107 sbQuery.append("'"); 108 109 DocumentModelList listDoc = coreSession.query(sbQuery.toString()); 110 // TODO : deal with references 111 if (!listDoc.isEmpty()) { 112 // Should have only one 113 if (listDoc.size() > 1) { 114 log.warn(String.format( 115 "Found more than one result in getEntry, the first result only will be returned")); 116 } 117 DocumentModel docResult = listDoc.get(0); 118 if (isReadOnly()) { 119 BaseSession.setReadOnlyEntry(docResult); 120 } 121 return docResult; 122 } 123 return null; 124 } 125 126 @Override 127 public DocumentModelList getEntries() { 128 throw new UnsupportedOperationException(); 129 } 130 131 private String getPrefixedFieldName(String fieldName) { 132 if (UUID_FIELD.equals(fieldName)) { 133 return fieldName; 134 } 135 Field schemaField = getDirectory().getField(fieldName); 136 return schemaField.getName().getPrefixedName(); 137 } 138 139 @Override 140 public DocumentModel createEntryWithoutReferences(Map<String, Object> fieldMap) { 141 // TODO once references are implemented 142 throw new UnsupportedOperationException(); 143 } 144 145 @Override 146 protected List<String> updateEntryWithoutReferences(DocumentModel docModel) { 147 // TODO once references are implemented 148 throw new UnsupportedOperationException(); 149 } 150 151 @Override 152 protected void deleteEntryWithoutReferences(String id) { 153 // TODO once references are implemented 154 throw new UnsupportedOperationException(); 155 } 156 157 @Override 158 public DocumentModel createEntry(Map<String, Object> fieldMap) { 159 if (isReadOnly()) { 160 log.warn(String.format("The directory '%s' is in read-only mode, could not create entry.", 161 directory.getName())); 162 return null; 163 } 164 // TODO : deal with auto-versionning 165 // TODO : deal with encrypted password 166 // TODO : deal with references 167 Map<String, Object> properties = new HashMap<>(); 168 List<String> createdRefs = new LinkedList<>(); 169 for (String fieldId : fieldMap.keySet()) { 170 if (getDirectory().isReference(fieldId)) { 171 createdRefs.add(fieldId); 172 } 173 Object value = fieldMap.get(fieldId); 174 properties.put(getMappedPrefixedFieldName(fieldId), value); 175 } 176 177 String rawid = (String) properties.get(getPrefixedFieldName(schemaIdField)); 178 if (rawid == null && (!UUID_FIELD.equals(getIdField()))) { 179 throw new DirectoryException(String.format("Entry is missing id field '%s'", schemaIdField)); 180 } 181 182 DocumentModel docModel = coreSession.createDocumentModel(createPath, rawid, docType); 183 184 docModel.setProperties(schemaName, properties); 185 DocumentModel createdDoc = coreSession.createDocument(docModel); 186 187 for (String referenceFieldName : createdRefs) { 188 Reference reference = directory.getReference(referenceFieldName); 189 List<String> targetIds = toStringList(createdDoc.getProperty(schemaName, referenceFieldName)); 190 reference.setTargetIdsForSource(docModel.getId(), targetIds); 191 } 192 return docModel; 193 } 194 195 @Override 196 public void updateEntry(DocumentModel docModel) { 197 if (isReadOnly()) { 198 log.warn(String.format("The directory '%s' is in read-only mode, could not update entry.", 199 directory.getName())); 200 } else { 201 202 if (!isReadOnlyEntry(docModel)) { 203 204 String id = (String) docModel.getProperty(schemaName, getIdField()); 205 if (id == null) { 206 throw new DirectoryException( 207 "Can not update entry with a null id for document ref " + docModel.getRef()); 208 } else { 209 if (getEntry(id) == null) { 210 throw new DirectoryException( 211 String.format("Update entry failed : Entry with id '%s' not found !", id)); 212 } else { 213 214 DataModel dataModel = docModel.getDataModel(schemaName); 215 Map<String, Object> updatedProps = new HashMap<String, Object>(); 216 List<String> updatedRefs = new LinkedList<String>(); 217 218 for (String field : docModel.getProperties(schemaName).keySet()) { 219 String schemaField = getMappedPrefixedFieldName(field); 220 if (!dataModel.isDirty(schemaField)) { 221 if (getDirectory().isReference(field)) { 222 updatedRefs.add(field); 223 } else { 224 updatedProps.put(schemaField, docModel.getProperties(schemaName).get(field)); 225 } 226 } 227 228 } 229 230 docModel.setProperties(schemaName, updatedProps); 231 232 // update reference fields 233 for (String referenceFieldName : updatedRefs) { 234 Reference reference = directory.getReference(referenceFieldName); 235 List<String> targetIds = toStringList(docModel.getProperty(schemaName, 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) { 249 String id = (String) docModel.getProperty(schemaName, schemaIdField); 250 deleteEntry(id); 251 } 252 253 @Override 254 public void deleteEntry(String id) { 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) { 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) { 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 DocumentModelList query(QueryBuilder queryBuilder, boolean fetchReferences) { 369 throw new UnsupportedOperationException(); 370 } 371 372 @Override 373 public List<String> queryIds(QueryBuilder queryBuilder) { 374 throw new UnsupportedOperationException(); 375 } 376 377 @Override 378 public void close() { 379 ((CloseableCoreSession) coreSession).close(); 380 getDirectory().removeSession(this); 381 } 382 383 @Override 384 public List<String> getProjection(Map<String, Serializable> filter, String columnName) { 385 throw new UnsupportedOperationException(); 386 } 387 388 @Override 389 public List<String> getProjection(Map<String, Serializable> filter, Set<String> fulltext, String columnName) { 390 throw new UnsupportedOperationException(); 391 } 392 393 @Override 394 public boolean authenticate(String username, String password) { 395 DocumentModel entry = getEntry(username); 396 if (entry == null) { 397 return false; 398 } 399 String storedPassword = (String) entry.getProperty(schemaName, schemaPasswordField); 400 return PasswordHelper.verifyPassword(password, storedPassword); 401 } 402 403 @Override 404 public boolean isAuthenticating() { 405 return schemaPasswordField != null; 406 } 407 408 @Override 409 public boolean hasEntry(String id) { 410 return getEntry(id) != null; 411 } 412}