001/* 002 * (C) Copyright 2006-2011 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 */ 019 020package org.nuxeo.ecm.core.storage.sql; 021 022import java.io.Serializable; 023import java.util.ConcurrentModificationException; 024import java.util.HashMap; 025 026import org.nuxeo.ecm.core.api.model.Delta; 027import org.nuxeo.ecm.core.storage.sql.RowMapper.RowUpdate; 028 029/** 030 * A rich value corresponding to one row or a collection of rows in a table. 031 * <p> 032 * In addition to the basic {@link Row}, this holds the old values (to check dirty state), the state and a reference to 033 * the session. 034 * <p> 035 * This class has two kinds of state-changing methods: 036 * <ul> 037 * <li>the "set" ones, which only change the state,</li> 038 * <li>the "mark" ones, which change the state and do the corresponding changes in the pristine/modified maps of the 039 * context.</li> 040 * <li></li> 041 * </ul> 042 * 043 * @author Florent Guillaume 044 */ 045public abstract class Fragment implements Serializable { 046 047 private static final long serialVersionUID = 1L; 048 049 /** 050 * The possible states of a fragment. 051 */ 052 public enum State { 053 054 /** 055 * The fragment is not attached to a persistence context. 056 */ 057 DETACHED, // first is default 058 059 /** 060 * The fragment has been read and found to be absent in the database. It contains default data (usually 061 * {@code null}). It lives in the context's pristine map. Upon modification, the state will change to 062 * {@link #CREATED}. 063 */ 064 ABSENT, 065 066 /** 067 * The fragment exists in the database but hasn't been changed yet. It lives in the context's pristine map. Upon 068 * modification, the state will change to {@link #MODIFIED}. 069 */ 070 PRISTINE, 071 072 /** 073 * The fragment does not exist in the database and will be inserted upon save. It lives in the context's 074 * modified map. Upon save it will be inserted in the database and the state will change to {@link #PRISTINE}. 075 */ 076 CREATED, 077 078 /** 079 * The fragment has been modified. It lives in the context's modified map. Upon save the database will be 080 * updated and the state will change to {@link #PRISTINE}. 081 */ 082 MODIFIED, 083 084 /** 085 * The fragment has been deleted. It lives in the context's modified map. Upon save it will be deleted from the 086 * database and the state will change to {@link #DETACHED}. 087 */ 088 DELETED, 089 090 /** 091 * The fragment has been deleted as a consequence of another fragment being deleted (cascade). It lives in the 092 * context's modified map. Upon save it will be implicitly deleted from the database by the deletion of a 093 * {@link #DELETED} fragment, and the state will change to {@link #DETACHED}. 094 */ 095 DELETED_DEPENDENT, 096 097 /** 098 * The fragment has been invalidated by a modification or creation. Any access must refetch it. It lives in the 099 * context's pristine map. 100 */ 101 INVALIDATED_MODIFIED, 102 103 /** 104 * The fragment has been invalidated by a deletion. It lives in the context's pristine map. 105 */ 106 INVALIDATED_DELETED 107 } 108 109 /** 110 * The row holding the data. 111 */ 112 protected Row row; 113 114 /** 115 * The row old values, from the time of construction / refetch. The size of the the array is following {@link #row.values.length}. 116 */ 117 protected Serializable[] oldvalues; 118 119 private State state; // default is DETACHED 120 121 protected PersistenceContext context; 122 123 /** 124 * Constructs a {@link Fragment} from a {@link Row}. 125 * 126 * @param row the row 127 * @param state the initial state for the fragment 128 * @param context the persistence context to which the fragment is tied, or {@code null} 129 */ 130 protected Fragment(Row row, State state, PersistenceContext context) { 131 this.row = row; 132 this.state = state; 133 this.context = context; 134 switch (state) { 135 case DETACHED: 136 if (context != null) { 137 throw new IllegalArgumentException(); 138 } 139 break; 140 case CREATED: 141 case DELETED: 142 case DELETED_DEPENDENT: 143 context.setFragmentModified(this); // not in pristine 144 break; 145 case ABSENT: 146 case PRISTINE: 147 context.setFragmentPristine(this); // not in modified 148 break; 149 case MODIFIED: 150 case INVALIDATED_MODIFIED: 151 case INVALIDATED_DELETED: 152 throw new IllegalArgumentException(state.toString()); 153 } 154 clearDirty(); 155 } 156 157 /** 158 * Gets the state. 159 * 160 * @return the state 161 */ 162 public State getState() { 163 return state; 164 } 165 166 /** 167 * Sets the id. This only used at most once to change a temporary id to the persistent one. 168 * 169 * @param id the new persistent id 170 */ 171 public void setId(Serializable id) { 172 row.id = id; 173 } 174 175 /** 176 * Gets the id. 177 * 178 * @return the id 179 */ 180 public Serializable getId() { 181 return row.id; 182 } 183 184 /** 185 * Clears the dirty state. 186 */ 187 public void clearDirty() { 188 // turn back deltas into full values 189 Serializable[] values = row.values; 190 int len = values.length; 191 for (int i = 0; i < len; i++) { 192 Serializable ob = values[i]; 193 if (ob instanceof Delta) { 194 values[i] = ((Delta) ob).getFullValue(); 195 } 196 } 197 // clone to clear the dirty state 198 oldvalues = values.clone(); 199 } 200 201 /** 202 * Returns the row update to do in the database to write this value. 203 * 204 * @return a row update, or {@code null} if the value is unchanged since last clear 205 * @since 8.3 206 */ 207 public abstract RowUpdate getRowUpdate(); 208 209 /** 210 * Refetches this fragment from the database. Needed when an invalidation has been received and the fragment is 211 * accessed again. 212 * 213 * @return the new state, {@link State#PRISTINE} or {@link State#ABSENT} 214 */ 215 protected abstract State refetch(); 216 217 /** 218 * Resets the data for a fragment that was invalidated by deletion. 219 * 220 * @return the new state, {@link State#PRISTINE} or {@link State#ABSENT} 221 */ 222 protected abstract State refetchDeleted(); 223 224 /** 225 * Checks that access to the fragment is possible. Called internally before a get, so that invalidated fragments can 226 * be refetched. 227 */ 228 protected void accessed() { 229 switch (state) { 230 case DETACHED: 231 case ABSENT: 232 case PRISTINE: 233 case CREATED: 234 case MODIFIED: 235 case DELETED: 236 case DELETED_DEPENDENT: 237 break; 238 case INVALIDATED_MODIFIED: 239 state = refetch(); 240 break; 241 case INVALIDATED_DELETED: 242 state = refetchDeleted(); 243 } 244 } 245 246 /** 247 * Marks the fragment modified. Called internally after a put/set. 248 */ 249 protected void markModified() { 250 switch (state) { 251 case ABSENT: 252 context.setFragmentModified(this); 253 state = State.CREATED; 254 break; 255 case INVALIDATED_MODIFIED: 256 // can only happen if overwrite all invalidated (array) 257 // fall through 258 case PRISTINE: 259 context.setFragmentModified(this); 260 state = State.MODIFIED; 261 break; 262 case DETACHED: 263 case CREATED: 264 case MODIFIED: 265 case DELETED: 266 case DELETED_DEPENDENT: 267 break; 268 case INVALIDATED_DELETED: 269 throw new ConcurrentModificationException("Modifying a concurrently deleted value"); 270 } 271 } 272 273 /** 274 * Marks the fragment deleted. Called after a remove. 275 */ 276 protected void setDeleted(boolean primary) { 277 switch (state) { 278 case DETACHED: 279 break; 280 case ABSENT: 281 case INVALIDATED_DELETED: 282 context = null; 283 state = State.DETACHED; 284 break; 285 case CREATED: 286 context = null; 287 state = State.DETACHED; 288 break; 289 case PRISTINE: 290 case INVALIDATED_MODIFIED: 291 state = primary ? State.DELETED : State.DELETED_DEPENDENT; 292 break; 293 case MODIFIED: 294 state = primary ? State.DELETED : State.DELETED_DEPENDENT; 295 break; 296 case DELETED: 297 case DELETED_DEPENDENT: 298 throw new RuntimeException(this.toString()); 299 } 300 } 301 302 /** 303 * Detaches the fragment from its persistence context. The caller makes sure that the fragment is removed from the 304 * context map. 305 */ 306 protected void setDetached() { 307 state = State.DETACHED; 308 context = null; 309 } 310 311 /** 312 * Sets the (created/modified) fragment in the pristine state. Called after a save. 313 */ 314 protected void setPristine() { 315 switch (state) { 316 case CREATED: 317 case MODIFIED: 318 state = State.PRISTINE; 319 break; 320 case ABSENT: 321 case PRISTINE: 322 case DELETED: 323 case DELETED_DEPENDENT: 324 case DETACHED: 325 case INVALIDATED_MODIFIED: 326 case INVALIDATED_DELETED: 327 // incoherent with the pristine map + expected state 328 throw new RuntimeException(this.toString()); 329 } 330 } 331 332 /** 333 * Sets the fragment in the "invalidated from a modification" state. This is called: 334 * <ul> 335 * <li>when a database operation does non-tracked changes, which means that on access a refetch will be needed, 336 * <li>during post-commit invalidation. 337 * </ul> 338 */ 339 protected void setInvalidatedModified() { 340 switch (state) { 341 case ABSENT: 342 case PRISTINE: 343 case CREATED: 344 case MODIFIED: 345 case DELETED: 346 case DELETED_DEPENDENT: 347 state = State.INVALIDATED_MODIFIED; 348 break; 349 case INVALIDATED_MODIFIED: 350 case INVALIDATED_DELETED: 351 break; 352 case DETACHED: 353 throw new RuntimeException(this.toString()); 354 } 355 } 356 357 /** 358 * Sets the fragment in the "invalidated from a deletion" state. This is called: 359 * <ul> 360 * <li>when a database operation does a delete, 361 * <li>during post-commit invalidation. 362 * </ul> 363 */ 364 protected void setInvalidatedDeleted() { 365 switch (state) { 366 case ABSENT: 367 case PRISTINE: 368 case CREATED: 369 case MODIFIED: 370 case DELETED: 371 case DELETED_DEPENDENT: 372 case INVALIDATED_MODIFIED: 373 state = State.INVALIDATED_DELETED; 374 break; 375 case INVALIDATED_DELETED: 376 break; 377 case DETACHED: 378 throw new RuntimeException(this.toString()); 379 } 380 } 381 382 @Override 383 public String toString() { 384 StringBuilder buf = new StringBuilder(); 385 buf.append(getClass().getSimpleName()); 386 buf.append("(row="); 387 buf.append(row); 388 buf.append(", state="); 389 buf.append(getState()); 390 buf.append(')'); 391 return buf.toString(); 392 } 393} 394 395/** 396 * A fragments map holds all {@link Fragment}s for non-main tables. 397 */ 398class FragmentsMap extends HashMap<String, Fragment> { 399 400 private static final long serialVersionUID = 1L; 401 402} 403 404/** 405 * Utility class grouping a main {@link Fragment} with a related hierarchy {@link Fragment} and additional fragments. 406 * <p> 407 * If the main and hierarchy tables are not separate, then the hierarchy fragment is unused. 408 * <p> 409 * This is all the data needed to describe a {@link Node}. 410 */ 411class FragmentGroup { 412 413 public final SimpleFragment hier; 414 415 public final FragmentsMap fragments; 416 417 public FragmentGroup(SimpleFragment hier, FragmentsMap fragments) { 418 this.hier = hier; 419 this.fragments = fragments; 420 } 421 422 @Override 423 public String toString() { 424 return getClass().getSimpleName() + '(' + hier + ", " + fragments + ')'; 425 } 426}