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 java.io.Serializable; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.HashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import javax.transaction.xa.XAException; 032import javax.transaction.xa.Xid; 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 { 071 private static final long serialVersionUID = 1L; 072 073 public final Row row; 074 075 public final Collection<String> keys; 076 077 public RowUpdate(Row row, Collection<String> keys) { 078 this.row = row; 079 this.keys = keys; 080 } 081 082 @Override 083 public int hashCode() { 084 return row.hashCode(); 085 } 086 087 @Override 088 public boolean equals(Object other) { 089 if (other instanceof RowUpdate) { 090 return equal((RowUpdate) other); 091 } 092 return false; 093 } 094 095 private boolean equal(RowUpdate other) { 096 return other.row.equals(row); 097 } 098 099 @Override 100 public String toString() { 101 return getClass().getSimpleName() + '(' + row + ", keys=" + keys + ')'; 102 } 103 } 104 105 /** 106 * The description of a set of rows to create, update or delete. 107 */ 108 public static class RowBatch implements Serializable { 109 private static final long serialVersionUID = 1L; 110 111 /** 112 * Creates are done first and are ordered. 113 */ 114 public final List<Row> creates; 115 116 /** 117 * Updates. 118 */ 119 public final Set<RowUpdate> updates; 120 121 /** 122 * Deletes are done last. 123 */ 124 public final Set<RowId> deletes; 125 126 /** 127 * Dependent deletes aren't executed in the database but still trigger invalidations. 128 */ 129 public final Set<RowId> deletesDependent; 130 131 public RowBatch() { 132 creates = new LinkedList<Row>(); 133 updates = new HashSet<RowUpdate>(); 134 deletes = new HashSet<RowId>(); 135 deletesDependent = new HashSet<RowId>(); 136 } 137 138 public boolean isEmpty() { 139 return creates.isEmpty() && updates.isEmpty() && deletes.isEmpty() && deletesDependent.isEmpty(); 140 } 141 142 @Override 143 public String toString() { 144 return getClass().getSimpleName() + "(creates=" + creates + ", updates=" + updates + ", deletes=" + deletes 145 + ", deletesDependent=" + deletesDependent + ')'; 146 } 147 } 148 149 /** 150 * Writes a set of rows. This includes creating, updating and deleting rows. 151 * 152 * @param batch the set of rows and the operations to do on them 153 */ 154 void write(RowBatch batch); 155 156 /* 157 * ----- Read ----- 158 */ 159 160 /** 161 * Gets a row for a {@link SimpleFragment} from the database, given its table name and id. If the row doesn't exist, 162 * {@code null} is returned. 163 * 164 * @param rowId the row id 165 * @return the row, or {@code null} 166 */ 167 Row readSimpleRow(RowId rowId); 168 169 /** 170 * Gets the fulltext extracted from the binary fields. 171 * 172 * @since 5.9.3 173 * @param rowId the row id 174 * @return the fulltext string representation or {@code null} if unsupported 175 */ 176 Map<String, String> getBinaryFulltext(RowId rowId); 177 178 /** 179 * Gets an array for a {@link CollectionFragment} from the database, given its table name and id. If no rows are 180 * found, an empty array is returned. 181 * 182 * @param rowId the row id 183 * @return the array 184 */ 185 Serializable[] readCollectionRowArray(RowId rowId); 186 187 /** 188 * Reads the rows corresponding to a selection. 189 * 190 * @param selType the selection type 191 * @param selId the selection id (parent id for a hierarchy selection) 192 * @param filter the filter value (name for a hierarchy selection) 193 * @param criterion an optional additional criterion depending on the selection type (complex prop flag for a 194 * hierarchy selection) 195 * @param limitToOne whether to stop after one row retrieved 196 * @return the list of rows 197 */ 198 List<Row> readSelectionRows(SelectionType selType, Serializable selId, Serializable filter, Serializable criterion, 199 boolean limitToOne); 200 201 /* 202 * ----- Copy ----- 203 */ 204 205 /** 206 * A document id and its primary type and mixin types. 207 */ 208 public static final class IdWithTypes implements Serializable { 209 private static final long serialVersionUID = 1L; 210 211 public final Serializable id; 212 213 public final String primaryType; 214 215 public final String[] mixinTypes; 216 217 public IdWithTypes(Serializable id, String primaryType, String[] mixinTypes) { 218 this.id = id; 219 this.primaryType = primaryType; 220 this.mixinTypes = mixinTypes; 221 } 222 223 public IdWithTypes(Node node) { 224 this.id = node.getId(); 225 this.primaryType = node.getPrimaryType(); 226 this.mixinTypes = node.getMixinTypes(); 227 } 228 229 public IdWithTypes(SimpleFragment hierFragment) { 230 this.id = hierFragment.getId(); 231 this.primaryType = hierFragment.getString(Model.MAIN_PRIMARY_TYPE_KEY); 232 this.mixinTypes = (String[]) hierFragment.get(Model.MAIN_MIXIN_TYPES_KEY); 233 } 234 235 @Override 236 public String toString() { 237 return getClass().getSimpleName() + "(id=" + id + ",primaryType=" + primaryType + ",mixinTypes=" 238 + Arrays.toString(mixinTypes) + ")"; 239 } 240 } 241 242 public static final class CopyResult implements Serializable { 243 private static final long serialVersionUID = 1L; 244 245 /** The id of the root of the copy. */ 246 public final Serializable copyId; 247 248 /** The invalidations generated by the copy. */ 249 public final Invalidations invalidations; 250 251 /** The ids of newly created proxies. */ 252 public final Set<Serializable> proxyIds; 253 254 public CopyResult(Serializable copyId, Invalidations invalidations, Set<Serializable> proxyIds) { 255 this.copyId = copyId; 256 this.invalidations = invalidations; 257 this.proxyIds = proxyIds; 258 } 259 } 260 261 /** 262 * Copies the hierarchy starting from a given row to a new parent with a new name. 263 * <p> 264 * If the new parent is {@code null}, then this is a version creation, which doesn't recurse in regular children. 265 * <p> 266 * If {@code overwriteRow} is passed, the copy is done onto this existing node as its root (version restore) instead 267 * of creating a new node in the parent. 268 * 269 * @param source the id, primary type and mixin types of the row to copy 270 * @param destParentId the new parent id, or {@code null} 271 * @param destName the new name 272 * @param overwriteRow when not {@code null}, the copy is done onto this existing row, and the values are set in 273 * hierarchy 274 * @return info about the copy 275 */ 276 CopyResult copy(IdWithTypes source, Serializable destParentId, String destName, Row overwriteRow); 277 278 /** 279 * A document id, parent id and primary type, along with the version and proxy information (the potentially impacted 280 * selections). 281 * <p> 282 * Used to return info about a descendants tree for removal. 283 */ 284 public static final class NodeInfo implements Serializable { 285 private static final long serialVersionUID = 1L; 286 287 public final Serializable id; 288 289 public final Serializable parentId; 290 291 public final String primaryType; 292 293 public final Boolean isProperty; 294 295 public final Serializable versionSeriesId; 296 297 public final Serializable targetId; 298 299 /** 300 * Creates node info for a node that may also be a proxy. 301 */ 302 public NodeInfo(Serializable id, Serializable parentId, String primaryType, Boolean isProperty, 303 Serializable versionSeriesId, Serializable targetId) { 304 this.id = id; 305 this.parentId = parentId; 306 this.primaryType = primaryType; 307 this.isProperty = isProperty; 308 this.versionSeriesId = versionSeriesId; 309 this.targetId = targetId; 310 } 311 312 /** 313 * Creates node info for a node that may also be a proxy or a version. 314 */ 315 public NodeInfo(SimpleFragment hierFragment, SimpleFragment versionFragment, SimpleFragment proxyFragment) { 316 id = hierFragment.getId(); 317 parentId = hierFragment.get(Model.HIER_PARENT_KEY); 318 primaryType = hierFragment.getString(Model.MAIN_PRIMARY_TYPE_KEY); 319 isProperty = (Boolean) hierFragment.get(Model.HIER_CHILD_ISPROPERTY_KEY); 320 Serializable ps = proxyFragment == null ? null : proxyFragment.get(Model.PROXY_VERSIONABLE_KEY); 321 if (ps == null) { 322 versionSeriesId = versionFragment == null ? null : versionFragment.get(Model.VERSION_VERSIONABLE_KEY); 323 // may still be null 324 targetId = null; // marks it as a version if versionableId not 325 // null 326 } else { 327 versionSeriesId = ps; 328 targetId = proxyFragment.get(Model.PROXY_TARGET_KEY); 329 } 330 } 331 } 332 333 /** 334 * Deletes a hierarchy and returns information to generate invalidations. 335 * 336 * @param rootInfo info about the root to be deleted with its children (root id, and the rest is for invalidations) 337 * @return info about the descendants removed (including the root) 338 */ 339 List<NodeInfo> remove(NodeInfo rootInfo); 340 341 /** 342 * Processes and returns the invalidations queued for processing by the cache (if any). 343 * <p> 344 * Called pre-transaction by session start or transactionless save; 345 * 346 * @return the invalidations, or {@code null} 347 */ 348 Invalidations receiveInvalidations(); 349 350 /** 351 * Post-transaction invalidations notification. 352 * <p> 353 * Called post-transaction by session commit/rollback or transactionless save. 354 * 355 * @param invalidations the known invalidations to send to others, or {@code null} 356 */ 357 void sendInvalidations(Invalidations invalidations); 358 359 /** 360 * Clears the mapper's cache (if any) 361 * <p> 362 * Called after a rollback, or a manual clear through RepositoryStatus MBean. 363 */ 364 void clearCache(); 365 366 /** 367 * Evaluate the cached elements size 368 * 369 * @since 5.7.2 370 */ 371 long getCacheSize(); 372 373 /** 374 * Rollback the XA Resource. 375 * <p> 376 * This is in the {@link RowMapper} interface because on rollback the cache must be invalidated. 377 */ 378 void rollback(Xid xid) throws XAException; 379 380}