001/* 002 * (C) Copyright 2006-2018 Nuxeo (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.Map.Entry; 032import java.util.Set; 033 034import org.nuxeo.ecm.core.api.DataModel; 035import org.nuxeo.ecm.core.api.DocumentModel; 036import org.nuxeo.ecm.core.api.DocumentModelComparator; 037import org.nuxeo.ecm.core.api.DocumentModelList; 038import org.nuxeo.ecm.core.api.PropertyException; 039import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 040import org.nuxeo.ecm.core.api.model.PropertyNotFoundException; 041import org.nuxeo.ecm.core.api.security.SecurityConstants; 042import org.nuxeo.ecm.core.query.sql.model.OrderByExpr; 043import org.nuxeo.ecm.core.query.sql.model.OrderByList; 044import org.nuxeo.ecm.core.query.sql.model.Predicate; 045import org.nuxeo.ecm.core.query.sql.model.QueryBuilder; 046import org.nuxeo.ecm.directory.AbstractDirectory; 047import org.nuxeo.ecm.directory.BaseSession; 048import org.nuxeo.ecm.directory.DirectoryException; 049import org.nuxeo.ecm.directory.BaseSession.FieldDetector; 050 051/** 052 * Trivial in-memory implementation of a Directory to use in unit tests. 053 * 054 * @author Florent Guillaume 055 */ 056public class MemoryDirectorySession extends BaseSession { 057 058 protected final Map<String, Map<String, Object>> data; 059 060 protected final String passwordField; 061 062 public MemoryDirectorySession(MemoryDirectory directory) { 063 super(directory, null); 064 data = Collections.synchronizedMap(new LinkedHashMap<String, Map<String, Object>>()); 065 passwordField = getPasswordField(); 066 } 067 068 /** To be implemented with a more specific type. */ 069 @Override 070 public MemoryDirectory getDirectory() { 071 return (MemoryDirectory) directory; 072 } 073 074 @Override 075 public boolean authenticate(String username, String password) { 076 Map<String, Object> map = data.get(username); 077 if (map == null) { 078 return false; 079 } 080 String expected = (String) map.get(passwordField); 081 if (expected == null) { 082 return false; 083 } 084 return expected.equals(password); 085 } 086 087 @Override 088 public void close() { 089 } 090 091 public void commit() { 092 } 093 094 public void rollback() { 095 throw new RuntimeException("Not implemented"); 096 } 097 098 @Override 099 public DocumentModel createEntryWithoutReferences(Map<String, Object> fieldMap) { 100 // find id 101 Object rawId = fieldMap.get(getIdField()); 102 if (rawId == null) { 103 throw new DirectoryException("Missing id"); 104 } 105 String id = String.valueOf(rawId); 106 Map<String, Object> map = data.get(id); 107 if (map != null) { 108 throw new DirectoryException(String.format("Entry with id %s already exists", id)); 109 } 110 map = new HashMap<>(); 111 data.put(id, map); 112 // put fields in map 113 for (Entry<String, Object> e : fieldMap.entrySet()) { 114 String fieldName = e.getKey(); 115 if (!getDirectory().schemaSet.contains(fieldName)) { 116 continue; 117 } 118 map.put(fieldName, e.getValue()); 119 } 120 return getEntry(id); 121 } 122 123 @Override 124 protected List<String> updateEntryWithoutReferences(DocumentModel docModel) { 125 String id = docModel.getId(); 126 DataModel dataModel = docModel.getDataModel(directory.getSchema()); 127 128 Map<String, Object> map = data.get(id); 129 if (map == null) { 130 throw new DirectoryException("UpdateEntry failed: entry '" + id + "' not found"); 131 } 132 133 for (String fieldName : getDirectory().schemaSet) { 134 try { 135 if (!dataModel.isDirty(fieldName) || fieldName.equals(getIdField())) { 136 continue; 137 } 138 } catch (PropertyNotFoundException e) { 139 continue; 140 } 141 // TODO references 142 map.put(fieldName, dataModel.getData(fieldName)); 143 } 144 dataModel.getDirtyFields().clear(); 145 return new ArrayList<>(); 146 } 147 148 @Override 149 protected void deleteEntryWithoutReferences(String id) { 150 checkDeleteConstraints(id); 151 data.remove(id); 152 } 153 154 @Override 155 public DocumentModel createEntry(Map<String, Object> fieldMap) { 156 checkPermission(SecurityConstants.WRITE); 157 return createEntryWithoutReferences(fieldMap); 158 } 159 160 @Override 161 public void updateEntry(DocumentModel docModel) { 162 checkPermission(SecurityConstants.WRITE); 163 updateEntryWithoutReferences(docModel); 164 } 165 166 @Override 167 public void deleteEntry(String id) { 168 checkPermission(SecurityConstants.WRITE); 169 deleteEntryWithoutReferences(id); 170 } 171 172 @Override 173 public DocumentModel getEntry(String id, boolean fetchReferences) { 174 // XXX no references here 175 Map<String, Object> map = data.get(id); 176 if (map == null) { 177 return null; 178 } 179 if (passwordField != null && map.get(passwordField) != null) { 180 map = new HashMap<>(map); 181 map.remove(passwordField); 182 } 183 try { 184 return createEntryModel(null, directory.getSchema(), id, map, isReadOnly()); 185 } catch (PropertyException e) { 186 throw new DirectoryException(e); 187 } 188 } 189 190 @Override 191 public DocumentModelList getEntries() { 192 DocumentModelList list = new DocumentModelListImpl(); 193 for (String id : data.keySet()) { 194 list.add(getEntry(id)); 195 } 196 return list; 197 } 198 199 // given our storage model this doesn't even make sense, as id field is 200 // unique 201 @Override 202 public void deleteEntry(String id, Map<String, String> map) { 203 throw new DirectoryException("Not implemented"); 204 } 205 206 @Override 207 public void deleteEntry(DocumentModel docModel) { 208 deleteEntry(docModel.getId()); 209 } 210 211 @Override 212 public DocumentModelList query(Map<String, Serializable> filter, Set<String> fulltext, Map<String, String> orderBy, 213 boolean fetchReferences, int limit, int offset) { 214 DocumentModelList results = new DocumentModelListImpl(); 215 // canonicalize filter 216 Map<String, Object> filt = new HashMap<>(); 217 for (Entry<String, Serializable> e : filter.entrySet()) { 218 String fieldName = e.getKey(); 219 if (!getDirectory().schemaSet.contains(fieldName)) { 220 continue; 221 } 222 filt.put(fieldName, e.getValue()); 223 } 224 // do the search 225 data_loop: for (Entry<String, Map<String, Object>> datae : data.entrySet()) { 226 String id = datae.getKey(); 227 Map<String, Object> map = datae.getValue(); 228 for (Entry<String, Object> e : filt.entrySet()) { 229 String fieldName = e.getKey(); 230 Object expected = e.getValue(); 231 Object value = map.get(fieldName); 232 if (value == null) { 233 if (expected != null) { 234 continue data_loop; 235 } 236 } else { 237 if (fulltext != null && fulltext.contains(fieldName)) { 238 if (!value.toString().toLowerCase().startsWith(expected.toString().toLowerCase())) { 239 continue data_loop; 240 } 241 } else { 242 if (!value.equals(expected)) { 243 continue data_loop; 244 } 245 } 246 } 247 } 248 // this entry matches 249 results.add(getEntry(id)); 250 } 251 // order entries 252 if (orderBy != null && !orderBy.isEmpty()) { 253 getDirectory().orderEntries(results, orderBy); 254 } 255 return applyQueryLimits(results, limit, offset); 256 } 257 258 @Override 259 public DocumentModelList query(QueryBuilder queryBuilder, boolean fetchReferences) { 260 if (!hasPermission(SecurityConstants.READ)) { 261 return new DocumentModelListImpl(); 262 } 263 if (FieldDetector.hasField(queryBuilder.predicate(), passwordField)) { 264 throw new DirectoryException("Cannot filter on password"); 265 } 266 DocumentModelList results = new DocumentModelListImpl(); 267 Predicate expression = queryBuilder.predicate(); 268 OrderByList orders = queryBuilder.orders(); 269 int limit = Math.max(0, (int) queryBuilder.limit()); 270 int offset = Math.max(0, (int) queryBuilder.offset()); 271 boolean countTotal = queryBuilder.countTotal(); 272 273 // do the search 274 MemoryDirectoryExpressionEvaluator evaluator = new MemoryDirectoryExpressionEvaluator(getDirectory()); 275 for (Entry<String, Map<String, Object>> datae : data.entrySet()) { 276 if (evaluator.matchesEntry(expression, datae.getValue())) { 277 results.add(getEntry(datae.getKey())); 278 } 279 } 280 // order entries 281 if (!orders.isEmpty()) { 282 getDirectory().orderEntries(results, AbstractDirectory.makeOrderBy(orders)); 283 } 284 results = applyQueryLimits(results, limit, offset); 285 if ((limit != 0 || offset != 0) && !countTotal) { 286 // compat with other directories 287 ((DocumentModelListImpl) results).setTotalSize(-2); 288 } 289 return results; 290 } 291 292 @Override 293 public List<String> queryIds(QueryBuilder queryBuilder) { 294 if (!hasPermission(SecurityConstants.READ)) { 295 return Collections.emptyList(); 296 } 297 if (FieldDetector.hasField(queryBuilder.predicate(), passwordField)) { 298 throw new DirectoryException("Cannot filter on password"); 299 } 300 DocumentModelList entries = new DocumentModelListImpl(); // needed if we have ordering 301 List<String> ids = new ArrayList<>(); 302 Predicate expression = queryBuilder.predicate(); 303 OrderByList orders = queryBuilder.orders(); 304 boolean order = !orders.isEmpty(); 305 int limit = Math.max(0, (int) queryBuilder.limit()); 306 int offset = Math.max(0, (int) queryBuilder.offset()); 307 308 // do the search 309 MemoryDirectoryExpressionEvaluator evaluator = new MemoryDirectoryExpressionEvaluator(getDirectory()); 310 for (Entry<String, Map<String, Object>> datae : data.entrySet()) { 311 if (evaluator.matchesEntry(expression, datae.getValue())) { 312 String id = datae.getKey(); 313 if (order) { 314 entries.add(getEntry(id)); 315 } else { 316 ids.add(id); 317 } 318 } 319 } 320 // order entries if needed 321 if (order) { 322 getDirectory().orderEntries(entries, AbstractDirectory.makeOrderBy(orders)); 323 entries.forEach(doc -> ids.add(doc.getId())); 324 } 325 // apply query limits 326 return applyQueryLimits(ids, limit, offset); 327 } 328 329 @Override 330 public DocumentModel createEntry(DocumentModel entry) { 331 Map<String, Object> fieldMap = entry.getProperties(directory.getSchema()); 332 return createEntry(fieldMap); 333 } 334 335 @Override 336 public boolean hasEntry(String id) { 337 return data.containsKey(id); 338 } 339 340}