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