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