001/* 002 * (C) Copyright 2014-2017 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 */ 019package org.nuxeo.ecm.core.storage.mem; 020 021import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_BLOB_DATA; 022import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_FULLTEXT_BINARY; 023import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_LOCK_CREATED; 024import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_LOCK_OWNER; 025 026import java.io.Serializable; 027import java.util.ArrayList; 028import java.util.Calendar; 029import java.util.Collections; 030import java.util.List; 031import java.util.Map; 032import java.util.UUID; 033import java.util.concurrent.ConcurrentHashMap; 034import java.util.concurrent.atomic.AtomicLong; 035 036import org.nuxeo.ecm.core.api.DocumentNotFoundException; 037import org.nuxeo.ecm.core.api.Lock; 038import org.nuxeo.ecm.core.api.lock.LockManager; 039import org.nuxeo.ecm.core.blob.DocumentBlobManager; 040import org.nuxeo.ecm.core.storage.State; 041import org.nuxeo.ecm.core.storage.dbs.DBSDocument; 042import org.nuxeo.ecm.core.storage.dbs.DBSRepository; 043import org.nuxeo.ecm.core.storage.dbs.DBSRepositoryBase; 044import org.nuxeo.runtime.api.Framework; 045 046/** 047 * In-memory implementation of a {@link DBSRepository}. 048 * <p> 049 * Internally, the repository is a map from id to document object. 050 * <p> 051 * A document object is a JSON-like document stored as a Map recursively containing the data, see {@link DBSDocument} 052 * for the description of the document. 053 * 054 * @since 5.9.4 055 */ 056public class MemRepository extends DBSRepositoryBase { 057 058 protected static final String NOSCROLL_ID = "noscroll"; 059 060 // for debug 061 private final AtomicLong temporaryIdCounter = new AtomicLong(0); 062 063 /** 064 * The content of the repository, a map of document id -> object. 065 */ 066 protected Map<String, State> states; 067 068 public MemRepository(MemRepositoryDescriptor descriptor) { 069 super(descriptor.name, descriptor); 070 initRepository(); 071 } 072 073 @Override 074 public List<IdType> getAllowedIdTypes() { 075 return Collections.singletonList(IdType.varchar); 076 } 077 078 @Override 079 public void shutdown() { 080 super.shutdown(); 081 states = null; 082 } 083 084 protected void initRepository() { 085 states = new ConcurrentHashMap<>(); 086 try (MemConnection connection = getConnection()) { 087 connection.initRepository(); 088 } 089 } 090 091 protected String generateNewId() { 092 if (DBSRepositoryBase.DEBUG_UUIDS) { 093 return "UUID_" + temporaryIdCounter.incrementAndGet(); 094 } else { 095 return UUID.randomUUID().toString(); 096 } 097 } 098 099 @Override 100 public boolean supportsTransactions() { 101 return false; 102 } 103 104 @Override 105 public MemConnection getConnection() { 106 return new MemConnection(this); 107 } 108 109 /* synchronized */ 110 @Override 111 public synchronized Lock getLock(String id) { 112 State state = states.get(id); 113 if (state == null) { 114 // document not found 115 throw new DocumentNotFoundException(id); 116 } 117 String owner = (String) state.get(KEY_LOCK_OWNER); 118 if (owner == null) { 119 return null; 120 } 121 Calendar created = (Calendar) state.get(KEY_LOCK_CREATED); 122 return new Lock(owner, created); 123 } 124 125 /* synchronized */ 126 @Override 127 public synchronized Lock setLock(String id, Lock lock) { 128 State state = states.get(id); 129 if (state == null) { 130 // document not found 131 throw new DocumentNotFoundException(id); 132 } 133 String owner = (String) state.get(KEY_LOCK_OWNER); 134 if (owner != null) { 135 // return old lock 136 Calendar created = (Calendar) state.get(KEY_LOCK_CREATED); 137 return new Lock(owner, created); 138 } 139 state.put(KEY_LOCK_OWNER, lock.getOwner()); 140 state.put(KEY_LOCK_CREATED, lock.getCreated()); 141 return null; 142 } 143 144 /* synchronized */ 145 @Override 146 public synchronized Lock removeLock(String id, String owner) { 147 State state = states.get(id); 148 if (state == null) { 149 // document not found 150 throw new DocumentNotFoundException(id); 151 } 152 String oldOwner = (String) state.get(KEY_LOCK_OWNER); 153 if (oldOwner == null) { 154 // no previous lock 155 return null; 156 } 157 Calendar oldCreated = (Calendar) state.get(KEY_LOCK_CREATED); 158 if (!LockManager.canLockBeRemoved(oldOwner, owner)) { 159 // existing mismatched lock, flag failure 160 return new Lock(oldOwner, oldCreated, true); 161 } 162 // remove lock 163 state.put(KEY_LOCK_OWNER, null); 164 state.put(KEY_LOCK_CREATED, null); 165 // return old lock 166 return new Lock(oldOwner, oldCreated); 167 } 168 169 protected List<List<String>> binaryPaths; 170 171 @Override 172 protected void initBlobsPaths() { 173 MemBlobFinder finder = new MemBlobFinder(); 174 finder.visit(); 175 binaryPaths = finder.binaryPaths; 176 } 177 178 protected static class MemBlobFinder extends BlobFinder { 179 protected List<List<String>> binaryPaths = new ArrayList<>(); 180 181 @Override 182 protected void recordBlobPath() { 183 binaryPaths.add(new ArrayList<>(path)); 184 } 185 } 186 187 @Override 188 public void markReferencedBinaries() { 189 DocumentBlobManager blobManager = Framework.getService(DocumentBlobManager.class); 190 for (State state : states.values()) { 191 for (List<String> path : binaryPaths) { 192 markReferencedBinaries(state, path, 0, blobManager); 193 } 194 } 195 } 196 197 protected void markReferencedBinaries(State state, List<String> path, int start, DocumentBlobManager blobManager) { 198 for (int i = start; i < path.size(); i++) { 199 String name = path.get(i); 200 Serializable value = state.get(name); 201 if (value instanceof State) { 202 state = (State) value; 203 } else { 204 if (value instanceof List) { 205 @SuppressWarnings("unchecked") 206 List<Object> list = (List<Object>) value; 207 for (Object v : list) { 208 if (v instanceof State) { 209 markReferencedBinaries((State) v, path, i + 1, blobManager); 210 } else { 211 markReferencedBinary(v, blobManager); 212 } 213 } 214 } 215 state = null; 216 break; 217 } 218 } 219 if (state != null) { 220 Serializable data = state.get(KEY_BLOB_DATA); 221 markReferencedBinary(data, blobManager); 222 if (isFulltextStoredInBlob()) { 223 data = state.get(KEY_FULLTEXT_BINARY); 224 markReferencedBinary(data, blobManager); 225 } 226 } 227 } 228 229 protected void markReferencedBinary(Object value, DocumentBlobManager blobManager) { 230 if (!(value instanceof String)) { 231 return; 232 } 233 String key = (String) value; 234 blobManager.markReferencedBinary(key, repositoryName); 235 } 236 237}