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}