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.HashMap; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.nuxeo.ecm.core.api.CoreInstance; 033import org.nuxeo.ecm.core.api.CoreSession; 034import org.nuxeo.ecm.core.api.DataModel; 035import org.nuxeo.ecm.core.api.DocumentModel; 036import org.nuxeo.ecm.core.api.DocumentModelList; 037import org.nuxeo.ecm.core.api.IdRef; 038import org.nuxeo.ecm.core.query.sql.model.QueryBuilder; 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.getCoreSession(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) { 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() { 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) { 139 // TODO once references are implemented 140 throw new UnsupportedOperationException(); 141 } 142 143 @Override 144 protected List<String> updateEntryWithoutReferences(DocumentModel docModel) { 145 // TODO once references are implemented 146 throw new UnsupportedOperationException(); 147 } 148 149 @Override 150 protected void deleteEntryWithoutReferences(String id) { 151 // TODO once references are implemented 152 throw new UnsupportedOperationException(); 153 } 154 155 @Override 156 public DocumentModel createEntry(Map<String, Object> fieldMap) { 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 = toStringList(createdDoc.getProperty(schemaName, referenceFieldName)); 188 reference.setTargetIdsForSource(docModel.getId(), targetIds); 189 } 190 return docModel; 191 } 192 193 @Override 194 public void updateEntry(DocumentModel docModel) { 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<>(); 214 List<String> updatedRefs = new LinkedList<>(); 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 = toStringList(docModel.getProperty(schemaName, referenceFieldName)); 234 reference.setTargetIdsForSource(docModel.getId(), targetIds); 235 } 236 237 coreSession.saveDocument(docModel); 238 } 239 240 } 241 } 242 } 243 } 244 245 @Override 246 public void deleteEntry(DocumentModel docModel) { 247 String id = (String) docModel.getProperty(schemaName, schemaIdField); 248 deleteEntry(id); 249 } 250 251 @Override 252 public void deleteEntry(String id) { 253 if (isReadOnly()) { 254 log.warn(String.format("The directory '%s' is in read-only mode, could not delete entry.", 255 directory.getName())); 256 } else { 257 if (id == null) { 258 throw new DirectoryException("Can not update entry with a null id "); 259 } else { 260 checkDeleteConstraints(id); 261 DocumentModel docModel = getEntry(id); 262 if (docModel != null) { 263 coreSession.removeDocument(docModel.getRef()); 264 } 265 } 266 } 267 } 268 269 @Override 270 public void deleteEntry(String id, Map<String, String> map) { 271 if (isReadOnly()) { 272 log.warn(String.format("The directory '%s' is in read-only mode, could not delete entry.", 273 directory.getName())); 274 } 275 276 Map<String, Serializable> props = new HashMap<>(map); 277 props.put(schemaIdField, id); 278 279 DocumentModelList docList = query(props); 280 if (!docList.isEmpty()) { 281 if (docList.size() > 1) { 282 log.warn( 283 String.format("Found more than one result in getEntry, the first result only will be deleted")); 284 } 285 deleteEntry(docList.get(0)); 286 } else { 287 throw new DirectoryException(String.format("Delete entry failed : Entry with id '%s' not found !", id)); 288 } 289 290 } 291 292 protected String getMappedPrefixedFieldName(String fieldName) { 293 String backendFieldId = getDirectory().getFieldMapper().getBackendField(fieldName); 294 return getPrefixedFieldName(backendFieldId); 295 } 296 297 @Override 298 public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy, 299 boolean fetchReferences, int limit, int offset) { 300 StringBuilder sbQuery = new StringBuilder("SELECT * FROM "); 301 sbQuery.append(docType); 302 // TODO deal with fetch ref 303 if (!filter.isEmpty() || !fulltext.isEmpty() || (createPath != null && !createPath.isEmpty())) { 304 sbQuery.append(" WHERE "); 305 } 306 int i = 1; 307 boolean hasFilter = false; 308 for (String filterKey : filter.keySet()) { 309 if (!fulltext.contains(filterKey)) { 310 sbQuery.append(getMappedPrefixedFieldName(filterKey)); 311 sbQuery.append(" = "); 312 sbQuery.append("'"); 313 sbQuery.append(filter.get(filterKey)); 314 sbQuery.append("'"); 315 if (i < filter.size()) { 316 sbQuery.append(" AND "); 317 i++; 318 } 319 hasFilter = true; 320 } 321 322 } 323 if (hasFilter && filter.size() > 0 && fulltext.size() > 0) { 324 sbQuery.append(" AND "); 325 } 326 if (fulltext.size() > 0) { 327 328 Collection<String> fullTextValues = Collections2.transform(fulltext, new Function<String, String>() { 329 330 @Override 331 public String apply(String key) { 332 return (String) filter.get(key); 333 } 334 335 }); 336 sbQuery.append("ecm:fulltext"); 337 sbQuery.append(" = "); 338 sbQuery.append("'"); 339 sbQuery.append(Joiner.on(" ").join(fullTextValues)); 340 sbQuery.append("'"); 341 } 342 343 if ((createPath != null && !createPath.isEmpty())) { 344 if (filter.size() > 0 || fulltext.size() > 0) { 345 sbQuery.append(" AND "); 346 } 347 sbQuery.append(" ecm:path STARTSWITH '"); 348 sbQuery.append(createPath); 349 sbQuery.append("'"); 350 } 351 352 // Filter facetFilter = new FacetFilter(FacetNames.VERSIONABLE, true); 353 354 DocumentModelList resultsDoc = coreSession.query(sbQuery.toString(), null, limit, offset, false); 355 356 if (isReadOnly()) { 357 for (DocumentModel documentModel : resultsDoc) { 358 BaseSession.setReadOnlyEntry(documentModel); 359 } 360 } 361 return resultsDoc; 362 363 } 364 365 @Override 366 public DocumentModelList query(QueryBuilder queryBuilder, boolean fetchReferences) { 367 throw new UnsupportedOperationException(); 368 } 369 370 @Override 371 public List<String> queryIds(QueryBuilder queryBuilder) { 372 throw new UnsupportedOperationException(); 373 } 374 375 @Override 376 public void close() { 377 getDirectory().removeSession(this); 378 } 379 380 @Override 381 public List<String> getProjection(Map<String, Serializable> filter, String columnName) { 382 throw new UnsupportedOperationException(); 383 } 384 385 @Override 386 public List<String> getProjection(Map<String, Serializable> filter, Set<String> fulltext, String columnName) { 387 throw new UnsupportedOperationException(); 388 } 389 390 @Override 391 public boolean authenticate(String username, String password) { 392 DocumentModel entry = getEntry(username); 393 if (entry == null) { 394 return false; 395 } 396 String storedPassword = (String) entry.getProperty(schemaName, schemaPasswordField); 397 return PasswordHelper.verifyPassword(password, storedPassword); 398 } 399 400 @Override 401 public boolean isAuthenticating() { 402 return schemaPasswordField != null; 403 } 404 405 @Override 406 public boolean hasEntry(String id) { 407 return getEntry(id) != null; 408 } 409}