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