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