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