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 -&gt; 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}