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