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