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