001/*
002 * (C) Copyright 2006-2007 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 *     Florent Guillaume
018 *
019 * $Id: MemoryDirectorySession.java 30374 2008-02-20 16:31:28Z gracinet $
020 */
021
022package org.nuxeo.ecm.directory.memory;
023
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032import java.util.Map.Entry;
033
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.PropertyException;
038import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
039import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
040import org.nuxeo.ecm.directory.BaseSession;
041import org.nuxeo.ecm.directory.DirectoryException;
042
043/**
044 * Trivial in-memory implementation of a Directory to use in unit tests.
045 *
046 * @author Florent Guillaume
047 */
048public class MemoryDirectorySession extends BaseSession {
049
050    protected final MemoryDirectory directory;
051
052    protected final Map<String, Map<String, Object>> data;
053
054    public MemoryDirectorySession(MemoryDirectory directory) {
055        this.directory = directory;
056        data = Collections.synchronizedMap(new LinkedHashMap<String, Map<String, Object>>());
057    }
058
059    public boolean authenticate(String username, String password) throws DirectoryException {
060        Map<String, Object> map = data.get(username);
061        if (map == null) {
062            return false;
063        }
064        String expected = (String) map.get(directory.passwordField);
065        if (expected == null) {
066            return false;
067        }
068        return expected.equals(password);
069    }
070
071    public void close() {
072    }
073
074    public void commit() {
075    }
076
077    public void rollback() throws DirectoryException {
078        // TODO Auto-generated method stub
079        throw new RuntimeException("Not implemented");
080    }
081
082    public DocumentModel createEntry(Map<String, Object> fieldMap) throws DirectoryException {
083        if (isReadOnly()) {
084            return null;
085        }
086        // find id
087        Object rawId = fieldMap.get(directory.idField);
088        if (rawId == null) {
089            throw new DirectoryException("Missing id");
090        }
091        String id = String.valueOf(rawId);
092        Map<String, Object> map = data.get(id);
093        if (map != null) {
094            throw new DirectoryException(String.format("Entry with id %s already exists", id));
095        }
096        map = new HashMap<String, Object>();
097        data.put(id, map);
098        // put fields in map
099        for (Entry<String, Object> e : fieldMap.entrySet()) {
100            String fieldName = e.getKey();
101            if (!directory.schemaSet.contains(fieldName)) {
102                continue;
103            }
104            map.put(fieldName, e.getValue());
105        }
106        return getEntry(id);
107    }
108
109    public DocumentModel getEntry(String id) throws DirectoryException {
110        return getEntry(id, true);
111    }
112
113    public DocumentModel getEntry(String id, boolean fetchReferences) throws DirectoryException {
114        // XXX no references here
115        Map<String, Object> map = data.get(id);
116        if (map == null) {
117            return null;
118        }
119        try {
120            DocumentModel entry = BaseSession.createEntryModel(null, directory.schemaName, id, map, isReadOnly());
121            return entry;
122        } catch (PropertyException e) {
123            throw new DirectoryException(e);
124        }
125    }
126
127    public void updateEntry(DocumentModel docModel) throws DirectoryException {
128        String id = docModel.getId();
129        DataModel dataModel = docModel.getDataModel(directory.schemaName);
130
131        Map<String, Object> map = data.get(id);
132        if (map == null) {
133            throw new DirectoryException("UpdateEntry failed: entry '" + id + "' not found");
134        }
135
136        for (String fieldName : directory.schemaSet) {
137            try {
138                if (!dataModel.isDirty(fieldName) || fieldName.equals(directory.idField)) {
139                    continue;
140                }
141            } catch (PropertyNotFoundException e) {
142                continue;
143            }
144            // TODO references
145            map.put(fieldName, dataModel.getData(fieldName));
146        }
147        dataModel.getDirtyFields().clear();
148    }
149
150    public DocumentModelList getEntries() throws DirectoryException {
151        DocumentModelList list = new DocumentModelListImpl();
152        for (String id : data.keySet()) {
153            list.add(getEntry(id));
154        }
155        return list;
156    }
157
158    public void deleteEntry(String id) throws DirectoryException {
159        data.remove(id);
160    }
161
162    // given our storage model this doesn't even make sense, as id field is
163    // unique
164    public void deleteEntry(String id, Map<String, String> map) throws DirectoryException {
165        throw new DirectoryException("Not implemented");
166    }
167
168    public void deleteEntry(DocumentModel docModel) throws DirectoryException {
169        deleteEntry(docModel.getId());
170    }
171
172    public String getIdField() {
173        return directory.idField;
174    }
175
176    public String getPasswordField() {
177        return directory.passwordField;
178    }
179
180    public boolean isAuthenticating() {
181        return directory.passwordField != null;
182    }
183
184    public boolean isReadOnly() {
185        return directory.isReadOnly();
186    }
187
188    public DocumentModelList query(Map<String, Serializable> filter) throws DirectoryException {
189        return query(filter, Collections.<String> emptySet());
190    }
191
192    public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext) throws DirectoryException {
193        return query(filter, fulltext, Collections.<String, String> emptyMap());
194    }
195
196    public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy)
197            throws DirectoryException {
198        return query(filter, fulltext, orderBy, true);
199    }
200
201    public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy,
202            boolean fetchReferences) throws DirectoryException {
203        DocumentModelList results = new DocumentModelListImpl();
204        // canonicalize filter
205        Map<String, Object> filt = new HashMap<String, Object>();
206        for (Entry<String, Serializable> e : filter.entrySet()) {
207            String fieldName = e.getKey();
208            if (!directory.schemaSet.contains(fieldName)) {
209                continue;
210            }
211            filt.put(fieldName, e.getValue());
212        }
213        // do the search
214        data_loop: for (Entry<String, Map<String, Object>> datae : data.entrySet()) {
215            String id = datae.getKey();
216            Map<String, Object> map = datae.getValue();
217            for (Entry<String, Object> e : filt.entrySet()) {
218                String fieldName = e.getKey();
219                Object expected = e.getValue();
220                Object value = map.get(fieldName);
221                if (value == null) {
222                    if (expected != null) {
223                        continue data_loop;
224                    }
225                } else {
226                    if (fulltext != null && fulltext.contains(fieldName)) {
227                        if (!value.toString().toLowerCase().startsWith(expected.toString().toLowerCase())) {
228                            continue data_loop;
229                        }
230                    } else {
231                        if (!value.equals(expected)) {
232                            continue data_loop;
233                        }
234                    }
235                }
236            }
237            // this entry matches
238            results.add(getEntry(id));
239        }
240        // order entries
241        if (orderBy != null && !orderBy.isEmpty()) {
242            directory.orderEntries(results, orderBy);
243        }
244        return results;
245    }
246
247    public List<String> getProjection(Map<String, Serializable> filter, String columnName) throws DirectoryException {
248        return getProjection(filter, Collections.<String> emptySet(), columnName);
249    }
250
251    public List<String> getProjection(Map<String, Serializable> filter, Set<String> fulltext, String columnName)
252            throws DirectoryException {
253        DocumentModelList l = query(filter, fulltext);
254        List<String> results = new ArrayList<String>(l.size());
255        for (DocumentModel doc : l) {
256            Object value;
257            try {
258                value = doc.getProperty(directory.schemaName, columnName);
259            } catch (PropertyException e) {
260                throw new DirectoryException(e);
261            }
262            if (value != null) {
263                results.add(value.toString());
264            } else {
265                results.add(null);
266            }
267        }
268        return results;
269    }
270
271    public DocumentModel createEntry(DocumentModel entry) {
272        Map<String, Object> fieldMap = entry.getProperties(directory.schemaName);
273        return createEntry(fieldMap);
274    }
275
276    public boolean hasEntry(String id) {
277        return data.containsKey(id);
278    }
279
280}