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 116 * {@code #row.values.length}. 117 */ 118 protected Serializable[] oldvalues; 119 120 private State state; // default is DETACHED 121 122 protected PersistenceContext context; 123 124 /** 125 * Constructs a {@link Fragment} from a {@link Row}. 126 * 127 * @param row the row 128 * @param state the initial state for the fragment 129 * @param context the persistence context to which the fragment is tied, or {@code null} 130 */ 131 protected Fragment(Row row, State state, PersistenceContext context) { 132 this.row = row; 133 this.state = state; 134 this.context = context; 135 switch (state) { 136 case DETACHED: 137 if (context != null) { 138 throw new IllegalArgumentException(); 139 } 140 break; 141 case CREATED: 142 case DELETED: 143 case DELETED_DEPENDENT: 144 context.setFragmentModified(this); // not in pristine 145 break; 146 case ABSENT: 147 case PRISTINE: 148 context.setFragmentPristine(this); // not in modified 149 break; 150 case MODIFIED: 151 case INVALIDATED_MODIFIED: 152 case INVALIDATED_DELETED: 153 throw new IllegalArgumentException(state.toString()); 154 } 155 clearDirty(); 156 } 157 158 /** 159 * Gets the state. 160 * 161 * @return the state 162 */ 163 public State getState() { 164 return state; 165 } 166 167 /** 168 * Sets the id. This only used at most once to change a temporary id to the persistent one. 169 * 170 * @param id the new persistent id 171 */ 172 public void setId(Serializable id) { 173 row.id = id; 174 } 175 176 /** 177 * Gets the id. 178 * 179 * @return the id 180 */ 181 public Serializable getId() { 182 return row.id; 183 } 184 185 /** 186 * Clears the dirty state. 187 */ 188 public void clearDirty() { 189 // turn back deltas into full values 190 Serializable[] values = row.values; 191 int len = values.length; 192 for (int i = 0; i < len; i++) { 193 Serializable ob = values[i]; 194 if (ob instanceof Delta) { 195 values[i] = ((Delta) ob).getFullValue(); 196 } 197 } 198 // clone to clear the dirty state 199 oldvalues = values.clone(); 200 } 201 202 /** 203 * Returns the row update to do in the database to write this value. 204 * 205 * @return a row update, or {@code null} if the value is unchanged since last clear 206 * @since 8.3 207 */ 208 public abstract RowUpdate getRowUpdate(); 209 210 /** 211 * Refetches this fragment from the database. Needed when an invalidation has been received and the fragment is 212 * accessed again. 213 * 214 * @return the new state, {@link State#PRISTINE} or {@link State#ABSENT} 215 */ 216 protected abstract State refetch(); 217 218 /** 219 * Resets the data for a fragment that was invalidated by deletion. 220 * 221 * @return the new state, {@link State#PRISTINE} or {@link State#ABSENT} 222 */ 223 protected abstract State refetchDeleted(); 224 225 /** 226 * Checks that access to the fragment is possible. Called internally before a get, so that invalidated fragments can 227 * be refetched. 228 */ 229 protected void accessed() { 230 switch (state) { 231 case DETACHED: 232 case ABSENT: 233 case PRISTINE: 234 case CREATED: 235 case MODIFIED: 236 case DELETED: 237 case DELETED_DEPENDENT: 238 break; 239 case INVALIDATED_MODIFIED: 240 state = refetch(); 241 break; 242 case INVALIDATED_DELETED: 243 state = refetchDeleted(); 244 } 245 } 246 247 /** 248 * Marks the fragment modified. Called internally after a put/set. 249 */ 250 protected void markModified() { 251 switch (state) { 252 case ABSENT: 253 context.setFragmentModified(this); 254 state = State.CREATED; 255 break; 256 case INVALIDATED_MODIFIED: 257 // can only happen if overwrite all invalidated (array) 258 // fall through 259 case PRISTINE: 260 context.setFragmentModified(this); 261 state = State.MODIFIED; 262 break; 263 case DETACHED: 264 case CREATED: 265 case MODIFIED: 266 case DELETED: 267 case DELETED_DEPENDENT: 268 break; 269 case INVALIDATED_DELETED: 270 throw new ConcurrentModificationException("Modifying a concurrently deleted value"); 271 } 272 } 273 274 /** 275 * Marks the fragment deleted. Called after a remove. 276 */ 277 protected void setDeleted(boolean primary) { 278 switch (state) { 279 case DETACHED: 280 break; 281 case ABSENT: 282 case INVALIDATED_DELETED: 283 case CREATED: 284 context = null; 285 state = State.DETACHED; 286 break; 287 case PRISTINE: 288 case INVALIDATED_MODIFIED: 289 state = primary ? State.DELETED : State.DELETED_DEPENDENT; 290 break; 291 case MODIFIED: 292 state = primary ? State.DELETED : State.DELETED_DEPENDENT; 293 break; 294 case DELETED: 295 case DELETED_DEPENDENT: 296 throw new RuntimeException(this.toString()); 297 } 298 } 299 300 /** 301 * Detaches the fragment from its persistence context. The caller makes sure that the fragment is removed from the 302 * context map. 303 */ 304 protected void setDetached() { 305 state = State.DETACHED; 306 context = null; 307 } 308 309 /** 310 * Sets the (created/modified) fragment in the pristine state. Called after a save. 311 */ 312 protected void setPristine() { 313 switch (state) { 314 case CREATED: 315 case MODIFIED: 316 state = State.PRISTINE; 317 break; 318 case ABSENT: 319 case PRISTINE: 320 case DELETED: 321 case DELETED_DEPENDENT: 322 case DETACHED: 323 case INVALIDATED_MODIFIED: 324 case INVALIDATED_DELETED: 325 // incoherent with the pristine map + expected state 326 throw new RuntimeException(this.toString()); 327 } 328 } 329 330 /** 331 * Sets the fragment in the "invalidated from a modification" state. This is called: 332 * <ul> 333 * <li>when a database operation does non-tracked changes, which means that on access a refetch will be needed, 334 * <li>during post-commit invalidation. 335 * </ul> 336 */ 337 protected void setInvalidatedModified() { 338 switch (state) { 339 case ABSENT: 340 case PRISTINE: 341 case CREATED: 342 case MODIFIED: 343 case DELETED: 344 case DELETED_DEPENDENT: 345 state = State.INVALIDATED_MODIFIED; 346 break; 347 case INVALIDATED_MODIFIED: 348 case INVALIDATED_DELETED: 349 break; 350 case DETACHED: 351 throw new RuntimeException(this.toString()); 352 } 353 } 354 355 /** 356 * Sets the fragment in the "invalidated from a deletion" state. This is called: 357 * <ul> 358 * <li>when a database operation does a delete, 359 * <li>during post-commit invalidation. 360 * </ul> 361 */ 362 protected void setInvalidatedDeleted() { 363 switch (state) { 364 case ABSENT: 365 case PRISTINE: 366 case CREATED: 367 case MODIFIED: 368 case DELETED: 369 case DELETED_DEPENDENT: 370 case INVALIDATED_MODIFIED: 371 state = State.INVALIDATED_DELETED; 372 break; 373 case INVALIDATED_DELETED: 374 break; 375 case DETACHED: 376 throw new RuntimeException(this.toString()); 377 } 378 } 379 380 @Override 381 public String toString() { 382 StringBuilder sb = new StringBuilder(); 383 sb.append(getClass().getSimpleName()); 384 sb.append("(row="); 385 sb.append(row); 386 sb.append(", state="); 387 sb.append(getState()); 388 sb.append(')'); 389 return sb.toString(); 390 } 391} 392 393/** 394 * A fragments map holds all {@link Fragment}s for non-main tables. 395 */ 396class FragmentsMap extends HashMap<String, Fragment> { 397 398 private static final long serialVersionUID = 1L; 399 400} 401 402/** 403 * Utility class grouping a main {@link Fragment} with a related hierarchy {@link Fragment} and additional fragments. 404 * <p> 405 * If the main and hierarchy tables are not separate, then the hierarchy fragment is unused. 406 * <p> 407 * This is all the data needed to describe a {@link Node}. 408 */ 409class FragmentGroup { 410 411 public final SimpleFragment hier; 412 413 public final FragmentsMap fragments; 414 415 public FragmentGroup(SimpleFragment hier, FragmentsMap fragments) { 416 this.hier = hier; 417 this.fragments = fragments; 418 } 419 420 @Override 421 public String toString() { 422 return getClass().getSimpleName() + '(' + hier + ", " + fragments + ')'; 423 } 424}