001/*
002 * (C) Copyright 2014 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 */
019package org.nuxeo.ecm.core.storage.dbs;
020
021import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_CHANGE_TOKEN;
022import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ID;
023import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_NAME;
024import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_PARENT_ID;
025import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_PRIMARY_TYPE;
026import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_SYS_CHANGE_TOKEN;
027import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_VERSION_SERIES_ID;
028
029import java.io.Serializable;
030
031import org.nuxeo.ecm.core.api.model.Delta;
032import org.nuxeo.ecm.core.model.Document;
033import org.nuxeo.ecm.core.storage.State.StateDiff;
034import org.nuxeo.ecm.core.storage.StateHelper;
035import org.nuxeo.ecm.core.storage.BaseDocument;
036import org.nuxeo.ecm.core.storage.State;
037
038/**
039 * Implementation of a {@link Document} state for Document-Based Storage.
040 * <p>
041 * It wraps a {@link State}, together with a dirty flag.
042 *
043 * @since 5.9.4
044 */
045public class DBSDocumentState {
046
047    private static final String UNDEFINED_PARENT_ID = "_undefined_";
048    /**
049     * The current state.
050     */
051    protected State state;
052
053    /**
054     * When non-null, the original state (otherwise the state hasn't been modified).
055     */
056    protected State originalState;
057
058    private String id;
059
060    private String parentId = UNDEFINED_PARENT_ID;
061
062    /**
063     * Constructs an empty state.
064     */
065    public DBSDocumentState() {
066        state = new State();
067        originalState = null;
068    }
069
070    /**
071     * Constructs a document state from the copy of an existing base state.
072     */
073    public DBSDocumentState(State base) {
074        state = StateHelper.deepCopy(base);
075        originalState = null;
076    }
077
078    /**
079     * This must be called if we're about to directly change the internal state.
080     */
081    public void markDirty() {
082        if (originalState == null) {
083            originalState = StateHelper.deepCopy(state);
084        }
085    }
086
087    /**
088     * Checks if the document state has been changed since its construction or the last call to {@link #setNotDirty}.
089     */
090    public boolean isDirty() {
091        return originalState != null;
092    }
093
094    public void setNotDirty() {
095        originalState = null;
096        StateHelper.resetDeltas(state);
097        // reset the change token delta even if delta processing is disabled in StateHelper.resetDeltas
098        Serializable value = state.get(KEY_SYS_CHANGE_TOKEN);
099        if (value instanceof Delta) {
100            state.put(KEY_SYS_CHANGE_TOKEN, ((Delta) value).getFullValue());
101        }
102    }
103
104    /**
105     * Gets the state. If the caller changes the state, it must also call {@link #markDirty()} to inform this object
106     * that the state is dirtied.
107     */
108    public State getState() {
109        return state;
110    }
111
112    /**
113     * Gets a diff of what changed since this document state was read from database or saved.
114     *
115     * @return {@code null} if there was no change, or a {@link StateDiff}
116     */
117    public StateDiff getStateChange() {
118        if (originalState == null) {
119            return null;
120        }
121        StateDiff diff = StateHelper.diff(originalState, state);
122        if (diff.isEmpty()) {
123            return null;
124        }
125        return diff;
126    }
127
128    /**
129     * Gets the original state for this, needed when creating an undo log.
130     *
131     * @return a state that must not be modified
132     * @since 7.4
133     */
134    public State getOriginalState() {
135        return originalState == null ? state : originalState;
136    }
137
138    public Serializable get(String key) {
139        if (KEY_ID.equals(key)) {
140            return getId();
141        } else if (KEY_PARENT_ID.equals(key)) {
142            return getParentId();
143        }
144        return state.get(key);
145    }
146
147    public void put(String key, Serializable value) {
148        markDirty();
149
150        if (KEY_ID.equals(key)) {
151            id = (String) value;
152        } else if (KEY_PARENT_ID.equals(key)) {
153            parentId = (String) value;
154        }
155        state.put(key, value);
156    }
157
158    public boolean containsKey(String key) {
159        return state.get(key) != null;
160    }
161
162    public String getId() {
163        if (id == null) {
164            id = (String) state.get(KEY_ID);
165        }
166        return id;
167    }
168
169    public String getParentId() {
170        // use a marker because parentId can be null
171        if (parentId == UNDEFINED_PARENT_ID) {
172            parentId = (String) state.get(KEY_PARENT_ID);
173        }
174        return parentId;
175    }
176
177    public String getName() {
178        return (String) get(KEY_NAME);
179    }
180
181    public String getPrimaryType() {
182        return (String) get(KEY_PRIMARY_TYPE);
183    }
184
185    public String getVersionSeriesId() {
186        return (String) get(KEY_VERSION_SERIES_ID);
187    }
188
189    public Long getSysChangeToken() {
190        return (Long) get(KEY_SYS_CHANGE_TOKEN);
191    }
192
193    public Long getChangeToken() {
194        return (Long) get(KEY_CHANGE_TOKEN);
195    }
196
197    public boolean validateUserVisibleChangeToken(String userVisibleChangeToken) {
198        Long sysChangeToken = getSysChangeToken();
199        Long changeToken = getChangeToken();
200        return BaseDocument.validateUserVisibleChangeToken(sysChangeToken, changeToken, userVisibleChangeToken);
201    }
202
203    @Override
204    public String toString() {
205        return getClass().getSimpleName() + '(' + (isDirty() ? "dirty," : "") + state.toString() + ')';
206    }
207
208}