/*
* (C) Copyright 2006-2007 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:
* Florent Guillaume
*
* $Id: MultiDirectorySession.java 29556 2008-01-23 00:59:39Z jcarsique $
*/
package org.nuxeo.ecm.directory.multi;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelList;
import org.nuxeo.ecm.core.api.PropertyException;
import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
import org.nuxeo.ecm.core.api.security.SecurityConstants;
import org.nuxeo.ecm.core.schema.SchemaManager;
import org.nuxeo.ecm.core.schema.types.Field;
import org.nuxeo.ecm.core.schema.types.Schema;
import org.nuxeo.ecm.directory.BaseSession;
import org.nuxeo.ecm.directory.DirectoryException;
import org.nuxeo.ecm.directory.Session;
import org.nuxeo.ecm.directory.api.DirectoryService;
import org.nuxeo.runtime.api.Framework;
/**
* Directory session aggregating entries from different sources.
* <p>
* Each source can build an entry aggregating fields from one or several directories.
*
* @author Florent Guillaume
* @author Anahide Tchertchian
*/
public class MultiDirectorySession extends BaseSession {
private static final Log log = LogFactory.getLog(MultiDirectorySession.class);
private final DirectoryService directoryService;
private final String schemaName;
private final String schemaIdField;
private List<SourceInfo> sourceInfos;
public MultiDirectorySession(MultiDirectory directory) {
super(directory);
directoryService = Framework.getService(DirectoryService.class);
MultiDirectoryDescriptor descriptor = directory.getDescriptor();
schemaName = descriptor.schemaName;
schemaIdField = descriptor.idField;
permissions = descriptor.permissions;
}
@Override
public MultiDirectory getDirectory() {
return (MultiDirectory) directory;
}
protected class SubDirectoryInfo {
final String dirName;
final String dirSchemaName;
final String idField;
final boolean isAuthenticating;
final Map<String, String> fromSource;
final Map<String, String> toSource;
final Map<String, Serializable> defaultEntry;
final boolean isOptional;
Session session;
SubDirectoryInfo(String dirName, String dirSchemaName, String idField, boolean isAuthenticating,
Map<String, String> fromSource, Map<String, String> toSource, Map<String, Serializable> defaultEntry,
boolean isOptional) {
this.dirName = dirName;
this.dirSchemaName = dirSchemaName;
this.idField = idField;
this.isAuthenticating = isAuthenticating;
this.fromSource = fromSource;
this.toSource = toSource;
this.defaultEntry = defaultEntry;
this.isOptional = isOptional;
}
Session getSession() throws DirectoryException {
if (session == null) {
session = directoryService.open(dirName);
}
return session;
}
@Override
public String toString() {
return String.format("{directory=%s fromSource=%s toSource=%s}", dirName, fromSource, toSource);
}
}
protected static class SourceInfo {
final SourceDescriptor source;
final List<SubDirectoryInfo> subDirectoryInfos;
final List<SubDirectoryInfo> requiredSubDirectoryInfos;
final List<SubDirectoryInfo> optionalSubDirectoryInfos;
final SubDirectoryInfo authDirectoryInfo;
SourceInfo(SourceDescriptor source, List<SubDirectoryInfo> subDirectoryInfos, SubDirectoryInfo authDirectoryInfo) {
this.source = source;
this.subDirectoryInfos = subDirectoryInfos;
requiredSubDirectoryInfos = new ArrayList<SubDirectoryInfo>();
optionalSubDirectoryInfos = new ArrayList<SubDirectoryInfo>();
for (SubDirectoryInfo subDirInfo : subDirectoryInfos) {
if (subDirInfo.isOptional) {
optionalSubDirectoryInfos.add(subDirInfo);
} else {
requiredSubDirectoryInfos.add(subDirInfo);
}
}
this.authDirectoryInfo = authDirectoryInfo;
}
@Override
public String toString() {
return String.format("{source=%s infos=%s}", source.name, subDirectoryInfos);
}
}
private void init() throws DirectoryException {
if (sourceInfos == null) {
recomputeSourceInfos();
}
}
/**
* Recomputes all the info needed for efficient access.
*/
private void recomputeSourceInfos() throws DirectoryException {
SchemaManager schemaManager = Framework.getService(SchemaManager.class);
final Schema schema = schemaManager.getSchema(schemaName);
if (schema == null) {
throw new DirectoryException(String.format("Directory '%s' has unknown schema '%s'", getName(),
schemaName));
}
final Set<String> sourceFields = new HashSet<String>();
for (Field f : schema.getFields()) {
sourceFields.add(f.getName().getLocalName());
}
if (!sourceFields.contains(schemaIdField)) {
throw new DirectoryException(String.format("Directory '%s' schema '%s' has no id field '%s'",
getName(), schemaName, schemaIdField));
}
List<SourceInfo> newSourceInfos = new ArrayList<SourceInfo>(2);
for (SourceDescriptor source : getDirectory().getDescriptor().sources) {
int ndirs = source.subDirectories.length;
if (ndirs == 0) {
throw new DirectoryException(String.format("Directory '%s' source '%s' has no subdirectories",
getName(), source.name));
}
final List<SubDirectoryInfo> subDirectoryInfos = new ArrayList<SubDirectoryInfo>(ndirs);
SubDirectoryInfo authDirectoryInfo = null;
boolean hasRequiredDir = false;
for (SubDirectoryDescriptor subDir : source.subDirectories) {
final String dirName = subDir.name;
final String dirSchemaName = directoryService.getDirectorySchema(dirName);
final String dirIdField = directoryService.getDirectoryIdField(dirName);
final boolean dirIsAuth = directoryService.getDirectoryPasswordField(dirName) != null;
final Map<String, String> fromSource = new HashMap<String, String>();
final Map<String, String> toSource = new HashMap<String, String>();
final Map<String, Serializable> defaultEntry = new HashMap<String, Serializable>();
final boolean dirIsOptional = subDir.isOptional;
// XXX check authenticating
final Schema dirSchema = schemaManager.getSchema(dirSchemaName);
if (dirSchema == null) {
throw new DirectoryException(String.format("Directory '%s' source '%s' subdirectory '%s' "
+ "has unknown schema '%s'", getName(), source.name, dirName, dirSchemaName));
}
// record default field mappings if same name and record default
// values
final Set<String> dirSchemaFields = new HashSet<String>();
for (Field f : dirSchema.getFields()) {
final String fieldName = f.getName().getLocalName();
dirSchemaFields.add(fieldName);
if (sourceFields.contains(fieldName)) {
// XXX check no duplicates!
fromSource.put(fieldName, fieldName);
toSource.put(fieldName, fieldName);
}
// XXX cast to Serializable
defaultEntry.put(fieldName, (Serializable) f.getDefaultValue());
}
// treat renamings
// XXX id field ?
for (FieldDescriptor field : subDir.fields) {
final String sourceFieldName = field.forField;
final String fieldName = field.name;
if (!sourceFields.contains(sourceFieldName)) {
throw new DirectoryException(String.format("Directory '%s' source '%s' subdirectory '%s' "
+ "has mapping for unknown field '%s'", getName(), source.name, dirName,
sourceFieldName));
}
if (!dirSchemaFields.contains(fieldName)) {
throw new DirectoryException(String.format("Directory '%s' source '%s' subdirectory '%s' "
+ "has mapping of unknown field' '%s'", getName(), source.name, dirName,
fieldName));
}
fromSource.put(sourceFieldName, fieldName);
toSource.put(fieldName, sourceFieldName);
}
SubDirectoryInfo subDirectoryInfo = new SubDirectoryInfo(dirName, dirSchemaName, dirIdField, dirIsAuth,
fromSource, toSource, defaultEntry, dirIsOptional);
subDirectoryInfos.add(subDirectoryInfo);
if (dirIsAuth) {
if (authDirectoryInfo != null) {
throw new DirectoryException(String.format("Directory '%s' source '%s' has two subdirectories "
+ "with a password field, '%s' and '%s'", getName(), source.name,
authDirectoryInfo.dirName, dirName));
}
authDirectoryInfo = subDirectoryInfo;
}
if (!dirIsOptional) {
hasRequiredDir = true;
}
}
if (isAuthenticating() && authDirectoryInfo == null) {
throw new DirectoryException(String.format("Directory '%s' source '%s' has no subdirectory "
+ "with a password field", getName(), source.name));
}
if (!hasRequiredDir) {
throw new DirectoryException(String.format(
"Directory '%s' source '%s' only has optional subdirectories: "
+ "no directory can be used has a reference.", getName(), source.name));
}
newSourceInfos.add(new SourceInfo(source, subDirectoryInfos, authDirectoryInfo));
}
sourceInfos = newSourceInfos;
}
@Override
public void close() throws DirectoryException {
try {
if (sourceInfos == null) {
return;
}
DirectoryException exc = null;
for (SourceInfo sourceInfo : sourceInfos) {
for (SubDirectoryInfo subDirectoryInfo : sourceInfo.subDirectoryInfos) {
Session session = subDirectoryInfo.session;
subDirectoryInfo.session = null;
if (session != null) {
try {
session.close();
} catch (DirectoryException e) {
// remember exception, we want to close all session
// first
if (exc == null) {
exc = e;
} else {
// we can't reraise both, log this one
log.error("Error closing directory " + subDirectoryInfo.dirName, e);
}
}
}
}
if (exc != null) {
throw exc;
}
}
} finally {
getDirectory().removeSession(this);
}
}
public String getName() {
return directory.getName();
}
@Override
public boolean authenticate(String username, String password) {
init();
for (SourceInfo sourceInfo : sourceInfos) {
for (SubDirectoryInfo dirInfo : sourceInfo.subDirectoryInfos) {
if (!dirInfo.isAuthenticating) {
continue;
}
if (dirInfo.getSession().authenticate(username, password)) {
return true;
}
if (dirInfo.isOptional && dirInfo.getSession().getEntry(username) == null) {
// check if given password equals to default value
String passwordField = dirInfo.getSession().getPasswordField();
String defaultPassword = (String) dirInfo.defaultEntry.get(passwordField);
if (defaultPassword != null && defaultPassword.equals(password)) {
return true;
}
}
}
}
return false;
}
@Override
public DocumentModel getEntry(String id) throws DirectoryException {
return getEntry(id, true);
}
@Override
public DocumentModel getEntry(String id, boolean fetchReferences) throws DirectoryException {
if (!hasPermission(SecurityConstants.READ)) {
return null;
}
init();
String entryId = id;
source_loop: for (SourceInfo sourceInfo : sourceInfos) {
boolean isReadOnlyEntry = true;
final Map<String, Object> map = new HashMap<String, Object>();
for (SubDirectoryInfo dirInfo : sourceInfo.subDirectoryInfos) {
final DocumentModel entry = dirInfo.getSession().getEntry(id, fetchReferences);
boolean isOptional = dirInfo.isOptional;
if (entry == null && !isOptional) {
// not in this source
continue source_loop;
}
if (entry != null && !isReadOnlyEntry(entry)) {
// set readonly to false if at least one source is writable
isReadOnlyEntry = false;
}
if (entry == null && isOptional && !dirInfo.getSession().isReadOnly()) {
// set readonly to false if null entry is from optional and writable directory
isReadOnlyEntry = false;
}
if (entry != null && StringUtils.isNotBlank(entry.getId())) {
entryId = entry.getId();
}
for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
if (entry != null) {
try {
map.put(e.getValue(), entry.getProperty(dirInfo.dirSchemaName, e.getKey()));
} catch (PropertyException e1) {
throw new DirectoryException(e1);
}
} else {
// fill with default values for this directory
if (!map.containsKey(e.getValue())) {
map.put(e.getValue(), dirInfo.defaultEntry.get(e.getKey()));
}
}
}
}
// force the entry in readonly if it's defined on the multidirectory
if (isReadOnly()) {
isReadOnlyEntry = true;
}
// ok we have the data
try {
return BaseSession.createEntryModel(null, schemaName, entryId, map, isReadOnlyEntry);
} catch (PropertyException e) {
throw new DirectoryException(e);
}
}
return null;
}
@Override
@SuppressWarnings("boxing")
public DocumentModelList getEntries() {
if (!hasPermission(SecurityConstants.READ)) {
return new DocumentModelListImpl();
}
init();
// list of entries
final DocumentModelList results = new DocumentModelListImpl();
// entry ids already seen (mapped to the source name)
final Map<String, String> seen = new HashMap<String, String>();
Set<String> readOnlyEntries = new HashSet<String>();
for (SourceInfo sourceInfo : sourceInfos) {
// accumulated map for each entry
final Map<String, Map<String, Object>> maps = new HashMap<String, Map<String, Object>>();
// number of dirs seen for each entry
final Map<String, Integer> counts = new HashMap<String, Integer>();
for (SubDirectoryInfo dirInfo : sourceInfo.requiredSubDirectoryInfos) {
final DocumentModelList entries = dirInfo.getSession().getEntries();
for (DocumentModel entry : entries) {
final String id = entry.getId();
// find or create map for this entry
Map<String, Object> map = maps.get(id);
if (map == null) {
map = new HashMap<String, Object>();
maps.put(id, map);
counts.put(id, 1);
} else {
counts.put(id, counts.get(id) + 1);
}
// put entry data in map
for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
map.put(e.getValue(), entry.getProperty(dirInfo.dirSchemaName, e.getKey()));
}
if (BaseSession.isReadOnlyEntry(entry)) {
readOnlyEntries.add(id);
}
}
}
for (SubDirectoryInfo dirInfo : sourceInfo.optionalSubDirectoryInfos) {
final DocumentModelList entries = dirInfo.getSession().getEntries();
Set<String> existingIds = new HashSet<String>();
for (DocumentModel entry : entries) {
final String id = entry.getId();
final Map<String, Object> map = maps.get(id);
if (map != null) {
existingIds.add(id);
// put entry data in map
for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
map.put(e.getValue(), entry.getProperty(dirInfo.dirSchemaName, e.getKey()));
}
} else {
log.warn(String.format("Entry '%s' for source '%s' is present in optional directory '%s' "
+ "but not in any required one. " + "It will be skipped.", id, sourceInfo.source.name,
dirInfo.dirName));
}
}
for (Entry<String, Map<String, Object>> mapEntry : maps.entrySet()) {
if (!existingIds.contains(mapEntry.getKey())) {
final Map<String, Object> map = mapEntry.getValue();
// put entry data in map
for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
// fill with default values for this directory
if (!map.containsKey(e.getValue())) {
map.put(e.getValue(), dirInfo.defaultEntry.get(e.getKey()));
}
}
}
}
}
// now create entries for all full maps
int numdirs = sourceInfo.requiredSubDirectoryInfos.size();
((ArrayList<?>) results).ensureCapacity(results.size() + maps.size());
for (Entry<String, Map<String, Object>> e : maps.entrySet()) {
final String id = e.getKey();
if (seen.containsKey(id)) {
log.warn(String.format("Entry '%s' is present in source '%s' but also in source '%s'. "
+ "The second one will be ignored.", id, seen.get(id), sourceInfo.source.name));
continue;
}
final Map<String, Object> map = e.getValue();
if (counts.get(id) != numdirs) {
log.warn(String.format("Entry '%s' for source '%s' is not present in all directories. "
+ "It will be skipped.", id, sourceInfo.source.name));
continue;
}
seen.put(id, sourceInfo.source.name);
final DocumentModel entry = BaseSession.createEntryModel(null, schemaName, id, map,
readOnlyEntries.contains(id));
results.add(entry);
}
}
return results;
}
@Override
public DocumentModel createEntry(Map<String, Object> fieldMap) {
checkPermission(SecurityConstants.WRITE);
init();
final Object rawid = fieldMap.get(schemaIdField);
if (rawid == null) {
throw new DirectoryException(String.format("Entry is missing id field '%s'", schemaIdField));
}
final String id = String.valueOf(rawid); // XXX allow longs too
for (SourceInfo sourceInfo : sourceInfos) {
if (!sourceInfo.source.creation) {
continue;
}
for (SubDirectoryInfo dirInfo : sourceInfo.subDirectoryInfos) {
Map<String, Object> map = new HashMap<String, Object>();
map.put(dirInfo.idField, id);
for (Entry<String, String> e : dirInfo.fromSource.entrySet()) {
map.put(e.getValue(), fieldMap.get(e.getKey()));
}
dirInfo.getSession().createEntry(map);
}
return getEntry(id);
}
throw new DirectoryException(String.format("Directory '%s' has no source allowing creation",
getName()));
}
@Override
public void deleteEntry(DocumentModel docModel) {
deleteEntry(docModel.getId());
}
@Override
public void deleteEntry(String id) {
checkPermission(SecurityConstants.WRITE);
checkDeleteConstraints(id);
init();
for (SourceInfo sourceInfo : sourceInfos) {
for (SubDirectoryInfo dirInfo : sourceInfo.subDirectoryInfos) {
// Check if the platform is able to manage entry
if (!sourceInfo.source.creation && !dirInfo.getSession().isReadOnly()) {
// If not check if entry exist to prevent exception that may
// stop the deletion loop to other subdirectories
// Do not raise exception, because creation is not managed
// by the platform
DocumentModel docModel = dirInfo.getSession().getEntry(id);
if (docModel == null) {
log.warn(String.format(
"MultiDirectory '%s' : The entry id '%s' could not be deleted on subdirectory '%s' because it does not exist",
getName(), id, dirInfo.dirName));
} else {
dirInfo.getSession().deleteEntry(id);
}
} else {
dirInfo.getSession().deleteEntry(id);
}
}
}
}
@Override
public void deleteEntry(String id, Map<String, String> map) throws DirectoryException {
log.warn("Calling deleteEntry extended on multi directory");
deleteEntry(id);
}
private static void updateSubDirectoryEntry(SubDirectoryInfo dirInfo, Map<String, Object> fieldMap, String id,
boolean canCreateIfOptional) {
DocumentModel dirEntry = dirInfo.getSession().getEntry(id);
if (dirInfo.getSession().isReadOnly() || (dirEntry != null && isReadOnlyEntry(dirEntry))) {
return;
}
if (dirEntry == null && !canCreateIfOptional) {
// entry to update doesn't belong to this directory
return;
}
Map<String, Object> map = new HashMap<String, Object>();
map.put(dirInfo.idField, id);
for (Entry<String, String> e : dirInfo.fromSource.entrySet()) {
map.put(e.getValue(), fieldMap.get(e.getKey()));
}
if (map.size() > 1) {
if (canCreateIfOptional && dirInfo.isOptional && dirEntry == null) {
// if entry does not exist, create it
dirInfo.getSession().createEntry(map);
} else {
final DocumentModel entry = BaseSession.createEntryModel(null, dirInfo.dirSchemaName, id, null);
entry.setProperties(dirInfo.dirSchemaName, map);
dirInfo.getSession().updateEntry(entry);
}
}
}
@Override
public void updateEntry(DocumentModel docModel) {
checkPermission(SecurityConstants.WRITE);
if (isReadOnlyEntry(docModel)) {
return;
}
init();
final String id = docModel.getId();
Map<String, Object> fieldMap = docModel.getProperties(schemaName);
for (SourceInfo sourceInfo : sourceInfos) {
// check if entry exists in this source, in case it can be created
// in optional subdirectories
boolean canCreateIfOptional = false;
for (SubDirectoryInfo dirInfo : sourceInfo.requiredSubDirectoryInfos) {
if (!canCreateIfOptional) {
canCreateIfOptional = dirInfo.getSession().getEntry(id) != null;
}
updateSubDirectoryEntry(dirInfo, fieldMap, id, false);
}
for (SubDirectoryInfo dirInfo : sourceInfo.optionalSubDirectoryInfos) {
updateSubDirectoryEntry(dirInfo, fieldMap, id, canCreateIfOptional);
}
}
}
@Override
public DocumentModelList query(Map<String, Serializable> filter) {
return query(filter, Collections.<String> emptySet());
}
@Override
public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext) {
return query(filter, fulltext, Collections.<String, String> emptyMap());
}
@Override
@SuppressWarnings("boxing")
public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy)
{
return query(filter, fulltext, orderBy, false);
}
@Override
@SuppressWarnings("boxing")
public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy,
boolean fetchReferences) {
if (!hasPermission(SecurityConstants.READ)) {
return new DocumentModelListImpl();
}
init();
// entry ids already seen (mapped to the source name)
final Map<String, String> seen = new HashMap<String, String>();
if (fulltext == null) {
fulltext = Collections.emptySet();
}
Set<String> readOnlyEntries = new HashSet<String>();
DocumentModelList results = new DocumentModelListImpl();
for (SourceInfo sourceInfo : sourceInfos) {
// accumulated map for each entry
final Map<String, Map<String, Object>> maps = new HashMap<String, Map<String, Object>>();
// number of dirs seen for each entry
final Map<String, Integer> counts = new HashMap<String, Integer>();
// list of optional dirs where filter matches default values
List<SubDirectoryInfo> optionalDirsMatching = new ArrayList<SubDirectoryInfo>();
for (SubDirectoryInfo dirInfo : sourceInfo.subDirectoryInfos) {
// compute filter
final Map<String, Serializable> dirFilter = new HashMap<String, Serializable>();
for (Entry<String, Serializable> e : filter.entrySet()) {
final String fieldName = dirInfo.fromSource.get(e.getKey());
if (fieldName == null) {
continue;
}
dirFilter.put(fieldName, e.getValue());
}
if (dirInfo.isOptional) {
// check if filter matches directory default values
boolean matches = true;
for (Map.Entry<String, Serializable> dirFilterEntry : dirFilter.entrySet()) {
Object defaultValue = dirInfo.defaultEntry.get(dirFilterEntry.getKey());
Object filterValue = dirFilterEntry.getValue();
if (defaultValue == null && filterValue != null) {
matches = false;
} else if (defaultValue != null && !defaultValue.equals(filterValue)) {
matches = false;
}
}
if (matches) {
optionalDirsMatching.add(dirInfo);
}
}
// compute fulltext
Set<String> dirFulltext = new HashSet<String>();
for (String sourceFieldName : fulltext) {
final String fieldName = dirInfo.fromSource.get(sourceFieldName);
if (fieldName != null) {
dirFulltext.add(fieldName);
}
}
// make query to subdirectory
DocumentModelList l = dirInfo.getSession().query(dirFilter, dirFulltext, null, fetchReferences);
for (DocumentModel entry : l) {
final String id = entry.getId();
Map<String, Object> map = maps.get(id);
if (map == null) {
map = new HashMap<String, Object>();
maps.put(id, map);
counts.put(id, 1);
} else {
counts.put(id, counts.get(id) + 1);
}
for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
map.put(e.getValue(), entry.getProperty(dirInfo.dirSchemaName, e.getKey()));
}
if (BaseSession.isReadOnlyEntry(entry)) {
readOnlyEntries.add(id);
}
}
}
// add default entry values for optional dirs
for (SubDirectoryInfo dirInfo : optionalDirsMatching) {
// add entry for every data found in other dirs
Set<String> existingIds = new HashSet<String>(dirInfo.getSession().getProjection(
Collections.<String, Serializable> emptyMap(), dirInfo.idField));
for (Entry<String, Map<String, Object>> result : maps.entrySet()) {
final String id = result.getKey();
if (!existingIds.contains(id)) {
counts.put(id, counts.get(id) + 1);
final Map<String, Object> map = result.getValue();
for (Entry<String, String> e : dirInfo.toSource.entrySet()) {
String value = e.getValue();
if (!map.containsKey(value)) {
map.put(value, dirInfo.defaultEntry.get(e.getKey()));
}
}
}
}
}
// intersection, ignore entries not in all subdirectories
final int numdirs = sourceInfo.subDirectoryInfos.size();
for (Iterator<String> it = maps.keySet().iterator(); it.hasNext();) {
final String id = it.next();
if (counts.get(id) != numdirs) {
it.remove();
}
}
// now create entries
((ArrayList<?>) results).ensureCapacity(results.size() + maps.size());
for (Entry<String, Map<String, Object>> e : maps.entrySet()) {
final String id = e.getKey();
if (seen.containsKey(id)) {
log.warn(String.format("Entry '%s' is present in source '%s' but also in source '%s'. "
+ "The second one will be ignored.", id, seen.get(id), sourceInfo.source.name));
continue;
}
final Map<String, Object> map = e.getValue();
seen.put(id, sourceInfo.source.name);
final DocumentModel entry = BaseSession.createEntryModel(null, schemaName, id, map,
readOnlyEntries.contains(id));
results.add(entry);
}
}
if (orderBy != null && !orderBy.isEmpty()) {
getDirectory().orderEntries(results, orderBy);
}
return results;
}
@Override
public List<String> getProjection(Map<String, Serializable> filter, String columnName) {
return getProjection(filter, Collections.<String> emptySet(), columnName);
}
@Override
public List<String> getProjection(Map<String, Serializable> filter, Set<String> fulltext, String columnName)
{
// There's no way to do an efficient getProjection to a source with
// multiple subdirectories given the current API (we'd need an API that
// passes several columns).
// So just do a non-optimal implementation for now.
final DocumentModelList entries = query(filter, fulltext);
final List<String> results = new ArrayList<String>(entries.size());
for (DocumentModel entry : entries) {
final Object value = entry.getProperty(schemaName, columnName);
if (value == null) {
results.add(null);
} else {
results.add(value.toString());
}
}
return results;
}
@Override
public DocumentModel createEntry(DocumentModel entry) {
Map<String, Object> fieldMap = entry.getProperties(schemaName);
return createEntry(fieldMap);
}
@Override
public boolean hasEntry(String id) {
init();
for (SourceInfo sourceInfo : sourceInfos) {
for (SubDirectoryInfo dirInfo : sourceInfo.subDirectoryInfos) {
Session session = dirInfo.getSession();
if (session.hasEntry(id)) {
return true;
}
}
}
return false;
}
}