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