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 { 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 String toString() { 129 String string = getClass().getSimpleName() + '(' + row + ", keys=" + keys + ')'; 130 if (conditions != null && !conditions.isEmpty()) { 131 string += "(IF=" + conditions + ')'; 132 } 133 return string; 134 } 135 } 136 137 /** 138 * The description of a set of rows to create, update or delete. 139 */ 140 public static class RowBatch implements Serializable { 141 private static final long serialVersionUID = 1L; 142 143 /** 144 * Creates are done first and are ordered. 145 */ 146 public final List<Row> creates; 147 148 /** 149 * Updates. 150 */ 151 public final Set<RowUpdate> updates; 152 153 /** 154 * Deletes are done last. 155 */ 156 public final Set<RowId> deletes; 157 158 /** 159 * Dependent deletes aren't executed in the database but still trigger invalidations. 160 */ 161 public final Set<RowId> deletesDependent; 162 163 public RowBatch() { 164 creates = new LinkedList<Row>(); 165 updates = new HashSet<RowUpdate>(); 166 deletes = new HashSet<RowId>(); 167 deletesDependent = new HashSet<RowId>(); 168 } 169 170 public boolean isEmpty() { 171 return creates.isEmpty() && updates.isEmpty() && deletes.isEmpty() && deletesDependent.isEmpty(); 172 } 173 174 @Override 175 public String toString() { 176 return getClass().getSimpleName() + "(creates=" + creates + ", updates=" + updates + ", deletes=" + deletes 177 + ", deletesDependent=" + deletesDependent + ')'; 178 } 179 } 180 181 /** 182 * Writes a set of rows. This includes creating, updating and deleting rows. 183 * 184 * @param batch the set of rows and the operations to do on them 185 */ 186 void write(RowBatch batch); 187 188 /* 189 * ----- Read ----- 190 */ 191 192 /** 193 * Gets a row for a {@link SimpleFragment} from the database, given its table name and id. If the row doesn't exist, 194 * {@code null} is returned. 195 * 196 * @param rowId the row id 197 * @return the row, or {@code null} 198 */ 199 Row readSimpleRow(RowId rowId); 200 201 /** 202 * Gets the fulltext extracted from the binary fields. 203 * 204 * @since 5.9.3 205 * @param rowId the row id 206 * @return the fulltext string representation or {@code null} if unsupported 207 */ 208 Map<String, String> getBinaryFulltext(RowId rowId); 209 210 /** 211 * Gets an array for a {@link CollectionFragment} from the database, given its table name and id. If no rows are 212 * found, an empty array is returned. 213 * 214 * @param rowId the row id 215 * @return the array 216 */ 217 Serializable[] readCollectionRowArray(RowId rowId); 218 219 /** 220 * Reads the rows corresponding to a selection. 221 * 222 * @param selType the selection type 223 * @param selId the selection id (parent id for a hierarchy selection) 224 * @param filter the filter value (name for a hierarchy selection) 225 * @param criterion an optional additional criterion depending on the selection type (complex prop flag for a 226 * hierarchy selection) 227 * @param limitToOne whether to stop after one row retrieved 228 * @return the list of rows 229 */ 230 List<Row> readSelectionRows(SelectionType selType, Serializable selId, Serializable filter, Serializable criterion, 231 boolean limitToOne); 232 233 /** 234 * Gets all the selection ids for a given list of values. 235 * 236 * @since 9.2 237 */ 238 Set<Serializable> readSelectionsIds(SelectionType selType, List<Serializable> values); 239 240 /* 241 * ----- Copy ----- 242 */ 243 244 /** 245 * A document id and its primary type and mixin types. 246 */ 247 public static final class IdWithTypes implements Serializable { 248 private static final long serialVersionUID = 1L; 249 250 public final Serializable id; 251 252 public final String primaryType; 253 254 public final String[] mixinTypes; 255 256 public IdWithTypes(Serializable id, String primaryType, String[] mixinTypes) { 257 this.id = id; 258 this.primaryType = primaryType; 259 this.mixinTypes = mixinTypes; 260 } 261 262 public IdWithTypes(Node node) { 263 this.id = node.getId(); 264 this.primaryType = node.getPrimaryType(); 265 this.mixinTypes = node.getMixinTypes(); 266 } 267 268 public IdWithTypes(SimpleFragment hierFragment) { 269 this.id = hierFragment.getId(); 270 this.primaryType = hierFragment.getString(Model.MAIN_PRIMARY_TYPE_KEY); 271 this.mixinTypes = (String[]) hierFragment.get(Model.MAIN_MIXIN_TYPES_KEY); 272 } 273 274 @Override 275 public String toString() { 276 return getClass().getSimpleName() + "(id=" + id + ",primaryType=" + primaryType + ",mixinTypes=" 277 + Arrays.toString(mixinTypes) + ")"; 278 } 279 } 280 281 public static final class CopyResult implements Serializable { 282 private static final long serialVersionUID = 1L; 283 284 /** The id of the root of the copy. */ 285 public final Serializable copyId; 286 287 /** The invalidations generated by the copy. */ 288 public final Invalidations invalidations; 289 290 /** The ids of newly created proxies. */ 291 public final Set<Serializable> proxyIds; 292 293 public CopyResult(Serializable copyId, Invalidations invalidations, Set<Serializable> proxyIds) { 294 this.copyId = copyId; 295 this.invalidations = invalidations; 296 this.proxyIds = proxyIds; 297 } 298 } 299 300 /** 301 * Copies the hierarchy starting from a given row to a new parent with a new name. 302 * <p> 303 * If the new parent is {@code null}, then this is a version creation, which doesn't recurse in regular children. 304 * <p> 305 * If {@code overwriteRow} is passed, the copy is done onto this existing node as its root (version restore) instead 306 * of creating a new node in the parent. 307 * 308 * @param source the id, primary type and mixin types of the row to copy 309 * @param destParentId the new parent id, or {@code null} 310 * @param destName the new name 311 * @param overwriteRow when not {@code null}, the copy is done onto this existing row, and the values are set in 312 * hierarchy 313 * @return info about the copy 314 */ 315 CopyResult copy(IdWithTypes source, Serializable destParentId, String destName, Row overwriteRow); 316 317 /** 318 * A document id, parent id and primary type, along with the version and proxy information (the potentially impacted 319 * selections). 320 * <p> 321 * Used to return info about a descendants tree for removal. 322 */ 323 public static final class NodeInfo implements Serializable { 324 private static final long serialVersionUID = 1L; 325 326 public final Serializable id; 327 328 public final Serializable parentId; 329 330 public final String primaryType; 331 332 public final Boolean isProperty; 333 334 public final Serializable versionSeriesId; 335 336 public final Serializable targetId; 337 338 public final boolean isRetentionActive; 339 340 /** 341 * Creates node info for a node that may also be a proxy. 342 */ 343 public NodeInfo(Serializable id, Serializable parentId, String primaryType, Boolean isProperty, 344 Serializable versionSeriesId, Serializable targetId, boolean isRetentionActive) { 345 this.id = id; 346 this.parentId = parentId; 347 this.primaryType = primaryType; 348 this.isProperty = isProperty; 349 this.versionSeriesId = versionSeriesId; 350 this.targetId = targetId; 351 this.isRetentionActive = isRetentionActive; 352 } 353 354 /** 355 * Creates node info for a node that may also be a proxy or a version. 356 */ 357 public NodeInfo(SimpleFragment hierFragment, SimpleFragment versionFragment, SimpleFragment proxyFragment) { 358 id = hierFragment.getId(); 359 parentId = hierFragment.get(Model.HIER_PARENT_KEY); 360 primaryType = hierFragment.getString(Model.MAIN_PRIMARY_TYPE_KEY); 361 isProperty = (Boolean) hierFragment.get(Model.HIER_CHILD_ISPROPERTY_KEY); 362 Serializable ps = proxyFragment == null ? null : proxyFragment.get(Model.PROXY_VERSIONABLE_KEY); 363 if (ps == null) { 364 versionSeriesId = versionFragment == null ? null : versionFragment.get(Model.VERSION_VERSIONABLE_KEY); 365 // may still be null 366 targetId = null; // marks it as a version if versionableId not 367 // null 368 } else { 369 versionSeriesId = ps; 370 targetId = proxyFragment.get(Model.PROXY_TARGET_KEY); 371 } 372 isRetentionActive = TRUE.equals(hierFragment.get(Model.MAIN_IS_RETENTION_ACTIVE_KEY)); 373 } 374 } 375 376 /** 377 * Gets descendants infos from a given root node. This does not include information about the root node itself. 378 * 379 * @param rootId the root node id from which to get descendants info 380 * @return the list of descendant nodes info 381 * @since 9.2 382 */ 383 List<NodeInfo> getDescendantsInfo(Serializable rootId); 384 385 /** 386 * Deletes a hierarchy. 387 * 388 * @param rootId the id of the root node to be deleted with its children 389 * @param nodeInfos the information about all descendants being deleted along the root node 390 * @since 9.2 391 */ 392 void remove(Serializable rootId, List<NodeInfo> nodeInfos); 393 394 /** 395 * Processes and returns the invalidations queued for processing by the cache (if any). 396 * <p> 397 * Called pre-transaction by session start or transactionless save; 398 * 399 * @return the invalidations, or {@code null} 400 */ 401 Invalidations receiveInvalidations(); 402 403 /** 404 * Post-transaction invalidations notification. 405 * <p> 406 * Called post-transaction by session commit/rollback or transactionless save. 407 * 408 * @param invalidations the known invalidations to send to others, or {@code null} 409 */ 410 void sendInvalidations(Invalidations invalidations); 411 412 /** 413 * Clears the mapper's cache (if any) 414 * <p> 415 * Called after a rollback, or a manual clear through RepositoryStatus MBean. 416 */ 417 void clearCache(); 418 419 /** 420 * Evaluate the cached elements size 421 * 422 * @since 5.7.2 423 */ 424 long getCacheSize(); 425 426 /** 427 * Rollback the XA Resource. 428 * <p> 429 * This is in the {@link RowMapper} interface because on rollback the cache must be invalidated. 430 */ 431 void rollback(Xid xid) throws XAException; 432 433}