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 static java.lang.Boolean.TRUE;
023
024import java.io.Serializable;
025import java.util.Arrays;
026import java.util.Collection;
027import java.util.HashSet;
028import java.util.LinkedList;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032
033import javax.transaction.xa.XAException;
034import javax.transaction.xa.Xid;
035
036/**
037 * A {@link RowMapper} maps {@link Row}s to and from the database.
038 * <p>
039 * These are the operations that can benefit from a cache.
040 *
041 * @see SoftRefCachingRowMapper
042 */
043public interface RowMapper {
044
045    /**
046     * Computes a new unique id.
047     *
048     * @return a new unique id
049     */
050    Serializable generateNewId();
051
052    /*
053     * ----- Batch -----
054     */
055
056    /**
057     * Reads a set of rows for the given {@link RowId}s.
058     * <p>
059     * For each requested row, either a {@link Row} is found and returned, or a {@link RowId} (not implementing
060     * {@link Row}) is returned to signify an absent row.
061     *
062     * @param rowIds the row ids (including their table name)
063     * @param cacheOnly if {@code true}, only hit memory
064     * @return the collection of {@link Row}s (or {@link RowId}s if the row was absent from the database). Order is not
065     *         the same as the input {@code rowIds}
066     */
067    List<? extends RowId> read(Collection<RowId> rowIds, boolean cacheOnly);
068
069    /**
070     * A {@link Row} and a list of its keys that have to be updated.
071     */
072    public static final class RowUpdate implements Serializable, Comparable<RowUpdate> {
073        private static final long serialVersionUID = 1L;
074
075        public final Row row;
076
077        // used for simple fragments
078        public final Collection<String> keys;
079
080        // used for collection fragment right push, the pos at which to start to insert
081        // if -1 then a full update must be done
082        public final int pos;
083
084        // conditions to add to get a conditional update, for change token
085        public Map<String, Serializable> conditions;
086
087        /** Constructor for simple fragment update. */
088        public RowUpdate(Row row, Collection<String> keys) {
089            this.row = row;
090            this.keys = keys;
091            pos = -1;
092        }
093
094        /** Constructor for collection fragment full update. */
095        public RowUpdate(Row row) {
096            this(row, -1);
097        }
098
099        /** Constructor for collection fragment right push update. */
100        public RowUpdate(Row row, int pos) {
101            this.row = row;
102            keys = null;
103            this.pos = pos;
104        }
105
106        public void setConditions(Map<String, Serializable> conditions) {
107            this.conditions = conditions;
108        }
109
110        @Override
111        public int hashCode() {
112            return row.hashCode();
113        }
114
115        @Override
116        public boolean equals(Object other) {
117            if (other instanceof RowUpdate) {
118                return equal((RowUpdate) other);
119            }
120            return false;
121        }
122
123        private boolean equal(RowUpdate other) {
124            return other.row.equals(row);
125        }
126
127        @Override
128        public int compareTo(RowUpdate other) {
129            return row.compareTo(other.row);
130        }
131
132        @Override
133        public String toString() {
134            String string = getClass().getSimpleName() + '(' + row + ", keys=" + keys + ')';
135            if (conditions != null && !conditions.isEmpty()) {
136                string += "(IF=" + conditions + ')';
137            }
138            return string;
139        }
140    }
141
142    /**
143     * The description of a set of rows to create, update or delete.
144     */
145    public static class RowBatch implements Serializable {
146        private static final long serialVersionUID = 1L;
147
148        /**
149         * Creates are done first and are ordered.
150         */
151        public final List<Row> creates;
152
153        /**
154         * Updates.
155         */
156        public final Set<RowUpdate> updates;
157
158        /**
159         * Deletes are done last.
160         */
161        public final Set<RowId> deletes;
162
163        /**
164         * Dependent deletes aren't executed in the database but still trigger invalidations.
165         */
166        public final Set<RowId> deletesDependent;
167
168        public RowBatch() {
169            creates = new LinkedList<Row>();
170            updates = new HashSet<RowUpdate>();
171            deletes = new HashSet<RowId>();
172            deletesDependent = new HashSet<RowId>();
173        }
174
175        public boolean isEmpty() {
176            return creates.isEmpty() && updates.isEmpty() && deletes.isEmpty() && deletesDependent.isEmpty();
177        }
178
179        @Override
180        public String toString() {
181            return getClass().getSimpleName() + "(creates=" + creates + ", updates=" + updates + ", deletes=" + deletes
182                    + ", deletesDependent=" + deletesDependent + ')';
183        }
184    }
185
186    /**
187     * Writes a set of rows. This includes creating, updating and deleting rows.
188     *
189     * @param batch the set of rows and the operations to do on them
190     */
191    void write(RowBatch batch);
192
193    /*
194     * ----- Read -----
195     */
196
197    /**
198     * Gets a row for a {@link SimpleFragment} from the database, given its table name and id. If the row doesn't exist,
199     * {@code null} is returned.
200     *
201     * @param rowId the row id
202     * @return the row, or {@code null}
203     */
204    Row readSimpleRow(RowId rowId);
205
206    /**
207     * Gets the fulltext extracted from the binary fields.
208     *
209     * @since 5.9.3
210     * @param rowId the row id
211     * @return the fulltext string representation or {@code null} if unsupported
212     */
213    Map<String, String> getBinaryFulltext(RowId rowId);
214
215    /**
216     * Gets an array for a {@link CollectionFragment} from the database, given its table name and id. If no rows are
217     * found, an empty array is returned.
218     *
219     * @param rowId the row id
220     * @return the array
221     */
222    Serializable[] readCollectionRowArray(RowId rowId);
223
224    /**
225     * Reads the rows corresponding to a selection.
226     *
227     * @param selType the selection type
228     * @param selId the selection id (parent id for a hierarchy selection)
229     * @param filter the filter value (name for a hierarchy selection)
230     * @param criterion an optional additional criterion depending on the selection type (complex prop flag for a
231     *            hierarchy selection)
232     * @param limitToOne whether to stop after one row retrieved
233     * @return the list of rows
234     */
235    List<Row> readSelectionRows(SelectionType selType, Serializable selId, Serializable filter, Serializable criterion,
236            boolean limitToOne);
237
238    /**
239     * Gets all the selection ids for a given list of values.
240     *
241     * @since 9.2
242     */
243    Set<Serializable> readSelectionsIds(SelectionType selType, List<Serializable> values);
244
245    /*
246     * ----- Copy -----
247     */
248
249    /**
250     * A document id and its primary type and mixin types.
251     */
252    public static final class IdWithTypes implements Serializable {
253        private static final long serialVersionUID = 1L;
254
255        public final Serializable id;
256
257        public final String primaryType;
258
259        public final String[] mixinTypes;
260
261        public IdWithTypes(Serializable id, String primaryType, String[] mixinTypes) {
262            this.id = id;
263            this.primaryType = primaryType;
264            this.mixinTypes = mixinTypes;
265        }
266
267        public IdWithTypes(Node node) {
268            this.id = node.getId();
269            this.primaryType = node.getPrimaryType();
270            this.mixinTypes = node.getMixinTypes();
271        }
272
273        public IdWithTypes(SimpleFragment hierFragment) {
274            this.id = hierFragment.getId();
275            this.primaryType = hierFragment.getString(Model.MAIN_PRIMARY_TYPE_KEY);
276            this.mixinTypes = (String[]) hierFragment.get(Model.MAIN_MIXIN_TYPES_KEY);
277        }
278
279        @Override
280        public String toString() {
281            return getClass().getSimpleName() + "(id=" + id + ",primaryType=" + primaryType + ",mixinTypes="
282                    + Arrays.toString(mixinTypes) + ")";
283        }
284    }
285
286    public static final class CopyResult implements Serializable {
287        private static final long serialVersionUID = 1L;
288
289        /** The id of the root of the copy. */
290        public final Serializable copyId;
291
292        /** The invalidations generated by the copy. */
293        public final Invalidations invalidations;
294
295        /** The ids of newly created proxies. */
296        public final Set<Serializable> proxyIds;
297
298        public CopyResult(Serializable copyId, Invalidations invalidations, Set<Serializable> proxyIds) {
299            this.copyId = copyId;
300            this.invalidations = invalidations;
301            this.proxyIds = proxyIds;
302        }
303    }
304
305    /**
306     * Copies the hierarchy starting from a given row to a new parent with a new name.
307     * <p>
308     * If the new parent is {@code null}, then this is a version creation, which doesn't recurse in regular children.
309     * <p>
310     * If {@code overwriteRow} is passed, the copy is done onto this existing node as its root (version restore) instead
311     * of creating a new node in the parent.
312     *
313     * @param source the id, primary type and mixin types of the row to copy
314     * @param destParentId the new parent id, or {@code null}
315     * @param destName the new name
316     * @param overwriteRow when not {@code null}, the copy is done onto this existing row, and the values are set in
317     *            hierarchy
318     * @return info about the copy
319     */
320    CopyResult copy(IdWithTypes source, Serializable destParentId, String destName, Row overwriteRow);
321
322    /**
323     * A document id, parent id and primary type, along with the version and proxy information (the potentially impacted
324     * selections).
325     * <p>
326     * Used to return info about a descendants tree for removal.
327     */
328    public static final class NodeInfo implements Serializable {
329        private static final long serialVersionUID = 1L;
330
331        public final Serializable id;
332
333        public final Serializable parentId;
334
335        public final String primaryType;
336
337        public final Boolean isProperty;
338
339        public final Serializable versionSeriesId;
340
341        public final Serializable targetId;
342
343        public final boolean isRetentionActive;
344
345        /**
346         * Creates node info for a node that may also be a proxy.
347         */
348        public NodeInfo(Serializable id, Serializable parentId, String primaryType, Boolean isProperty,
349                Serializable versionSeriesId, Serializable targetId, boolean isRetentionActive) {
350            this.id = id;
351            this.parentId = parentId;
352            this.primaryType = primaryType;
353            this.isProperty = isProperty;
354            this.versionSeriesId = versionSeriesId;
355            this.targetId = targetId;
356            this.isRetentionActive = isRetentionActive;
357        }
358
359        /**
360         * Creates node info for a node that may also be a proxy or a version.
361         */
362        public NodeInfo(SimpleFragment hierFragment, SimpleFragment versionFragment, SimpleFragment proxyFragment) {
363            id = hierFragment.getId();
364            parentId = hierFragment.get(Model.HIER_PARENT_KEY);
365            primaryType = hierFragment.getString(Model.MAIN_PRIMARY_TYPE_KEY);
366            isProperty = (Boolean) hierFragment.get(Model.HIER_CHILD_ISPROPERTY_KEY);
367            Serializable ps = proxyFragment == null ? null : proxyFragment.get(Model.PROXY_VERSIONABLE_KEY);
368            if (ps == null) {
369                versionSeriesId = versionFragment == null ? null : versionFragment.get(Model.VERSION_VERSIONABLE_KEY);
370                // may still be null
371                targetId = null; // marks it as a version if versionableId not
372                                 // null
373            } else {
374                versionSeriesId = ps;
375                targetId = proxyFragment.get(Model.PROXY_TARGET_KEY);
376            }
377            isRetentionActive = TRUE.equals(hierFragment.get(Model.MAIN_IS_RETENTION_ACTIVE_KEY));
378        }
379    }
380
381    /**
382     * Gets descendants infos from a given root node. This does not include information about the root node itself.
383     *
384     * @param rootId the root node id from which to get descendants info
385     * @return the list of descendant nodes info
386     * @since 9.2
387     */
388    List<NodeInfo> getDescendantsInfo(Serializable rootId);
389
390    /**
391     * Deletes a hierarchy.
392     *
393     * @param rootId the id of the root node to be deleted with its children
394     * @param nodeInfos the information about all descendants being deleted along the root node
395     * @since 9.2
396     */
397    void remove(Serializable rootId, List<NodeInfo> nodeInfos);
398
399    /**
400     * Processes and returns the invalidations queued for processing by the cache (if any).
401     * <p>
402     * Called pre-transaction by session start or transactionless save;
403     *
404     * @return the invalidations, or {@code null}
405     */
406    Invalidations receiveInvalidations();
407
408    /**
409     * Post-transaction invalidations notification.
410     * <p>
411     * Called post-transaction by session commit/rollback or transactionless save.
412     *
413     * @param invalidations the known invalidations to send to others, or {@code null}
414     */
415    void sendInvalidations(Invalidations invalidations);
416
417    /**
418     * Clears the mapper's cache (if any)
419     * <p>
420     * Called after a rollback, or a manual clear through RepositoryStatus MBean.
421     */
422    void clearCache();
423
424    /**
425     * Evaluate the cached elements size
426     *
427     * @since 5.7.2
428     */
429    long getCacheSize();
430
431    /**
432     * Rollback the XA Resource.
433     * <p>
434     * This is in the {@link RowMapper} interface because on rollback the cache must be invalidated.
435     */
436    void rollback(Xid xid) throws XAException;
437
438}