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}