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