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