/* * (C) Copyright 2014-2016 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Maxime Hilaire * Florent Guillaume */ package org.nuxeo.ecm.directory.core; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.CoreInstance; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DataModel; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.IdRef; import org.nuxeo.ecm.core.schema.types.Field; import org.nuxeo.ecm.directory.BaseSession; import org.nuxeo.ecm.directory.DirectoryException; import org.nuxeo.ecm.directory.PasswordHelper; import org.nuxeo.ecm.directory.Reference; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Collections2; /** * Session class for directory on repository * * @since 8.2 */ public class CoreDirectorySession extends BaseSession { protected final String schemaName; protected final String schemaIdField; protected final String schemaPasswordField; protected final CoreSession coreSession; protected final String createPath; protected final String docType; protected static final String UUID_FIELD = "ecm:uuid"; private final static Log log = LogFactory.getLog(CoreDirectorySession.class); public CoreDirectorySession(CoreDirectory directory) { super(directory); schemaName = directory.getSchema(); CoreDirectoryDescriptor descriptor = directory.getDescriptor(); coreSession = CoreInstance.openCoreSession(descriptor.getRepositoryName()); schemaIdField = directory.getFieldMapper().getBackendField(getIdField()); schemaPasswordField = directory.getFieldMapper().getBackendField(getPasswordField()); docType = descriptor.docType; createPath = descriptor.getCreatePath(); } @Override public CoreDirectory getDirectory() { return (CoreDirectory) directory; } @Override public DocumentModel getEntry(String id) throws DirectoryException { return getEntry(id, false); } @Override public DocumentModel getEntry(String id, boolean fetchReferences) throws DirectoryException { if (UUID_FIELD.equals(getIdField())) { IdRef ref = new IdRef(id); if (coreSession.exists(ref)) { DocumentModel document = coreSession.getDocument(new IdRef(id)); return docType.equals(document.getType()) ? document : null; } else { return null; } } StringBuilder sbQuery = new StringBuilder("SELECT * FROM "); sbQuery.append(docType); sbQuery.append(" WHERE "); sbQuery.append(getDirectory().getField(schemaIdField).getName().getPrefixedName()); sbQuery.append(" = '"); sbQuery.append(id); sbQuery.append("' AND ecm:path STARTSWITH '"); sbQuery.append(createPath); sbQuery.append("'"); DocumentModelList listDoc = coreSession.query(sbQuery.toString()); // TODO : deal with references if (!listDoc.isEmpty()) { // Should have only one if (listDoc.size() > 1) { log.warn(String.format( "Found more than one result in getEntry, the first result only will be returned")); } DocumentModel docResult = listDoc.get(0); if (isReadOnly()) { BaseSession.setReadOnlyEntry(docResult); } return docResult; } return null; } @Override public DocumentModelList getEntries() throws DirectoryException { throw new UnsupportedOperationException(); } private String getPrefixedFieldName(String fieldName) { if (UUID_FIELD.equals(fieldName)) { return fieldName; } Field schemaField = getDirectory().getField(fieldName); return schemaField.getName().getPrefixedName(); } @Override public DocumentModel createEntry(Map<String, Object> fieldMap) throws DirectoryException { if (isReadOnly()) { log.warn(String.format("The directory '%s' is in read-only mode, could not create entry.", directory.getName())); return null; } // TODO : deal with auto-versionning // TODO : deal with encrypted password // TODO : deal with references Map<String, Object> properties = new HashMap<String, Object>(); List<String> createdRefs = new LinkedList<String>(); for (String fieldId : fieldMap.keySet()) { if (getDirectory().isReference(fieldId)) { createdRefs.add(fieldId); } Object value = fieldMap.get(fieldId); properties.put(getMappedPrefixedFieldName(fieldId), value); } String rawid = (String) properties.get(getPrefixedFieldName(schemaIdField)); if (rawid == null && (!UUID_FIELD.equals(getIdField()))) { throw new DirectoryException(String.format("Entry is missing id field '%s'", schemaIdField)); } DocumentModel docModel = coreSession.createDocumentModel(createPath, rawid, docType); docModel.setProperties(schemaName, properties); DocumentModel createdDoc = coreSession.createDocument(docModel); for (String referenceFieldName : createdRefs) { Reference reference = directory.getReference(referenceFieldName); List<String> targetIds = (List<String>) createdDoc.getProperty(schemaName, referenceFieldName); reference.setTargetIdsForSource(docModel.getId(), targetIds); } return docModel; } @Override public void updateEntry(DocumentModel docModel) throws DirectoryException { if (isReadOnly()) { log.warn(String.format("The directory '%s' is in read-only mode, could not update entry.", directory.getName())); } else { if (!isReadOnlyEntry(docModel)) { String id = (String) docModel.getProperty(schemaName, getIdField()); if (id == null) { throw new DirectoryException( "Can not update entry with a null id for document ref " + docModel.getRef()); } else { if (getEntry(id) == null) { throw new DirectoryException( String.format("Update entry failed : Entry with id '%s' not found !", id)); } else { DataModel dataModel = docModel.getDataModel(schemaName); Map<String, Object> updatedProps = new HashMap<String, Object>(); List<String> updatedRefs = new LinkedList<String>(); for (String field : docModel.getProperties(schemaName).keySet()) { String schemaField = getMappedPrefixedFieldName(field); if (!dataModel.isDirty(schemaField)) { if (getDirectory().isReference(field)) { updatedRefs.add(field); } else { updatedProps.put(schemaField, docModel.getProperties(schemaName).get(field)); } } } docModel.setProperties(schemaName, updatedProps); // update reference fields for (String referenceFieldName : updatedRefs) { Reference reference = directory.getReference(referenceFieldName); List<String> targetIds = (List<String>) docModel.getProperty(schemaName, referenceFieldName); reference.setTargetIdsForSource(docModel.getId(), targetIds); } coreSession.saveDocument(docModel); } } } } } @Override public void deleteEntry(DocumentModel docModel) throws DirectoryException { String id = (String) docModel.getProperty(schemaName, schemaIdField); deleteEntry(id); } @Override public void deleteEntry(String id) throws DirectoryException { if (isReadOnly()) { log.warn(String.format("The directory '%s' is in read-only mode, could not delete entry.", directory.getName())); } else { if (id == null) { throw new DirectoryException("Can not update entry with a null id "); } else { checkDeleteConstraints(id); DocumentModel docModel = getEntry(id); if (docModel != null) { coreSession.removeDocument(docModel.getRef()); } } } } @Override public void deleteEntry(String id, Map<String, String> map) throws DirectoryException { if (isReadOnly()) { log.warn(String.format("The directory '%s' is in read-only mode, could not delete entry.", directory.getName())); } Map<String, Serializable> props = new HashMap<String, Serializable>(map); props.put(schemaIdField, id); DocumentModelList docList = query(props); if (!docList.isEmpty()) { if (docList.size() > 1) { log.warn( String.format("Found more than one result in getEntry, the first result only will be deleted")); } deleteEntry(docList.get(0)); } else { throw new DirectoryException(String.format("Delete entry failed : Entry with id '%s' not found !", id)); } } @Override public DocumentModelList query(Map<String, Serializable> filter) { Set<String> emptySet = Collections.emptySet(); return query(filter, emptySet); } @Override public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy) { // XXX not fetch references by default: breaks current behavior return query(filter, fulltext, orderBy, false); } @Override public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy, boolean fetchReferences) { return query(filter, fulltext, orderBy, fetchReferences, 0, 0); } protected String getMappedPrefixedFieldName(String fieldName) { String backendFieldId = getDirectory().getFieldMapper().getBackendField(fieldName); return getPrefixedFieldName(backendFieldId); } @Override public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy, boolean fetchReferences, int limit, int offset) throws DirectoryException { StringBuilder sbQuery = new StringBuilder("SELECT * FROM "); sbQuery.append(docType); // TODO deal with fetch ref if (!filter.isEmpty() || !fulltext.isEmpty() || (createPath != null && !createPath.isEmpty())) { sbQuery.append(" WHERE "); } int i = 1; boolean hasFilter = false; for (String filterKey : filter.keySet()) { if (!fulltext.contains(filterKey)) { sbQuery.append(getMappedPrefixedFieldName(filterKey)); sbQuery.append(" = "); sbQuery.append("'"); sbQuery.append(filter.get(filterKey)); sbQuery.append("'"); if (i < filter.size()) { sbQuery.append(" AND "); i++; } hasFilter = true; } } if (hasFilter && filter.size() > 0 && fulltext.size() > 0) { sbQuery.append(" AND "); } if (fulltext.size() > 0) { Collection<String> fullTextValues = Collections2.transform(fulltext, new Function<String, String>() { @Override public String apply(String key) { return (String) filter.get(key); } }); sbQuery.append("ecm:fulltext"); sbQuery.append(" = "); sbQuery.append("'"); sbQuery.append(Joiner.on(" ").join(fullTextValues)); sbQuery.append("'"); } if ((createPath != null && !createPath.isEmpty())) { if (filter.size() > 0 || fulltext.size() > 0) { sbQuery.append(" AND "); } sbQuery.append(" ecm:path STARTSWITH '"); sbQuery.append(createPath); sbQuery.append("'"); } // Filter facetFilter = new FacetFilter(FacetNames.VERSIONABLE, true); DocumentModelList resultsDoc = coreSession.query(sbQuery.toString(), null, limit, offset, false); if (isReadOnly()) { for (DocumentModel documentModel : resultsDoc) { BaseSession.setReadOnlyEntry(documentModel); } } return resultsDoc; } @Override public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext) throws DirectoryException { return query(filter, fulltext, new HashMap<String, String>()); } @Override public void close() throws DirectoryException { coreSession.close(); getDirectory().removeSession(this); } @Override public List<String> getProjection(Map<String, Serializable> filter, String columnName) throws DirectoryException { // TODO Auto-generated method stub // return null; throw new UnsupportedOperationException(); } @Override public List<String> getProjection(Map<String, Serializable> filter, Set<String> fulltext, String columnName) throws DirectoryException { // TODO Auto-generated method stub // return null; throw new UnsupportedOperationException(); } @Override public boolean authenticate(String username, String password) { DocumentModel entry = getEntry(username); if (entry == null) { return false; } String storedPassword = (String) entry.getProperty(schemaName, schemaPasswordField); return PasswordHelper.verifyPassword(password, storedPassword); } @Override public boolean isAuthenticating() { return schemaPasswordField != null; } @Override public boolean hasEntry(String id) { return getEntry(id) != null; } @Override public DocumentModel createEntry(DocumentModel entry) { Map<String, Object> fieldMap = entry.getProperties(schemaName); return createEntry(fieldMap); } }