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}