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}