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