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        checkDeleteConstraints(id);
172        data.remove(id);
173    }
174
175    // given our storage model this doesn't even make sense, as id field is
176    // unique
177    @Override
178    public void deleteEntry(String id, Map<String, String> map) throws DirectoryException {
179        throw new DirectoryException("Not implemented");
180    }
181
182    @Override
183    public void deleteEntry(DocumentModel docModel) throws DirectoryException {
184        deleteEntry(docModel.getId());
185    }
186
187    @Override
188    public DocumentModelList query(Map<String, Serializable> filter) throws DirectoryException {
189        return query(filter, Collections.<String> emptySet());
190    }
191
192    @Override
193    public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext) throws DirectoryException {
194        return query(filter, fulltext, Collections.<String, String> emptyMap());
195    }
196
197    @Override
198    public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy)
199            throws DirectoryException {
200        return query(filter, fulltext, orderBy, true);
201    }
202
203    @Override
204    public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy,
205            boolean fetchReferences) throws DirectoryException {
206        DocumentModelList results = new DocumentModelListImpl();
207        // canonicalize filter
208        Map<String, Object> filt = new HashMap<String, Object>();
209        for (Entry<String, Serializable> e : filter.entrySet()) {
210            String fieldName = e.getKey();
211            if (!getDirectory().schemaSet.contains(fieldName)) {
212                continue;
213            }
214            filt.put(fieldName, e.getValue());
215        }
216        // do the search
217        data_loop: for (Entry<String, Map<String, Object>> datae : data.entrySet()) {
218            String id = datae.getKey();
219            Map<String, Object> map = datae.getValue();
220            for (Entry<String, Object> e : filt.entrySet()) {
221                String fieldName = e.getKey();
222                Object expected = e.getValue();
223                Object value = map.get(fieldName);
224                if (value == null) {
225                    if (expected != null) {
226                        continue data_loop;
227                    }
228                } else {
229                    if (fulltext != null && fulltext.contains(fieldName)) {
230                        if (!value.toString().toLowerCase().startsWith(expected.toString().toLowerCase())) {
231                            continue data_loop;
232                        }
233                    } else {
234                        if (!value.equals(expected)) {
235                            continue data_loop;
236                        }
237                    }
238                }
239            }
240            // this entry matches
241            results.add(getEntry(id));
242        }
243        // order entries
244        if (orderBy != null && !orderBy.isEmpty()) {
245            getDirectory().orderEntries(results, orderBy);
246        }
247        return results;
248    }
249
250    @Override
251    public List<String> getProjection(Map<String, Serializable> filter, String columnName) throws DirectoryException {
252        return getProjection(filter, Collections.<String> emptySet(), columnName);
253    }
254
255    @Override
256    public List<String> getProjection(Map<String, Serializable> filter, Set<String> fulltext, String columnName)
257            throws DirectoryException {
258        DocumentModelList l = query(filter, fulltext);
259        List<String> results = new ArrayList<String>(l.size());
260        for (DocumentModel doc : l) {
261            Object value;
262            try {
263                value = doc.getProperty(directory.getSchema(), columnName);
264            } catch (PropertyException e) {
265                throw new DirectoryException(e);
266            }
267            if (value != null) {
268                results.add(value.toString());
269            } else {
270                results.add(null);
271            }
272        }
273        return results;
274    }
275
276    @Override
277    public DocumentModel createEntry(DocumentModel entry) {
278        Map<String, Object> fieldMap = entry.getProperties(directory.getSchema());
279        return createEntry(fieldMap);
280    }
281
282    @Override
283    public boolean hasEntry(String id) {
284        return data.containsKey(id);
285    }
286
287}