/* * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Florent Guillaume */ package org.nuxeo.ecm.core.storage.sql; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.transaction.xa.XAException; import javax.transaction.xa.Xid; /** * A {@link RowMapper} maps {@link Row}s to and from the database. * <p> * These are the operations that can benefit from a cache. * * @see SoftRefCachingRowMapper */ public interface RowMapper { /** * Computes a new unique id. * * @return a new unique id */ Serializable generateNewId(); /* * ----- Batch ----- */ /** * Reads a set of rows for the given {@link RowId}s. * <p> * For each requested row, either a {@link Row} is found and returned, or a {@link RowId} (not implementing * {@link Row}) is returned to signify an absent row. * * @param rowIds the row ids (including their table name) * @param cacheOnly if {@code true}, only hit memory * @return the collection of {@link Row}s (or {@link RowId}s if the row was absent from the database). Order is not * the same as the input {@code rowIds} */ List<? extends RowId> read(Collection<RowId> rowIds, boolean cacheOnly); /** * A {@link Row} and a list of its keys that have to be updated. */ public static final class RowUpdate implements Serializable { private static final long serialVersionUID = 1L; public final Row row; // used for simple fragments public final Collection<String> keys; // used for collection fragment right push, the pos at which to start to insert // if -1 then a full update must be done public final int pos; // conditions to add to get a conditional update, for change token public Map<String, Serializable> conditions; /** Constructor for simple fragment update. */ public RowUpdate(Row row, Collection<String> keys) { this.row = row; this.keys = keys; pos = -1; } /** Constructor for collection fragment full update. */ public RowUpdate(Row row) { this(row, -1); } /** Constructor for collection fragment right push update. */ public RowUpdate(Row row, int pos) { this.row = row; keys = null; this.pos = pos; } public void setConditions(Map<String, Serializable> conditions) { this.conditions = conditions; } @Override public int hashCode() { return row.hashCode(); } @Override public boolean equals(Object other) { if (other instanceof RowUpdate) { return equal((RowUpdate) other); } return false; } private boolean equal(RowUpdate other) { return other.row.equals(row); } @Override public String toString() { String string = getClass().getSimpleName() + '(' + row + ", keys=" + keys + ')'; if (conditions != null && !conditions.isEmpty()) { string += "(IF=" + conditions + ')'; } return string; } } /** * The description of a set of rows to create, update or delete. */ public static class RowBatch implements Serializable { private static final long serialVersionUID = 1L; /** * Creates are done first and are ordered. */ public final List<Row> creates; /** * Updates. */ public final Set<RowUpdate> updates; /** * Deletes are done last. */ public final Set<RowId> deletes; /** * Dependent deletes aren't executed in the database but still trigger invalidations. */ public final Set<RowId> deletesDependent; public RowBatch() { creates = new LinkedList<Row>(); updates = new HashSet<RowUpdate>(); deletes = new HashSet<RowId>(); deletesDependent = new HashSet<RowId>(); } public boolean isEmpty() { return creates.isEmpty() && updates.isEmpty() && deletes.isEmpty() && deletesDependent.isEmpty(); } @Override public String toString() { return getClass().getSimpleName() + "(creates=" + creates + ", updates=" + updates + ", deletes=" + deletes + ", deletesDependent=" + deletesDependent + ')'; } } /** * Writes a set of rows. This includes creating, updating and deleting rows. * * @param batch the set of rows and the operations to do on them */ void write(RowBatch batch); /* * ----- Read ----- */ /** * Gets a row for a {@link SimpleFragment} from the database, given its table name and id. If the row doesn't exist, * {@code null} is returned. * * @param rowId the row id * @return the row, or {@code null} */ Row readSimpleRow(RowId rowId); /** * Gets the fulltext extracted from the binary fields. * * @since 5.9.3 * @param rowId the row id * @return the fulltext string representation or {@code null} if unsupported */ Map<String, String> getBinaryFulltext(RowId rowId); /** * Gets an array for a {@link CollectionFragment} from the database, given its table name and id. If no rows are * found, an empty array is returned. * * @param rowId the row id * @return the array */ Serializable[] readCollectionRowArray(RowId rowId); /** * Reads the rows corresponding to a selection. * * @param selType the selection type * @param selId the selection id (parent id for a hierarchy selection) * @param filter the filter value (name for a hierarchy selection) * @param criterion an optional additional criterion depending on the selection type (complex prop flag for a * hierarchy selection) * @param limitToOne whether to stop after one row retrieved * @return the list of rows */ List<Row> readSelectionRows(SelectionType selType, Serializable selId, Serializable filter, Serializable criterion, boolean limitToOne); /* * ----- Copy ----- */ /** * A document id and its primary type and mixin types. */ public static final class IdWithTypes implements Serializable { private static final long serialVersionUID = 1L; public final Serializable id; public final String primaryType; public final String[] mixinTypes; public IdWithTypes(Serializable id, String primaryType, String[] mixinTypes) { this.id = id; this.primaryType = primaryType; this.mixinTypes = mixinTypes; } public IdWithTypes(Node node) { this.id = node.getId(); this.primaryType = node.getPrimaryType(); this.mixinTypes = node.getMixinTypes(); } public IdWithTypes(SimpleFragment hierFragment) { this.id = hierFragment.getId(); this.primaryType = hierFragment.getString(Model.MAIN_PRIMARY_TYPE_KEY); this.mixinTypes = (String[]) hierFragment.get(Model.MAIN_MIXIN_TYPES_KEY); } @Override public String toString() { return getClass().getSimpleName() + "(id=" + id + ",primaryType=" + primaryType + ",mixinTypes=" + Arrays.toString(mixinTypes) + ")"; } } public static final class CopyResult implements Serializable { private static final long serialVersionUID = 1L; /** The id of the root of the copy. */ public final Serializable copyId; /** The invalidations generated by the copy. */ public final Invalidations invalidations; /** The ids of newly created proxies. */ public final Set<Serializable> proxyIds; public CopyResult(Serializable copyId, Invalidations invalidations, Set<Serializable> proxyIds) { this.copyId = copyId; this.invalidations = invalidations; this.proxyIds = proxyIds; } } /** * Copies the hierarchy starting from a given row to a new parent with a new name. * <p> * If the new parent is {@code null}, then this is a version creation, which doesn't recurse in regular children. * <p> * If {@code overwriteRow} is passed, the copy is done onto this existing node as its root (version restore) instead * of creating a new node in the parent. * * @param source the id, primary type and mixin types of the row to copy * @param destParentId the new parent id, or {@code null} * @param destName the new name * @param overwriteRow when not {@code null}, the copy is done onto this existing row, and the values are set in * hierarchy * @return info about the copy */ CopyResult copy(IdWithTypes source, Serializable destParentId, String destName, Row overwriteRow); /** * A document id, parent id and primary type, along with the version and proxy information (the potentially impacted * selections). * <p> * Used to return info about a descendants tree for removal. */ public static final class NodeInfo implements Serializable { private static final long serialVersionUID = 1L; public final Serializable id; public final Serializable parentId; public final String primaryType; public final Boolean isProperty; public final Serializable versionSeriesId; public final Serializable targetId; /** * Creates node info for a node that may also be a proxy. */ public NodeInfo(Serializable id, Serializable parentId, String primaryType, Boolean isProperty, Serializable versionSeriesId, Serializable targetId) { this.id = id; this.parentId = parentId; this.primaryType = primaryType; this.isProperty = isProperty; this.versionSeriesId = versionSeriesId; this.targetId = targetId; } /** * Creates node info for a node that may also be a proxy or a version. */ public NodeInfo(SimpleFragment hierFragment, SimpleFragment versionFragment, SimpleFragment proxyFragment) { id = hierFragment.getId(); parentId = hierFragment.get(Model.HIER_PARENT_KEY); primaryType = hierFragment.getString(Model.MAIN_PRIMARY_TYPE_KEY); isProperty = (Boolean) hierFragment.get(Model.HIER_CHILD_ISPROPERTY_KEY); Serializable ps = proxyFragment == null ? null : proxyFragment.get(Model.PROXY_VERSIONABLE_KEY); if (ps == null) { versionSeriesId = versionFragment == null ? null : versionFragment.get(Model.VERSION_VERSIONABLE_KEY); // may still be null targetId = null; // marks it as a version if versionableId not // null } else { versionSeriesId = ps; targetId = proxyFragment.get(Model.PROXY_TARGET_KEY); } } } /** * Deletes a hierarchy and returns information to generate invalidations. * * @param rootInfo info about the root to be deleted with its children (root id, and the rest is for invalidations) * @return info about the descendants removed (including the root) */ List<NodeInfo> remove(NodeInfo rootInfo); /** * Processes and returns the invalidations queued for processing by the cache (if any). * <p> * Called pre-transaction by session start or transactionless save; * * @return the invalidations, or {@code null} */ Invalidations receiveInvalidations(); /** * Post-transaction invalidations notification. * <p> * Called post-transaction by session commit/rollback or transactionless save. * * @param invalidations the known invalidations to send to others, or {@code null} */ void sendInvalidations(Invalidations invalidations); /** * Clears the mapper's cache (if any) * <p> * Called after a rollback, or a manual clear through RepositoryStatus MBean. */ void clearCache(); /** * Evaluate the cached elements size * * @since 5.7.2 */ long getCacheSize(); /** * Rollback the XA Resource. * <p> * This is in the {@link RowMapper} interface because on rollback the cache must be invalidated. */ void rollback(Xid xid) throws XAException; }