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}