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}