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