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