001/* 002 * (C) Copyright 2014 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 */ 019package org.nuxeo.ecm.core.storage.dbs; 020 021import static java.lang.Boolean.TRUE; 022import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ACE_BEGIN; 023import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ACE_CREATOR; 024import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ACE_END; 025import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ACE_GRANT; 026import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ACE_PERMISSION; 027import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ACE_STATUS; 028import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ACE_USER; 029import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ACL; 030import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ACL_NAME; 031import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ACP; 032import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ANCESTOR_IDS; 033import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_BASE_VERSION_ID; 034import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_FULLTEXT_BINARY; 035import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_FULLTEXT_JOBID; 036import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_FULLTEXT_SCORE; 037import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_FULLTEXT_SIMPLE; 038import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_ID; 039import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_IS_CHECKED_IN; 040import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_IS_LATEST_MAJOR_VERSION; 041import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_IS_LATEST_VERSION; 042import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_IS_PROXY; 043import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_IS_VERSION; 044import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_LIFECYCLE_POLICY; 045import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_LIFECYCLE_STATE; 046import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_LOCK_CREATED; 047import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_LOCK_OWNER; 048import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_MAJOR_VERSION; 049import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_MINOR_VERSION; 050import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_MIXIN_TYPES; 051import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_NAME; 052import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_PARENT_ID; 053import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_PATH_INTERNAL; 054import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_POS; 055import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_PRIMARY_TYPE; 056import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_PROXY_IDS; 057import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_PROXY_TARGET_ID; 058import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_PROXY_VERSION_SERIES_ID; 059import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_READ_ACL; 060import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_VERSION_CREATED; 061import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_VERSION_DESCRIPTION; 062import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_VERSION_LABEL; 063import static org.nuxeo.ecm.core.storage.dbs.DBSDocument.KEY_VERSION_SERIES_ID; 064 065import java.io.Serializable; 066import java.text.DateFormat; 067import java.text.Normalizer; 068import java.text.ParseException; 069import java.util.ArrayList; 070import java.util.Calendar; 071import java.util.Collections; 072import java.util.Comparator; 073import java.util.GregorianCalendar; 074import java.util.HashMap; 075import java.util.HashSet; 076import java.util.Iterator; 077import java.util.LinkedList; 078import java.util.List; 079import java.util.Map; 080import java.util.Map.Entry; 081import java.util.NoSuchElementException; 082import java.util.Set; 083import java.util.regex.Matcher; 084import java.util.regex.Pattern; 085 086import org.apache.commons.lang.ObjectUtils; 087import org.apache.commons.lang.StringUtils; 088import org.nuxeo.ecm.core.api.CoreSession; 089import org.nuxeo.ecm.core.api.DocumentExistsException; 090import org.nuxeo.ecm.core.api.DocumentNotFoundException; 091import org.nuxeo.ecm.core.api.IterableQueryResult; 092import org.nuxeo.ecm.core.api.NuxeoException; 093import org.nuxeo.ecm.core.api.PartialList; 094import org.nuxeo.ecm.core.api.VersionModel; 095import org.nuxeo.ecm.core.api.security.ACE; 096import org.nuxeo.ecm.core.api.security.ACL; 097import org.nuxeo.ecm.core.api.security.ACP; 098import org.nuxeo.ecm.core.api.security.Access; 099import org.nuxeo.ecm.core.api.security.SecurityConstants; 100import org.nuxeo.ecm.core.api.security.impl.ACLImpl; 101import org.nuxeo.ecm.core.api.security.impl.ACPImpl; 102import org.nuxeo.ecm.core.blob.BlobManager; 103import org.nuxeo.ecm.core.model.Document; 104import org.nuxeo.ecm.core.model.LockManager; 105import org.nuxeo.ecm.core.model.Session; 106import org.nuxeo.ecm.core.query.QueryFilter; 107import org.nuxeo.ecm.core.query.QueryParseException; 108import org.nuxeo.ecm.core.query.sql.NXQL; 109import org.nuxeo.ecm.core.query.sql.SQLQueryParser; 110import org.nuxeo.ecm.core.query.sql.model.MultiExpression; 111import org.nuxeo.ecm.core.query.sql.model.OrderByClause; 112import org.nuxeo.ecm.core.query.sql.model.OrderByExpr; 113import org.nuxeo.ecm.core.query.sql.model.OrderByList; 114import org.nuxeo.ecm.core.query.sql.model.Reference; 115import org.nuxeo.ecm.core.query.sql.model.SQLQuery; 116import org.nuxeo.ecm.core.query.sql.model.SelectClause; 117import org.nuxeo.ecm.core.schema.DocumentType; 118import org.nuxeo.ecm.core.schema.FacetNames; 119import org.nuxeo.ecm.core.schema.SchemaManager; 120import org.nuxeo.ecm.core.schema.types.ListTypeImpl; 121import org.nuxeo.ecm.core.schema.types.Type; 122import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; 123import org.nuxeo.ecm.core.schema.types.primitives.DateType; 124import org.nuxeo.ecm.core.schema.types.primitives.StringType; 125import org.nuxeo.ecm.core.storage.ExpressionEvaluator; 126import org.nuxeo.ecm.core.storage.FulltextConfiguration; 127import org.nuxeo.ecm.core.storage.QueryOptimizer; 128import org.nuxeo.ecm.core.storage.State; 129import org.nuxeo.ecm.core.storage.StateHelper; 130import org.nuxeo.runtime.api.Framework; 131import org.nuxeo.runtime.transaction.TransactionHelper; 132 133/** 134 * Implementation of a {@link Session} for Document-Based Storage. 135 * 136 * @since 5.9.4 137 */ 138public class DBSSession implements Session { 139 140 protected final DBSRepository repository; 141 142 protected final DBSTransactionState transaction; 143 144 protected final boolean fulltextSearchDisabled; 145 146 protected boolean closed; 147 148 public DBSSession(DBSRepository repository) { 149 this.repository = repository; 150 transaction = new DBSTransactionState(repository, this); 151 FulltextConfiguration fulltextConfiguration = repository.getFulltextConfiguration(); 152 fulltextSearchDisabled = fulltextConfiguration == null || fulltextConfiguration.fulltextSearchDisabled; 153 } 154 155 @Override 156 public String getRepositoryName() { 157 return repository.getName(); 158 } 159 160 @Override 161 public void close() { 162 closed = true; 163 } 164 165 @Override 166 public boolean isLive() { 167 return !closed; 168 } 169 170 @Override 171 public void save() { 172 transaction.save(); 173 if (!TransactionHelper.isTransactionActiveOrMarkedRollback()) { 174 transaction.commit(); 175 } 176 } 177 178 public void begin() { 179 transaction.begin(); 180 } 181 182 public void commit() { 183 transaction.commit(); 184 } 185 186 public void rollback() { 187 transaction.rollback(); 188 } 189 190 @Override 191 public boolean isStateSharedByAllThreadSessions() { 192 return false; 193 } 194 195 protected BlobManager getBlobManager() { 196 return repository.getBlobManager(); 197 } 198 199 protected String getRootId() { 200 return repository.getRootId(); 201 } 202 203 /* 204 * Normalize using NFC to avoid decomposed characters (like 'e' + COMBINING ACUTE ACCENT instead of LATIN SMALL 205 * LETTER E WITH ACUTE). NFKC (normalization using compatibility decomposition) is not used, because compatibility 206 * decomposition turns some characters (LATIN SMALL LIGATURE FFI, TRADE MARK SIGN, FULLWIDTH SOLIDUS) into a series 207 * of characters ('f'+'f'+'i', 'T'+'M', '/') that cannot be re-composed into the original, and therefore loses 208 * information. 209 */ 210 protected String normalize(String path) { 211 return Normalizer.normalize(path, Normalizer.Form.NFC); 212 } 213 214 @Override 215 public Document resolvePath(String path) { 216 // TODO move checks and normalize higher in call stack 217 if (path == null) { 218 throw new DocumentNotFoundException("Null path"); 219 } 220 int len = path.length(); 221 if (len == 0) { 222 throw new DocumentNotFoundException("Empty path"); 223 } 224 if (path.charAt(0) != '/') { 225 throw new DocumentNotFoundException("Relative path: " + path); 226 } 227 if (len > 1 && path.charAt(len - 1) == '/') { 228 // remove final slash 229 path = path.substring(0, len - 1); 230 len--; 231 } 232 path = normalize(path); 233 234 if (len == 1) { 235 return getRootDocument(); 236 } 237 DBSDocumentState docState = null; 238 String parentId = getRootId(); 239 String[] names = path.split("/", -1); 240 for (int i = 1; i < names.length; i++) { 241 String name = names[i]; 242 if (name.length() == 0) { 243 throw new DocumentNotFoundException("Path with empty component: " + path); 244 } 245 docState = transaction.getChildState(parentId, name); 246 if (docState == null) { 247 throw new DocumentNotFoundException(path); 248 } 249 parentId = docState.getId(); 250 } 251 return getDocument(docState); 252 } 253 254 protected String getDocumentIdByPath(String path) { 255 // TODO move checks and normalize higher in call stack 256 if (path == null) { 257 throw new DocumentNotFoundException("Null path"); 258 } 259 int len = path.length(); 260 if (len == 0) { 261 throw new DocumentNotFoundException("Empty path"); 262 } 263 if (path.charAt(0) != '/') { 264 throw new DocumentNotFoundException("Relative path: " + path); 265 } 266 if (len > 1 && path.charAt(len - 1) == '/') { 267 // remove final slash 268 path = path.substring(0, len - 1); 269 len--; 270 } 271 path = normalize(path); 272 273 if (len == 1) { 274 return getRootId(); 275 } 276 DBSDocumentState docState = null; 277 String parentId = getRootId(); 278 String[] names = path.split("/", -1); 279 for (int i = 1; i < names.length; i++) { 280 String name = names[i]; 281 if (name.length() == 0) { 282 throw new DocumentNotFoundException("Path with empty component: " + path); 283 } 284 // TODO XXX add getChildId method 285 docState = transaction.getChildState(parentId, name); 286 if (docState == null) { 287 return null; 288 } 289 parentId = docState.getId(); 290 } 291 return docState.getId(); 292 } 293 294 protected Document getChild(String parentId, String name) { 295 name = normalize(name); 296 DBSDocumentState docState = transaction.getChildState(parentId, name); 297 DBSDocument doc = getDocument(docState); 298 if (doc == null) { 299 throw new DocumentNotFoundException(name); 300 } 301 return doc; 302 } 303 304 protected List<Document> getChildren(String parentId) { 305 List<DBSDocumentState> docStates = transaction.getChildrenStates(parentId); 306 if (isOrderable(parentId)) { 307 // sort children in order 308 Collections.sort(docStates, POS_COMPARATOR); 309 } 310 List<Document> children = new ArrayList<Document>(docStates.size()); 311 for (DBSDocumentState docState : docStates) { 312 try { 313 children.add(getDocument(docState)); 314 } catch (DocumentNotFoundException e) { 315 // ignore error retrieving one of the children 316 // (Unknown document type) 317 continue; 318 } 319 } 320 return children; 321 } 322 323 protected List<String> getChildrenIds(String parentId) { 324 if (isOrderable(parentId)) { 325 // TODO get only id and pos, not full state 326 // TODO state not for update 327 List<DBSDocumentState> docStates = transaction.getChildrenStates(parentId); 328 Collections.sort(docStates, POS_COMPARATOR); 329 List<String> children = new ArrayList<String>(docStates.size()); 330 for (DBSDocumentState docState : docStates) { 331 children.add(docState.getId()); 332 } 333 return children; 334 } else { 335 return transaction.getChildrenIds(parentId); 336 } 337 } 338 339 protected boolean hasChildren(String parentId) { 340 return transaction.hasChildren(parentId); 341 342 } 343 344 @Override 345 public Document getDocumentByUUID(String id) { 346 Document doc = getDocument(id); 347 if (doc != null) { 348 return doc; 349 } 350 // exception required by API 351 throw new DocumentNotFoundException(id); 352 } 353 354 @Override 355 public Document getRootDocument() { 356 return getDocument(getRootId()); 357 } 358 359 @Override 360 public Document getNullDocument() { 361 return new DBSDocument(null, null, this, true); 362 } 363 364 protected DBSDocument getDocument(String id) { 365 DBSDocumentState docState = transaction.getStateForUpdate(id); 366 return getDocument(docState); 367 } 368 369 protected List<Document> getDocuments(List<String> ids) { 370 List<DBSDocumentState> docStates = transaction.getStatesForUpdate(ids); 371 List<Document> docs = new ArrayList<Document>(ids.size()); 372 for (DBSDocumentState docState : docStates) { 373 docs.add(getDocument(docState)); 374 } 375 return docs; 376 } 377 378 protected DBSDocument getDocument(DBSDocumentState docState) { 379 return getDocument(docState, true); 380 } 381 382 protected DBSDocument getDocument(DBSDocumentState docState, boolean readonly) { 383 if (docState == null) { 384 return null; 385 } 386 boolean isVersion = TRUE.equals(docState.get(KEY_IS_VERSION)); 387 388 String typeName = docState.getPrimaryType(); 389 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 390 DocumentType type = schemaManager.getDocumentType(typeName); 391 if (type == null) { 392 throw new DocumentNotFoundException("Unknown document type: " + typeName); 393 } 394 395 if (isVersion) { 396 return new DBSDocument(docState, type, this, readonly); 397 } else { 398 return new DBSDocument(docState, type, this, false); 399 } 400 } 401 402 protected boolean hasChild(String parentId, String name) { 403 name = normalize(name); 404 return transaction.hasChild(parentId, name); 405 } 406 407 public Document createChild(String id, String parentId, String name, Long pos, String typeName) { 408 name = normalize(name); 409 DBSDocumentState docState = createChildState(id, parentId, name, pos, typeName); 410 return getDocument(docState); 411 } 412 413 protected DBSDocumentState createChildState(String id, String parentId, String name, Long pos, String typeName) { 414 if (pos == null && parentId != null) { 415 pos = getNextPos(parentId); 416 } 417 return transaction.createChild(id, parentId, name, pos, typeName); 418 } 419 420 protected boolean isOrderable(String id) { 421 State state = transaction.getStateForRead(id); 422 String typeName = (String) state.get(KEY_PRIMARY_TYPE); 423 SchemaManager schemaManager = Framework.getLocalService(SchemaManager.class); 424 return schemaManager.getDocumentType(typeName).getFacets().contains(FacetNames.ORDERABLE); 425 } 426 427 protected Long getNextPos(String parentId) { 428 if (!isOrderable(parentId)) { 429 return null; 430 } 431 long max = -1; 432 for (DBSDocumentState docState : transaction.getChildrenStates(parentId)) { 433 Long pos = (Long) docState.get(KEY_POS); 434 if (pos != null && pos.longValue() > max) { 435 max = pos.longValue(); 436 } 437 } 438 return Long.valueOf(max + 1); 439 } 440 441 protected void orderBefore(String parentId, String sourceId, String destId) { 442 if (!isOrderable(parentId)) { 443 // TODO throw exception? 444 return; 445 } 446 if (sourceId.equals(destId)) { 447 return; 448 } 449 // This is optimized by assuming the number of children is small enough 450 // to be manageable in-memory. 451 // fetch children 452 List<DBSDocumentState> docStates = transaction.getChildrenStates(parentId); 453 // sort children in order 454 Collections.sort(docStates, POS_COMPARATOR); 455 // renumber 456 int i = 0; 457 DBSDocumentState source = null; // source if seen 458 Long destPos = null; 459 for (DBSDocumentState docState : docStates) { 460 Serializable id = docState.getId(); 461 if (id.equals(destId)) { 462 destPos = Long.valueOf(i); 463 i++; 464 if (source != null) { 465 source.put(KEY_POS, destPos); 466 } 467 } 468 Long setPos; 469 if (id.equals(sourceId)) { 470 i--; 471 source = docState; 472 setPos = destPos; 473 } else { 474 setPos = Long.valueOf(i); 475 } 476 if (setPos != null) { 477 if (!setPos.equals(docState.get(KEY_POS))) { 478 docState.put(KEY_POS, setPos); 479 } 480 } 481 i++; 482 } 483 if (destId == null) { 484 Long setPos = Long.valueOf(i); 485 if (!setPos.equals(source.get(KEY_POS))) { 486 source.put(KEY_POS, setPos); 487 } 488 } 489 } 490 491 protected void checkOut(String id) { 492 DBSDocumentState docState = transaction.getStateForUpdate(id); 493 if (!TRUE.equals(docState.get(KEY_IS_CHECKED_IN))) { 494 throw new NuxeoException("Already checked out"); 495 } 496 docState.put(KEY_IS_CHECKED_IN, null); 497 } 498 499 protected Document checkIn(String id, String label, String checkinComment) { 500 transaction.save(); 501 DBSDocumentState docState = transaction.getStateForUpdate(id); 502 if (TRUE.equals(docState.get(KEY_IS_CHECKED_IN))) { 503 throw new NuxeoException("Already checked in"); 504 } 505 if (label == null) { 506 // use version major + minor as label 507 Long major = (Long) docState.get(KEY_MAJOR_VERSION); 508 Long minor = (Long) docState.get(KEY_MINOR_VERSION); 509 if (major == null || minor == null) { 510 label = ""; 511 } else { 512 label = major + "." + minor; 513 } 514 } 515 516 // copy into a version 517 DBSDocumentState verState = transaction.copy(id); 518 String verId = verState.getId(); 519 verState.put(KEY_PARENT_ID, null); 520 verState.put(KEY_ANCESTOR_IDS, null); 521 verState.put(KEY_IS_VERSION, TRUE); 522 verState.put(KEY_VERSION_SERIES_ID, id); 523 verState.put(KEY_VERSION_CREATED, new GregorianCalendar()); // now 524 verState.put(KEY_VERSION_LABEL, label); 525 verState.put(KEY_VERSION_DESCRIPTION, checkinComment); 526 verState.put(KEY_IS_LATEST_VERSION, TRUE); 527 verState.put(KEY_IS_CHECKED_IN, null); 528 verState.put(KEY_BASE_VERSION_ID, null); 529 boolean isMajor = Long.valueOf(0).equals(verState.get(KEY_MINOR_VERSION)); 530 verState.put(KEY_IS_LATEST_MAJOR_VERSION, isMajor ? TRUE : null); 531 532 // update the doc to mark it checked in 533 docState.put(KEY_IS_CHECKED_IN, TRUE); 534 docState.put(KEY_BASE_VERSION_ID, verId); 535 536 recomputeVersionSeries(id); 537 transaction.save(); 538 539 return getDocument(verId); 540 } 541 542 /** 543 * Recomputes isLatest / isLatestMajor on all versions. 544 */ 545 protected void recomputeVersionSeries(String versionSeriesId) { 546 List<DBSDocumentState> docStates = transaction.getKeyValuedStates(KEY_VERSION_SERIES_ID, versionSeriesId, 547 KEY_IS_VERSION, TRUE); 548 Collections.sort(docStates, VERSION_CREATED_COMPARATOR); 549 Collections.reverse(docStates); 550 boolean isLatest = true; 551 boolean isLatestMajor = true; 552 for (DBSDocumentState docState : docStates) { 553 // isLatestVersion 554 docState.put(KEY_IS_LATEST_VERSION, isLatest ? TRUE : null); 555 isLatest = false; 556 // isLatestMajorVersion 557 boolean isMajor = Long.valueOf(0).equals(docState.get(KEY_MINOR_VERSION)); 558 docState.put(KEY_IS_LATEST_MAJOR_VERSION, isMajor && isLatestMajor ? TRUE : null); 559 if (isMajor) { 560 isLatestMajor = false; 561 } 562 } 563 } 564 565 protected void restoreVersion(Document doc, Document version) { 566 String docId = doc.getUUID(); 567 String versionId = version.getUUID(); 568 569 DBSDocumentState docState = transaction.getStateForUpdate(docId); 570 State versionState = transaction.getStateForRead(versionId); 571 572 // clear all data 573 for (String key : docState.state.keyArray()) { 574 if (!keepWhenRestore(key)) { 575 docState.put(key, null); 576 } 577 } 578 // update from version 579 for (Entry<String, Serializable> en : versionState.entrySet()) { 580 String key = en.getKey(); 581 if (!keepWhenRestore(key)) { 582 docState.put(key, StateHelper.deepCopy(en.getValue())); 583 } 584 } 585 docState.put(KEY_IS_VERSION, null); 586 docState.put(KEY_IS_CHECKED_IN, TRUE); 587 docState.put(KEY_BASE_VERSION_ID, versionId); 588 } 589 590 // keys we don't copy from version when restoring 591 protected boolean keepWhenRestore(String key) { 592 switch (key) { 593 // these are placeful stuff 594 case KEY_ID: 595 case KEY_PARENT_ID: 596 case KEY_ANCESTOR_IDS: 597 case KEY_NAME: 598 case KEY_POS: 599 case KEY_PRIMARY_TYPE: 600 case KEY_ACP: 601 case KEY_READ_ACL: 602 // these are version-specific 603 case KEY_VERSION_CREATED: 604 case KEY_VERSION_DESCRIPTION: 605 case KEY_VERSION_LABEL: 606 case KEY_VERSION_SERIES_ID: 607 case KEY_IS_LATEST_VERSION: 608 case KEY_IS_LATEST_MAJOR_VERSION: 609 // these will be updated after restore 610 case KEY_IS_VERSION: 611 case KEY_IS_CHECKED_IN: 612 case KEY_BASE_VERSION_ID: 613 return true; 614 } 615 return false; 616 } 617 618 @Override 619 public Document copy(Document source, Document parent, String name) { 620 transaction.save(); 621 if (name == null) { 622 name = source.getName(); 623 } 624 name = findFreeName(parent, name); 625 String sourceId = source.getUUID(); 626 String parentId = parent.getUUID(); 627 State sourceState = transaction.getStateForRead(sourceId); 628 State parentState = transaction.getStateForRead(parentId); 629 String oldParentId = (String) sourceState.get(KEY_PARENT_ID); 630 Object[] parentAncestorIds = (Object[]) parentState.get(KEY_ANCESTOR_IDS); 631 LinkedList<String> ancestorIds = new LinkedList<String>(); 632 if (parentAncestorIds != null) { 633 for (Object id : parentAncestorIds) { 634 ancestorIds.add((String) id); 635 } 636 } 637 ancestorIds.add(parentId); 638 if (oldParentId != null && !oldParentId.equals(parentId)) { 639 if (ancestorIds.contains(sourceId)) { 640 throw new DocumentExistsException("Cannot copy a node under itself: " + parentId + " is under " + sourceId); 641 642 } 643 // checkNotUnder(parentId, sourceId, "copy"); 644 } 645 // do the copy 646 Long pos = getNextPos(parentId); 647 String copyId = copyRecurse(sourceId, parentId, ancestorIds, name); 648 DBSDocumentState copyState = transaction.getStateForUpdate(copyId); 649 // version copy fixup 650 if (source.isVersion()) { 651 copyState.put(KEY_IS_VERSION, null); 652 } 653 // pos fixup 654 copyState.put(KEY_POS, pos); 655 // update read acls 656 transaction.updateReadAcls(copyId); 657 658 return getDocument(copyState); 659 } 660 661 protected String copyRecurse(String sourceId, String parentId, LinkedList<String> ancestorIds, String name) { 662 String copyId = copy(sourceId, parentId, ancestorIds, name); 663 ancestorIds.addLast(copyId); 664 for (String childId : getChildrenIds(sourceId)) { 665 copyRecurse(childId, copyId, ancestorIds, null); 666 } 667 ancestorIds.removeLast(); 668 return copyId; 669 } 670 671 /** 672 * Copy source under parent, and set its ancestors. 673 */ 674 protected String copy(String sourceId, String parentId, List<String> ancestorIds, String name) { 675 DBSDocumentState copy = transaction.copy(sourceId); 676 copy.put(KEY_PARENT_ID, parentId); 677 copy.put(KEY_ANCESTOR_IDS, ancestorIds.toArray(new Object[ancestorIds.size()])); 678 if (name != null) { 679 copy.put(KEY_NAME, name); 680 } 681 copy.put(KEY_BASE_VERSION_ID, null); 682 copy.put(KEY_IS_CHECKED_IN, null); 683 if (parentId != null) { 684 // reset version 685 copy.put(KEY_MAJOR_VERSION, null); 686 copy.put(KEY_MINOR_VERSION, null); 687 } 688 return copy.getId(); 689 } 690 691 protected static final Pattern dotDigitsPattern = Pattern.compile("(.*)\\.[0-9]+$"); 692 693 protected String findFreeName(Document parent, String name) { 694 if (hasChild(parent.getUUID(), name)) { 695 Matcher m = dotDigitsPattern.matcher(name); 696 if (m.matches()) { 697 // remove trailing dot and digits 698 name = m.group(1); 699 } 700 // add dot + unique digits 701 name += "." + System.currentTimeMillis(); 702 } 703 return name; 704 } 705 706 /** Checks that we don't move/copy under ourselves. */ 707 protected void checkNotUnder(String parentId, String id, String op) { 708 // TODO use ancestors 709 String pid = parentId; 710 do { 711 if (pid.equals(id)) { 712 throw new DocumentExistsException("Cannot " + op + " a node under itself: " + parentId + " is under " + id); 713 } 714 State state = transaction.getStateForRead(pid); 715 if (state == null) { 716 // cannot happen 717 throw new NuxeoException("No parent: " + pid); 718 } 719 pid = (String) state.get(KEY_PARENT_ID); 720 } while (pid != null); 721 } 722 723 @Override 724 public Document move(Document source, Document parent, String name) { 725 String oldName = (String) source.getName(); 726 if (name == null) { 727 name = oldName; 728 } 729 String sourceId = source.getUUID(); 730 String parentId = parent.getUUID(); 731 DBSDocumentState sourceState = transaction.getStateForUpdate(sourceId); 732 String oldParentId = (String) sourceState.get(KEY_PARENT_ID); 733 734 // simple case of a rename 735 if (ObjectUtils.equals(oldParentId, parentId)) { 736 if (!oldName.equals(name)) { 737 if (hasChild(parentId, name)) { 738 throw new DocumentExistsException("Destination name already exists: " + name); 739 } 740 // do the move 741 sourceState.put(KEY_NAME, name); 742 // no ancestors to change 743 } 744 return source; 745 } else { 746 // if not just a simple rename, flush 747 transaction.save(); 748 if (hasChild(parentId, name)) { 749 throw new DocumentExistsException("Destination name already exists: " + name); 750 } 751 } 752 753 // prepare new ancestor ids 754 State parentState = transaction.getStateForRead(parentId); 755 Object[] parentAncestorIds = (Object[]) parentState.get(KEY_ANCESTOR_IDS); 756 List<String> ancestorIdsList = new ArrayList<String>(); 757 if (parentAncestorIds != null) { 758 for (Object id : parentAncestorIds) { 759 ancestorIdsList.add((String) id); 760 } 761 } 762 ancestorIdsList.add(parentId); 763 Object[] ancestorIds = ancestorIdsList.toArray(new Object[ancestorIdsList.size()]); 764 765 if (ancestorIdsList.contains(sourceId)) { 766 throw new DocumentExistsException("Cannot move a node under itself: " + parentId + " is under " + sourceId); 767 } 768 769 // do the move 770 sourceState.put(KEY_NAME, name); 771 sourceState.put(KEY_PARENT_ID, parentId); 772 773 // update ancestors on all sub-children 774 Object[] oldAncestorIds = (Object[]) sourceState.get(KEY_ANCESTOR_IDS); 775 int ndel = oldAncestorIds == null ? 0 : oldAncestorIds.length; 776 transaction.updateAncestors(sourceId, ndel, ancestorIds); 777 778 // update read acls 779 transaction.updateReadAcls(sourceId); 780 781 return source; 782 } 783 784 /** 785 * Removes a document. 786 * <p> 787 * We also have to update everything impacted by "relations": 788 * <ul> 789 * <li>parent-child relations: delete all subchildren recursively, 790 * <li>proxy-target relations: if a proxy is removed, update the target's PROXY_IDS; and if a target is removed, 791 * raise an error if a proxy still exists for that target. 792 * </ul> 793 */ 794 protected void remove(String id) { 795 transaction.save(); 796 797 State state = transaction.getStateForRead(id); 798 String versionSeriesId; 799 if (TRUE.equals(state.get(KEY_IS_VERSION))) { 800 versionSeriesId = (String) state.get(KEY_VERSION_SERIES_ID); 801 } else { 802 versionSeriesId = null; 803 } 804 // find all sub-docs and whether they're proxies 805 Map<String, String> proxyTargets = new HashMap<>(); 806 Map<String, Object[]> targetProxies = new HashMap<>(); 807 Set<String> removedIds = transaction.getSubTree(id, proxyTargets, targetProxies); 808 809 // add this node 810 removedIds.add(id); 811 if (TRUE.equals(state.get(KEY_IS_PROXY))) { 812 String targetId = (String) state.get(KEY_PROXY_TARGET_ID); 813 proxyTargets.put(id, targetId); 814 } 815 Object[] proxyIds = (Object[]) state.get(KEY_PROXY_IDS); 816 if (proxyIds != null) { 817 targetProxies.put(id, proxyIds); 818 } 819 820 // if a proxy target is removed, check that all proxies to it 821 // are removed 822 for (Entry<String, Object[]> en : targetProxies.entrySet()) { 823 String targetId = en.getKey(); 824 if (!removedIds.contains(targetId)) { 825 continue; 826 } 827 for (Object proxyId : en.getValue()) { 828 if (!removedIds.contains(proxyId)) { 829 throw new DocumentExistsException("Cannot remove " + id + ", subdocument " + targetId 830 + " is the target of proxy " + proxyId); 831 } 832 } 833 } 834 835 // remove all docs 836 transaction.removeStates(removedIds); 837 838 // fix proxies back-pointers on proxy targets 839 Set<String> targetIds = new HashSet<>(proxyTargets.values()); 840 for (String targetId : targetIds) { 841 if (removedIds.contains(targetId)) { 842 // the target was also removed, skip 843 continue; 844 } 845 DBSDocumentState target = transaction.getStateForUpdate(targetId); 846 if (target != null) { 847 removeBackProxyIds(target, removedIds); 848 } 849 } 850 851 // recompute version series if needed 852 // only done for root of deletion as versions are not fileable 853 if (versionSeriesId != null) { 854 recomputeVersionSeries(versionSeriesId); 855 } 856 } 857 858 @Override 859 public Document createProxy(Document doc, Document folder) { 860 if (doc == null) { 861 throw new NullPointerException(); 862 } 863 String id = doc.getUUID(); 864 String targetId; 865 String versionSeriesId; 866 if (doc.isVersion()) { 867 targetId = id; 868 versionSeriesId = doc.getVersionSeriesId(); 869 } else if (doc.isProxy()) { 870 // copy the proxy 871 State state = transaction.getStateForRead(id); 872 targetId = (String) state.get(KEY_PROXY_TARGET_ID); 873 versionSeriesId = (String) state.get(KEY_PROXY_VERSION_SERIES_ID); 874 } else { 875 // working copy (live document) 876 targetId = id; 877 versionSeriesId = targetId; 878 } 879 880 String parentId = folder.getUUID(); 881 String name = findFreeName(folder, doc.getName()); 882 Long pos = parentId == null ? null : getNextPos(parentId); 883 884 DBSDocumentState docState = addProxyState(null, parentId, name, pos, targetId, versionSeriesId); 885 return getDocument(docState); 886 } 887 888 protected DBSDocumentState addProxyState(String id, String parentId, String name, Long pos, String targetId, 889 String versionSeriesId) { 890 DBSDocumentState target = transaction.getStateForUpdate(targetId); 891 String typeName = (String) target.get(KEY_PRIMARY_TYPE); 892 893 DBSDocumentState proxy = transaction.createChild(id, parentId, name, pos, typeName); 894 String proxyId = proxy.getId(); 895 proxy.put(KEY_IS_PROXY, TRUE); 896 proxy.put(KEY_PROXY_TARGET_ID, targetId); 897 proxy.put(KEY_PROXY_VERSION_SERIES_ID, versionSeriesId); 898 899 // copy target state to proxy 900 transaction.updateProxy(target, proxyId); 901 902 // add back-reference to proxy on target 903 addBackProxyId(target, proxyId); 904 905 return transaction.getStateForUpdate(proxyId); 906 } 907 908 protected void addBackProxyId(DBSDocumentState docState, String id) { 909 Object[] proxyIds = (Object[]) docState.get(KEY_PROXY_IDS); 910 Object[] newProxyIds; 911 if (proxyIds == null) { 912 newProxyIds = new Object[] { id }; 913 } else { 914 newProxyIds = new Object[proxyIds.length + 1]; 915 System.arraycopy(proxyIds, 0, newProxyIds, 0, proxyIds.length); 916 newProxyIds[proxyIds.length] = id; 917 } 918 docState.put(KEY_PROXY_IDS, newProxyIds); 919 } 920 921 protected void removeBackProxyId(DBSDocumentState docState, String id) { 922 removeBackProxyIds(docState, Collections.singleton(id)); 923 } 924 925 protected void removeBackProxyIds(DBSDocumentState docState, Set<String> ids) { 926 Object[] proxyIds = (Object[]) docState.get(KEY_PROXY_IDS); 927 if (proxyIds == null) { 928 return; 929 } 930 List<Object> keepIds = new ArrayList<>(proxyIds.length); 931 for (Object pid : proxyIds) { 932 if (!ids.contains(pid)) { 933 keepIds.add(pid); 934 } 935 } 936 Object[] newProxyIds = keepIds.isEmpty() ? null : keepIds.toArray(new Object[keepIds.size()]); 937 docState.put(KEY_PROXY_IDS, newProxyIds); 938 } 939 940 @Override 941 public List<Document> getProxies(Document doc, Document folder) { 942 List<DBSDocumentState> docStates; 943 String docId = doc.getUUID(); 944 if (doc.isVersion()) { 945 docStates = transaction.getKeyValuedStates(KEY_PROXY_TARGET_ID, docId); 946 } else { 947 String versionSeriesId; 948 if (doc.isProxy()) { 949 State state = transaction.getStateForRead(docId); 950 versionSeriesId = (String) state.get(KEY_PROXY_VERSION_SERIES_ID); 951 } else { 952 versionSeriesId = docId; 953 } 954 docStates = transaction.getKeyValuedStates(KEY_PROXY_VERSION_SERIES_ID, versionSeriesId); 955 } 956 957 String parentId = folder == null ? null : folder.getUUID(); 958 List<Document> documents = new ArrayList<Document>(docStates.size()); 959 for (DBSDocumentState docState : docStates) { 960 // filter by parent 961 if (parentId != null && !parentId.equals(docState.getParentId())) { 962 continue; 963 } 964 documents.add(getDocument(docState)); 965 } 966 return documents; 967 } 968 969 @Override 970 public void setProxyTarget(Document proxy, Document target) { 971 String proxyId = proxy.getUUID(); 972 String targetId = target.getUUID(); 973 DBSDocumentState proxyState = transaction.getStateForUpdate(proxyId); 974 String oldTargetId = (String) proxyState.get(KEY_PROXY_TARGET_ID); 975 976 // update old target's back-pointers: remove proxy id 977 DBSDocumentState oldTargetState = transaction.getStateForUpdate(oldTargetId); 978 removeBackProxyId(oldTargetState, proxyId); 979 // update new target's back-pointers: add proxy id 980 DBSDocumentState targetState = transaction.getStateForUpdate(targetId); 981 addBackProxyId(targetState, proxyId); 982 // set new target 983 proxyState.put(KEY_PROXY_TARGET_ID, targetId); 984 } 985 986 @Override 987 public Document importDocument(String id, Document parent, String name, String typeName, 988 Map<String, Serializable> properties) { 989 String parentId = parent == null ? null : parent.getUUID(); 990 boolean isProxy = typeName.equals(CoreSession.IMPORT_PROXY_TYPE); 991 Map<String, Serializable> props = new HashMap<String, Serializable>(); 992 Long pos = null; // TODO pos 993 DBSDocumentState docState; 994 if (isProxy) { 995 // check that target exists and find its typeName 996 String targetId = (String) properties.get(CoreSession.IMPORT_PROXY_TARGET_ID); 997 if (targetId == null) { 998 throw new NuxeoException("Cannot import proxy " + id + " with null target"); 999 } 1000 State targetState = transaction.getStateForRead(targetId); 1001 if (targetState == null) { 1002 throw new DocumentNotFoundException("Cannot import proxy " + id + " with missing target " + targetId); 1003 } 1004 String versionSeriesId = (String) properties.get(CoreSession.IMPORT_PROXY_VERSIONABLE_ID); 1005 docState = addProxyState(id, parentId, name, pos, targetId, versionSeriesId); 1006 } else { 1007 // version & live document 1008 props.put(KEY_LIFECYCLE_POLICY, properties.get(CoreSession.IMPORT_LIFECYCLE_POLICY)); 1009 props.put(KEY_LIFECYCLE_STATE, properties.get(CoreSession.IMPORT_LIFECYCLE_STATE)); 1010 // compat with old lock import 1011 @SuppressWarnings("deprecation") 1012 String key = (String) properties.get(CoreSession.IMPORT_LOCK); 1013 if (key != null) { 1014 String[] values = key.split(":"); 1015 if (values.length == 2) { 1016 String owner = values[0]; 1017 Calendar created = new GregorianCalendar(); 1018 try { 1019 created.setTimeInMillis(DateFormat.getDateInstance(DateFormat.MEDIUM).parse(values[1]).getTime()); 1020 } catch (ParseException e) { 1021 // use current date 1022 } 1023 props.put(KEY_LOCK_OWNER, owner); 1024 props.put(KEY_LOCK_CREATED, created); 1025 } 1026 } 1027 1028 Serializable importLockOwnerProp = properties.get(CoreSession.IMPORT_LOCK_OWNER); 1029 if (importLockOwnerProp != null) { 1030 props.put(KEY_LOCK_OWNER, importLockOwnerProp); 1031 } 1032 Serializable importLockCreatedProp = properties.get(CoreSession.IMPORT_LOCK_CREATED); 1033 if (importLockCreatedProp != null) { 1034 props.put(KEY_LOCK_CREATED, importLockCreatedProp); 1035 } 1036 1037 props.put(KEY_MAJOR_VERSION, properties.get(CoreSession.IMPORT_VERSION_MAJOR)); 1038 props.put(KEY_MINOR_VERSION, properties.get(CoreSession.IMPORT_VERSION_MINOR)); 1039 Boolean isVersion = trueOrNull(properties.get(CoreSession.IMPORT_IS_VERSION)); 1040 props.put(KEY_IS_VERSION, isVersion); 1041 if (TRUE.equals(isVersion)) { 1042 // version 1043 props.put(KEY_VERSION_SERIES_ID, properties.get(CoreSession.IMPORT_VERSION_VERSIONABLE_ID)); 1044 props.put(KEY_VERSION_CREATED, properties.get(CoreSession.IMPORT_VERSION_CREATED)); 1045 props.put(KEY_VERSION_LABEL, properties.get(CoreSession.IMPORT_VERSION_LABEL)); 1046 props.put(KEY_VERSION_DESCRIPTION, properties.get(CoreSession.IMPORT_VERSION_DESCRIPTION)); 1047 // TODO maybe these should be recomputed at end of import: 1048 props.put(KEY_IS_LATEST_VERSION, trueOrNull(properties.get(CoreSession.IMPORT_VERSION_IS_LATEST))); 1049 props.put(KEY_IS_LATEST_MAJOR_VERSION, 1050 trueOrNull(properties.get(CoreSession.IMPORT_VERSION_IS_LATEST_MAJOR))); 1051 } else { 1052 // live document 1053 props.put(KEY_BASE_VERSION_ID, properties.get(CoreSession.IMPORT_BASE_VERSION_ID)); 1054 props.put(KEY_IS_CHECKED_IN, trueOrNull(properties.get(CoreSession.IMPORT_CHECKED_IN))); 1055 } 1056 docState = createChildState(id, parentId, name, pos, typeName); 1057 } 1058 for (Entry<String, Serializable> entry : props.entrySet()) { 1059 docState.put(entry.getKey(), entry.getValue()); 1060 } 1061 return getDocument(docState, false); // not readonly 1062 } 1063 1064 protected static Boolean trueOrNull(Object value) { 1065 return TRUE.equals(value) ? TRUE : null; 1066 } 1067 1068 @Override 1069 public Document getVersion(String versionSeriesId, VersionModel versionModel) { 1070 DBSDocumentState docState = getVersionByLabel(versionSeriesId, versionModel.getLabel()); 1071 if (docState == null) { 1072 return null; 1073 } 1074 versionModel.setDescription((String) docState.get(KEY_VERSION_DESCRIPTION)); 1075 versionModel.setCreated((Calendar) docState.get(KEY_VERSION_CREATED)); 1076 return getDocument(docState); 1077 } 1078 1079 protected DBSDocumentState getVersionByLabel(String versionSeriesId, String label) { 1080 List<DBSDocumentState> docStates = transaction.getKeyValuedStates(KEY_VERSION_SERIES_ID, versionSeriesId, 1081 KEY_IS_VERSION, TRUE); 1082 for (DBSDocumentState docState : docStates) { 1083 if (label.equals(docState.get(KEY_VERSION_LABEL))) { 1084 return docState; 1085 } 1086 } 1087 return null; 1088 } 1089 1090 protected List<String> getVersionsIds(String versionSeriesId) { 1091 // order by creation date 1092 List<DBSDocumentState> docStates = transaction.getKeyValuedStates(KEY_VERSION_SERIES_ID, versionSeriesId, 1093 KEY_IS_VERSION, TRUE); 1094 Collections.sort(docStates, VERSION_CREATED_COMPARATOR); 1095 List<String> ids = new ArrayList<String>(docStates.size()); 1096 for (DBSDocumentState docState : docStates) { 1097 ids.add(docState.getId()); 1098 } 1099 return ids; 1100 } 1101 1102 protected Document getLastVersion(String versionSeriesId) { 1103 List<DBSDocumentState> docStates = transaction.getKeyValuedStates(KEY_VERSION_SERIES_ID, versionSeriesId, 1104 KEY_IS_VERSION, TRUE); 1105 // find latest one 1106 Calendar latest = null; 1107 DBSDocumentState latestState = null; 1108 for (DBSDocumentState docState : docStates) { 1109 Calendar created = (Calendar) docState.get(KEY_VERSION_CREATED); 1110 if (latest == null || created.compareTo(latest) > 0) { 1111 latest = created; 1112 latestState = docState; 1113 } 1114 } 1115 return latestState == null ? null : getDocument(latestState); 1116 } 1117 1118 private static final Comparator<DBSDocumentState> VERSION_CREATED_COMPARATOR = new Comparator<DBSDocumentState>() { 1119 @Override 1120 public int compare(DBSDocumentState s1, DBSDocumentState s2) { 1121 Calendar c1 = (Calendar) s1.get(KEY_VERSION_CREATED); 1122 Calendar c2 = (Calendar) s2.get(KEY_VERSION_CREATED); 1123 if (c1 == null && c2 == null) { 1124 // coherent sort 1125 return s1.hashCode() - s2.hashCode(); 1126 } 1127 if (c1 == null) { 1128 return 1; 1129 } 1130 if (c2 == null) { 1131 return -1; 1132 } 1133 return c1.compareTo(c2); 1134 } 1135 }; 1136 1137 private static final Comparator<DBSDocumentState> POS_COMPARATOR = new Comparator<DBSDocumentState>() { 1138 @Override 1139 public int compare(DBSDocumentState s1, DBSDocumentState s2) { 1140 Long p1 = (Long) s1.get(KEY_POS); 1141 Long p2 = (Long) s2.get(KEY_POS); 1142 if (p1 == null && p2 == null) { 1143 // coherent sort 1144 return s1.hashCode() - s2.hashCode(); 1145 } 1146 if (p1 == null) { 1147 return 1; 1148 } 1149 if (p2 == null) { 1150 return -1; 1151 } 1152 return p1.compareTo(p2); 1153 } 1154 }; 1155 1156 @Override 1157 public boolean isNegativeAclAllowed() { 1158 return false; 1159 } 1160 1161 // TODO move logic higher 1162 @Override 1163 public ACP getMergedACP(Document doc) { 1164 Document base = doc.isVersion() ? doc.getSourceDocument() : doc; 1165 if (base == null) { 1166 return null; 1167 } 1168 ACP acp = getACP(base); 1169 if (doc.getParent() == null) { 1170 return acp; 1171 } 1172 // get inherited ACLs only if no blocking inheritance ACE exists 1173 // in the top level ACP. 1174 ACL acl = null; 1175 if (acp == null || acp.getAccess(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING) != Access.DENY) { 1176 acl = getInheritedACLs(doc); 1177 } 1178 if (acp == null) { 1179 if (acl == null) { 1180 return null; 1181 } 1182 acp = new ACPImpl(); 1183 } 1184 if (acl != null) { 1185 acp.addACL(acl); 1186 } 1187 return acp; 1188 } 1189 1190 protected ACL getInheritedACLs(Document doc) { 1191 doc = doc.getParent(); 1192 ACL merged = null; 1193 while (doc != null) { 1194 ACP acp = getACP(doc); 1195 if (acp != null) { 1196 ACL acl = acp.getMergedACLs(ACL.INHERITED_ACL); 1197 if (merged == null) { 1198 merged = acl; 1199 } else { 1200 merged.addAll(acl); 1201 } 1202 if (acp.getAccess(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING) == Access.DENY) { 1203 break; 1204 } 1205 } 1206 doc = doc.getParent(); 1207 } 1208 return merged; 1209 } 1210 1211 protected ACP getACP(Document doc) { 1212 State state = transaction.getStateForRead(doc.getUUID()); 1213 return memToAcp(state.get(KEY_ACP)); 1214 } 1215 1216 @Override 1217 public void setACP(Document doc, ACP acp, boolean overwrite) { 1218 checkNegativeAcl(acp); 1219 if (!overwrite) { 1220 if (acp == null) { 1221 return; 1222 } 1223 // merge with existing 1224 acp = updateACP(getACP(doc), acp); 1225 } 1226 String id = doc.getUUID(); 1227 DBSDocumentState docState = transaction.getStateForUpdate(id); 1228 docState.put(KEY_ACP, acpToMem(acp)); 1229 transaction.save(); // read acls update needs full tree 1230 transaction.updateReadAcls(id); 1231 } 1232 1233 protected void checkNegativeAcl(ACP acp) { 1234 if (acp == null) { 1235 return; 1236 } 1237 for (ACL acl : acp.getACLs()) { 1238 if (acl.getName().equals(ACL.INHERITED_ACL)) { 1239 continue; 1240 } 1241 for (ACE ace : acl.getACEs()) { 1242 if (ace.isGranted()) { 1243 continue; 1244 } 1245 String permission = ace.getPermission(); 1246 if (permission.equals(SecurityConstants.EVERYTHING) 1247 && ace.getUsername().equals(SecurityConstants.EVERYONE)) { 1248 continue; 1249 } 1250 // allow Write, as we're sure it doesn't include Read/Browse 1251 if (permission.equals(SecurityConstants.WRITE)) { 1252 continue; 1253 } 1254 throw new IllegalArgumentException("Negative ACL not allowed: " + ace); 1255 } 1256 } 1257 } 1258 1259 /** 1260 * Returns the merge of two ACPs. 1261 */ 1262 // TODO move to ACPImpl 1263 protected static ACP updateACP(ACP curAcp, ACP addAcp) { 1264 if (curAcp == null) { 1265 return addAcp; 1266 } 1267 ACP newAcp = curAcp.clone(); 1268 Map<String, ACL> acls = new HashMap<String, ACL>(); 1269 for (ACL acl : newAcp.getACLs()) { 1270 String name = acl.getName(); 1271 if (ACL.INHERITED_ACL.equals(name)) { 1272 throw new IllegalStateException(curAcp.toString()); 1273 } 1274 acls.put(name, acl); 1275 } 1276 for (ACL acl : addAcp.getACLs()) { 1277 String name = acl.getName(); 1278 if (ACL.INHERITED_ACL.equals(name)) { 1279 continue; 1280 } 1281 ACL curAcl = acls.get(name); 1282 if (curAcl != null) { 1283 // TODO avoid duplicates 1284 curAcl.addAll(acl); 1285 } else { 1286 newAcp.addACL(acl); 1287 } 1288 } 1289 return newAcp; 1290 } 1291 1292 protected static Serializable acpToMem(ACP acp) { 1293 if (acp == null) { 1294 return null; 1295 } 1296 ACL[] acls = acp.getACLs(); 1297 if (acls.length == 0) { 1298 return null; 1299 } 1300 List<Serializable> aclList = new ArrayList<Serializable>(acls.length); 1301 for (ACL acl : acls) { 1302 String name = acl.getName(); 1303 if (name.equals(ACL.INHERITED_ACL)) { 1304 continue; 1305 } 1306 ACE[] aces = acl.getACEs(); 1307 List<Serializable> aceList = new ArrayList<Serializable>(aces.length); 1308 for (ACE ace : aces) { 1309 State aceMap = new State(6); 1310 aceMap.put(KEY_ACE_USER, ace.getUsername()); 1311 aceMap.put(KEY_ACE_PERMISSION, ace.getPermission()); 1312 aceMap.put(KEY_ACE_GRANT, Boolean.valueOf(ace.isGranted())); 1313 String creator = ace.getCreator(); 1314 if (creator != null) { 1315 aceMap.put(KEY_ACE_CREATOR, creator); 1316 } 1317 Calendar begin = ace.getBegin(); 1318 if (begin != null) { 1319 aceMap.put(KEY_ACE_BEGIN, begin); 1320 } 1321 Calendar end = ace.getEnd(); 1322 if (end != null) { 1323 aceMap.put(KEY_ACE_END, end); 1324 } 1325 Long status = ace.getLongStatus(); 1326 if (status != null) { 1327 aceMap.put(KEY_ACE_STATUS, status); 1328 } 1329 aceList.add(aceMap); 1330 } 1331 if (aceList.isEmpty()) { 1332 continue; 1333 } 1334 State aclMap = new State(2); 1335 aclMap.put(KEY_ACL_NAME, name); 1336 aclMap.put(KEY_ACL, (Serializable) aceList); 1337 aclList.add(aclMap); 1338 } 1339 return (Serializable) aclList; 1340 } 1341 1342 protected static ACP memToAcp(Serializable acpSer) { 1343 if (acpSer == null) { 1344 return null; 1345 } 1346 @SuppressWarnings("unchecked") 1347 List<Serializable> aclList = (List<Serializable>) acpSer; 1348 ACP acp = new ACPImpl(); 1349 for (Serializable aclSer : aclList) { 1350 State aclMap = (State) aclSer; 1351 String name = (String) aclMap.get(KEY_ACL_NAME); 1352 @SuppressWarnings("unchecked") 1353 List<Serializable> aceList = (List<Serializable>) aclMap.get(KEY_ACL); 1354 if (aceList == null) { 1355 continue; 1356 } 1357 ACL acl = new ACLImpl(name); 1358 for (Serializable aceSer : aceList) { 1359 State aceMap = (State) aceSer; 1360 String username = (String) aceMap.get(KEY_ACE_USER); 1361 String permission = (String) aceMap.get(KEY_ACE_PERMISSION); 1362 Boolean granted = (Boolean) aceMap.get(KEY_ACE_GRANT); 1363 String creator = (String) aceMap.get(KEY_ACE_CREATOR); 1364 Calendar begin = (Calendar) aceMap.get(KEY_ACE_BEGIN); 1365 Calendar end = (Calendar) aceMap.get(KEY_ACE_END); 1366 // status not read, ACE always computes it on read 1367 ACE ace = ACE.builder(username, permission).isGranted(granted.booleanValue()).creator(creator).begin( 1368 begin).end(end).build(); 1369 acl.add(ace); 1370 } 1371 acp.addACL(acl); 1372 } 1373 return acp; 1374 } 1375 1376 @Override 1377 public Map<String, String> getBinaryFulltext(String id) { 1378 State state = transaction.getStateForRead(id); 1379 String fulltext = (String) state.get(KEY_FULLTEXT_BINARY); 1380 return Collections.singletonMap("binarytext", fulltext); 1381 } 1382 1383 @Override 1384 public PartialList<Document> query(String query, String queryType, QueryFilter queryFilter, long countUpTo) { 1385 // query 1386 PartialList<String> pl = doQuery(query, queryType, queryFilter, (int) countUpTo); 1387 1388 // get Documents in bulk 1389 List<Document> docs = getDocuments(pl.list); 1390 1391 return new PartialList<>(docs, pl.totalSize); 1392 } 1393 1394 protected PartialList<String> doQuery(String query, String queryType, QueryFilter queryFilter, int countUpTo) { 1395 PartialList<Map<String, Serializable>> pl = doQueryAndFetch(query, queryType, queryFilter, true, countUpTo); 1396 List<String> ids = new ArrayList<String>(pl.list.size()); 1397 for (Map<String, Serializable> map : pl.list) { 1398 String id = (String) map.get(NXQL.ECM_UUID); 1399 ids.add(id); 1400 } 1401 return new PartialList<String>(ids, pl.totalSize); 1402 } 1403 1404 protected PartialList<Map<String, Serializable>> doQueryAndFetch(String query, String queryType, 1405 QueryFilter queryFilter, boolean distinctDocuments, int countUpTo) { 1406 if ("NXTAG".equals(queryType)) { 1407 // for now don't try to implement tags 1408 // and return an empty list 1409 return new PartialList<Map<String, Serializable>>(Collections.<Map<String, Serializable>> emptyList(), 0); 1410 } 1411 if (!NXQL.NXQL.equals(queryType)) { 1412 throw new NuxeoException("No QueryMaker accepts query type: " + queryType); 1413 } 1414 // transform the query according to the transformers defined by the 1415 // security policies 1416 SQLQuery sqlQuery = SQLQueryParser.parse(query); 1417 SelectClause selectClause = sqlQuery.select; 1418 if (selectClause.isEmpty()) { 1419 // turned into SELECT ecm:uuid 1420 selectClause.add(new Reference(NXQL.ECM_UUID)); 1421 } 1422 if (selectClause.isDistinct()) { 1423 if (selectClause.getSelectList().size() == 1 1424 && (selectClause.get(0).equals(new Reference(NXQL.ECM_UUID)))) { 1425 // ok, SELECT ecm:uuid 1426 } else { 1427 throw new QueryParseException("SELECT DISTINCT not supported on DBS"); 1428 } 1429 distinctDocuments = true; 1430 } 1431 for (SQLQuery.Transformer transformer : queryFilter.getQueryTransformers()) { 1432 sqlQuery = transformer.transform(queryFilter.getPrincipal(), sqlQuery); 1433 } 1434 OrderByClause orderByClause = sqlQuery.orderBy; 1435 1436 QueryOptimizer optimizer = new QueryOptimizer(); 1437 MultiExpression expression = optimizer.getOptimizedQuery(sqlQuery, queryFilter.getFacetFilter()); 1438 DBSExpressionEvaluator evaluator = new DBSExpressionEvaluator(this, selectClause, expression, orderByClause, 1439 queryFilter.getPrincipals(), fulltextSearchDisabled); 1440 1441 int limit = (int) queryFilter.getLimit(); 1442 int offset = (int) queryFilter.getOffset(); 1443 if (offset < 0) { 1444 offset = 0; 1445 } 1446 if (limit < 0) { 1447 limit = 0; 1448 } 1449 1450 int repoLimit; 1451 int repoOffset; 1452 OrderByClause repoOrderByClause; 1453 boolean postFilter = isOrderByPath(orderByClause); 1454 if (postFilter) { 1455 // we have to merge ordering and batching between memory and 1456 // repository 1457 repoLimit = 0; 1458 repoOffset = 0; 1459 repoOrderByClause = null; 1460 } else { 1461 // fast case, we can use the repository query directly 1462 repoLimit = limit; 1463 repoOffset = offset; 1464 repoOrderByClause = orderByClause; 1465 } 1466 1467 // query the repository 1468 PartialList<Map<String, Serializable>> pl = repository.queryAndFetch(evaluator, repoOrderByClause, 1469 distinctDocuments, repoLimit, repoOffset, countUpTo); 1470 1471 List<Map<String, Serializable>> projections = pl.list; 1472 long totalSize = pl.totalSize; 1473 if (totalSize >= 0) { 1474 if (countUpTo == -1) { 1475 // count full size 1476 } else if (countUpTo == 0) { 1477 // no count 1478 totalSize = -1; // not counted 1479 } else { 1480 // count only if less than countUpTo 1481 if (totalSize > countUpTo) { 1482 totalSize = -2; // truncated 1483 } 1484 } 1485 } 1486 1487 if (postFilter) { 1488 // ORDER BY 1489 if (orderByClause != null) { 1490 doOrderBy(projections, orderByClause); 1491 } 1492 // LIMIT / OFFSET 1493 if (limit != 0) { 1494 int size = projections.size(); 1495 projections.subList(0, offset > size ? size : offset).clear(); 1496 size = projections.size(); 1497 if (limit < size) { 1498 projections.subList(limit, size).clear(); 1499 } 1500 } 1501 } 1502 1503 return new PartialList<Map<String, Serializable>>(projections, totalSize); 1504 } 1505 1506 /** Does an ORDER BY clause include ecm:path */ 1507 protected boolean isOrderByPath(OrderByClause orderByClause) { 1508 if (orderByClause == null) { 1509 return false; 1510 } 1511 for (OrderByExpr ob : orderByClause.elements) { 1512 if (ob.reference.name.equals(NXQL.ECM_PATH)) { 1513 return true; 1514 } 1515 } 1516 return false; 1517 } 1518 1519 protected String getPath(Map<String, Serializable> projection) { 1520 String name = (String) projection.get(NXQL.ECM_NAME); 1521 String parentId = (String) projection.get(NXQL.ECM_PARENTID); 1522 State state; 1523 if (parentId == null || (state = transaction.getStateForRead(parentId)) == null) { 1524 if ("".equals(name)) { 1525 return "/"; // root 1526 } else { 1527 return name; // placeless, no slash 1528 } 1529 } 1530 LinkedList<String> list = new LinkedList<String>(); 1531 list.addFirst(name); 1532 for (;;) { 1533 name = (String) state.get(KEY_NAME); 1534 parentId = (String) state.get(KEY_PARENT_ID); 1535 list.addFirst(name); 1536 if (parentId == null || (state = transaction.getStateForRead(parentId)) == null) { 1537 return StringUtils.join(list, '/'); 1538 } 1539 } 1540 } 1541 1542 protected void doOrderBy(List<Map<String, Serializable>> projections, OrderByClause orderByClause) { 1543 if (isOrderByPath(orderByClause)) { 1544 // add path info to do the sort 1545 for (Map<String, Serializable> projection : projections) { 1546 projection.put(ExpressionEvaluator.NXQL_ECM_PATH, getPath(projection)); 1547 } 1548 } 1549 Collections.sort(projections, new OrderByComparator(orderByClause)); 1550 } 1551 1552 public static class OrderByComparator implements Comparator<Map<String, Serializable>> { 1553 1554 protected final OrderByClause orderByClause; 1555 1556 public OrderByComparator(OrderByClause orderByClause) { 1557 // replace ecm:path with ecm:__path for evaluation 1558 // (we don't want to allow ecm:path to be usable anywhere else 1559 // and resolve to a null value) 1560 OrderByList obl = new OrderByList(null); // stupid constructor 1561 obl.clear(); 1562 for (OrderByExpr ob : orderByClause.elements) { 1563 if (ob.reference.name.equals(NXQL.ECM_PATH)) { 1564 ob = new OrderByExpr(new Reference(ExpressionEvaluator.NXQL_ECM_PATH), ob.isDescending); 1565 } 1566 obl.add(ob); 1567 } 1568 this.orderByClause = new OrderByClause(obl); 1569 } 1570 1571 @Override 1572 public int compare(Map<String, Serializable> map1, Map<String, Serializable> map2) { 1573 for (OrderByExpr ob : orderByClause.elements) { 1574 Reference ref = ob.reference; 1575 boolean desc = ob.isDescending; 1576 Object v1 = map1.get(ref.name); 1577 Object v2 = map2.get(ref.name); 1578 int cmp; 1579 if (v1 == null) { 1580 cmp = v2 == null ? 0 : -1; 1581 } else if (v2 == null) { 1582 cmp = 1; 1583 } else { 1584 if (!(v1 instanceof Comparable)) { 1585 throw new QueryParseException("Not a comparable: " + v1); 1586 } 1587 cmp = ((Comparable<Object>) v1).compareTo(v2); 1588 } 1589 if (desc) { 1590 cmp = -cmp; 1591 } 1592 if (cmp != 0) { 1593 return cmp; 1594 } 1595 // loop for lexicographical comparison 1596 } 1597 return 0; 1598 } 1599 } 1600 1601 @Override 1602 public IterableQueryResult queryAndFetch(String query, String queryType, QueryFilter queryFilter, 1603 boolean distinctDocuments, Object[] params) { 1604 PartialList<Map<String, Serializable>> pl = doQueryAndFetch(query, queryType, queryFilter, distinctDocuments, 1605 -1); 1606 return new DBSQueryResult(pl); 1607 } 1608 1609 protected static class DBSQueryResult implements IterableQueryResult, Iterator<Map<String, Serializable>> { 1610 1611 boolean closed; 1612 1613 protected List<Map<String, Serializable>> maps; 1614 1615 protected long totalSize; 1616 1617 protected long pos; 1618 1619 protected DBSQueryResult(PartialList<Map<String, Serializable>> pl) { 1620 this.maps = pl.list; 1621 this.totalSize = pl.totalSize; 1622 } 1623 1624 @Override 1625 public Iterator<Map<String, Serializable>> iterator() { 1626 return this; 1627 } 1628 1629 @Override 1630 public void close() { 1631 closed = true; 1632 pos = -1; 1633 } 1634 1635 @Override 1636 public boolean isLife() { 1637 return !closed; 1638 } 1639 1640 @Override 1641 public boolean mustBeClosed() { 1642 return false; // holds no resources 1643 } 1644 1645 @Override 1646 public long size() { 1647 return totalSize; 1648 } 1649 1650 @Override 1651 public long pos() { 1652 return pos; 1653 } 1654 1655 @Override 1656 public void skipTo(long pos) { 1657 if (pos < 0) { 1658 pos = 0; 1659 } else if (pos > totalSize) { 1660 pos = totalSize; 1661 } 1662 this.pos = pos; 1663 } 1664 1665 @Override 1666 public boolean hasNext() { 1667 return pos < totalSize; 1668 } 1669 1670 @Override 1671 public Map<String, Serializable> next() { 1672 if (closed || pos == totalSize) { 1673 throw new NoSuchElementException(); 1674 } 1675 Map<String, Serializable> map = maps.get((int) pos); 1676 pos++; 1677 return map; 1678 } 1679 1680 @Override 1681 public void remove() { 1682 throw new UnsupportedOperationException(); 1683 } 1684 } 1685 1686 public static String convToInternal(String name) { 1687 switch (name) { 1688 case NXQL.ECM_UUID: 1689 return KEY_ID; 1690 case NXQL.ECM_NAME: 1691 return KEY_NAME; 1692 case NXQL.ECM_POS: 1693 return KEY_POS; 1694 case NXQL.ECM_PARENTID: 1695 return KEY_PARENT_ID; 1696 case NXQL.ECM_MIXINTYPE: 1697 return KEY_MIXIN_TYPES; 1698 case NXQL.ECM_PRIMARYTYPE: 1699 return KEY_PRIMARY_TYPE; 1700 case NXQL.ECM_ISPROXY: 1701 return KEY_IS_PROXY; 1702 case NXQL.ECM_ISVERSION: 1703 case NXQL.ECM_ISVERSION_OLD: 1704 return KEY_IS_VERSION; 1705 case NXQL.ECM_LIFECYCLESTATE: 1706 return KEY_LIFECYCLE_STATE; 1707 case NXQL.ECM_LOCK_OWNER: 1708 return KEY_LOCK_OWNER; 1709 case NXQL.ECM_LOCK_CREATED: 1710 return KEY_LOCK_CREATED; 1711 case NXQL.ECM_PROXY_TARGETID: 1712 return KEY_PROXY_TARGET_ID; 1713 case NXQL.ECM_PROXY_VERSIONABLEID: 1714 return KEY_PROXY_VERSION_SERIES_ID; 1715 case NXQL.ECM_ISCHECKEDIN: 1716 return KEY_IS_CHECKED_IN; 1717 case NXQL.ECM_ISLATESTVERSION: 1718 return KEY_IS_LATEST_VERSION; 1719 case NXQL.ECM_ISLATESTMAJORVERSION: 1720 return KEY_IS_LATEST_MAJOR_VERSION; 1721 case NXQL.ECM_VERSIONLABEL: 1722 return KEY_VERSION_LABEL; 1723 case NXQL.ECM_VERSIONCREATED: 1724 return KEY_VERSION_CREATED; 1725 case NXQL.ECM_VERSIONDESCRIPTION: 1726 return KEY_VERSION_DESCRIPTION; 1727 case NXQL.ECM_VERSION_VERSIONABLEID: 1728 return KEY_VERSION_SERIES_ID; 1729 case ExpressionEvaluator.NXQL_ECM_ANCESTOR_IDS: 1730 return KEY_ANCESTOR_IDS; 1731 case ExpressionEvaluator.NXQL_ECM_PATH: 1732 return KEY_PATH_INTERNAL; 1733 case ExpressionEvaluator.NXQL_ECM_READ_ACL: 1734 return KEY_READ_ACL; 1735 case NXQL.ECM_FULLTEXT_JOBID: 1736 return KEY_FULLTEXT_JOBID; 1737 case NXQL.ECM_FULLTEXT_SCORE: 1738 return KEY_FULLTEXT_SCORE; 1739 case ExpressionEvaluator.NXQL_ECM_FULLTEXT_SIMPLE: 1740 return KEY_FULLTEXT_SIMPLE; 1741 case ExpressionEvaluator.NXQL_ECM_FULLTEXT_BINARY: 1742 return KEY_FULLTEXT_BINARY; 1743 case NXQL.ECM_ACL: 1744 return KEY_ACP; 1745 case NXQL.ECM_FULLTEXT: 1746 case NXQL.ECM_TAG: 1747 throw new UnsupportedOperationException(name); 1748 } 1749 throw new QueryParseException("No such property: " + name); 1750 } 1751 1752 public static String convToInternalAce(String name) { 1753 switch (name) { 1754 case NXQL.ECM_ACL_NAME: 1755 return KEY_ACL_NAME; 1756 case NXQL.ECM_ACL_PRINCIPAL: 1757 return KEY_ACE_USER; 1758 case NXQL.ECM_ACL_PERMISSION: 1759 return KEY_ACE_PERMISSION; 1760 case NXQL.ECM_ACL_GRANT: 1761 return KEY_ACE_GRANT; 1762 case NXQL.ECM_ACL_CREATOR: 1763 return KEY_ACE_CREATOR; 1764 case NXQL.ECM_ACL_BEGIN: 1765 return KEY_ACE_BEGIN; 1766 case NXQL.ECM_ACL_END: 1767 return KEY_ACE_END; 1768 case NXQL.ECM_ACL_STATUS: 1769 return KEY_ACE_STATUS; 1770 } 1771 return null; 1772 } 1773 1774 public static String convToNXQL(String name) { 1775 switch (name) { 1776 case KEY_ID: 1777 return NXQL.ECM_UUID; 1778 case KEY_NAME: 1779 return NXQL.ECM_NAME; 1780 case KEY_POS: 1781 return NXQL.ECM_POS; 1782 case KEY_PARENT_ID: 1783 return NXQL.ECM_PARENTID; 1784 case KEY_MIXIN_TYPES: 1785 return NXQL.ECM_MIXINTYPE; 1786 case KEY_PRIMARY_TYPE: 1787 return NXQL.ECM_PRIMARYTYPE; 1788 case KEY_IS_PROXY: 1789 return NXQL.ECM_ISPROXY; 1790 case KEY_IS_VERSION: 1791 return NXQL.ECM_ISVERSION; 1792 case KEY_LIFECYCLE_STATE: 1793 return NXQL.ECM_LIFECYCLESTATE; 1794 case KEY_LOCK_OWNER: 1795 return NXQL.ECM_LOCK_OWNER; 1796 case KEY_LOCK_CREATED: 1797 return NXQL.ECM_LOCK_CREATED; 1798 case KEY_PROXY_TARGET_ID: 1799 return NXQL.ECM_PROXY_TARGETID; 1800 case KEY_PROXY_VERSION_SERIES_ID: 1801 return NXQL.ECM_PROXY_VERSIONABLEID; 1802 case KEY_IS_CHECKED_IN: 1803 return NXQL.ECM_ISCHECKEDIN; 1804 case KEY_IS_LATEST_VERSION: 1805 return NXQL.ECM_ISLATESTVERSION; 1806 case KEY_IS_LATEST_MAJOR_VERSION: 1807 return NXQL.ECM_ISLATESTMAJORVERSION; 1808 case KEY_VERSION_LABEL: 1809 return NXQL.ECM_VERSIONLABEL; 1810 case KEY_VERSION_CREATED: 1811 return NXQL.ECM_VERSIONCREATED; 1812 case KEY_VERSION_DESCRIPTION: 1813 return NXQL.ECM_VERSIONDESCRIPTION; 1814 case KEY_VERSION_SERIES_ID: 1815 return NXQL.ECM_VERSION_VERSIONABLEID; 1816 case KEY_MAJOR_VERSION: 1817 return "major_version"; // TODO XXX constant 1818 case KEY_MINOR_VERSION: 1819 return "minor_version"; 1820 case KEY_FULLTEXT_SCORE: 1821 return NXQL.ECM_FULLTEXT_SCORE; 1822 case KEY_LIFECYCLE_POLICY: 1823 case KEY_ACP: 1824 case KEY_ANCESTOR_IDS: 1825 case KEY_BASE_VERSION_ID: 1826 case KEY_READ_ACL: 1827 case KEY_FULLTEXT_SIMPLE: 1828 case KEY_FULLTEXT_BINARY: 1829 case KEY_FULLTEXT_JOBID: 1830 case KEY_PATH_INTERNAL: 1831 return null; 1832 } 1833 throw new QueryParseException("No such property: " + name); 1834 } 1835 1836 protected static final Type STRING_ARRAY_TYPE = new ListTypeImpl("", "", StringType.INSTANCE); 1837 1838 public static Type getType(String name) { 1839 switch (name) { 1840 case KEY_IS_VERSION: 1841 case KEY_IS_CHECKED_IN: 1842 case KEY_IS_LATEST_VERSION: 1843 case KEY_IS_LATEST_MAJOR_VERSION: 1844 case KEY_IS_PROXY: 1845 case KEY_ACE_GRANT: 1846 return BooleanType.INSTANCE; 1847 case KEY_VERSION_CREATED: 1848 case KEY_LOCK_CREATED: 1849 case KEY_ACE_BEGIN: 1850 case KEY_ACE_END: 1851 return DateType.INSTANCE; 1852 case KEY_MIXIN_TYPES: 1853 case KEY_ANCESTOR_IDS: 1854 case KEY_PROXY_IDS: 1855 return STRING_ARRAY_TYPE; 1856 } 1857 return null; 1858 } 1859 1860 @Override 1861 public LockManager getLockManager() { 1862 return repository.getLockManager(); 1863 } 1864 1865}