001/* 002 * Copyright 2010 Anyware Services 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 */ 016package org.ametys.plugins.repository.jcr; 017 018import java.util.Arrays; 019import java.util.Calendar; 020import java.util.Comparator; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.Optional; 027import java.util.Set; 028 029import javax.jcr.Node; 030import javax.jcr.NodeIterator; 031import javax.jcr.Property; 032import javax.jcr.PropertyIterator; 033import javax.jcr.RepositoryException; 034import javax.jcr.version.OnParentVersionAction; 035import javax.jcr.version.Version; 036import javax.jcr.version.VersionException; 037import javax.jcr.version.VersionHistory; 038import javax.jcr.version.VersionIterator; 039 040import org.apache.jackrabbit.core.NodeImpl; 041 042import org.ametys.core.group.GroupIdentity; 043import org.ametys.core.right.ProfileAssignmentStorage.AnonymousOrAnyConnectedKeys; 044import org.ametys.core.right.ProfileAssignmentStorage.UserOrGroup; 045import org.ametys.core.user.UserIdentity; 046import org.ametys.plugins.repository.AmetysObject; 047import org.ametys.plugins.repository.AmetysRepositoryException; 048import org.ametys.plugins.repository.ModifiableACLAmetysObject; 049import org.ametys.plugins.repository.UnknownAmetysObjectException; 050import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder; 051import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder; 052import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 053import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 054import org.ametys.plugins.repository.lock.LockableAmetysObject; 055import org.ametys.plugins.repository.version.ModifiableDataAwareVersionableAmetysObject; 056import org.ametys.plugins.repository.version.VersionableAmetysObject; 057 058/** 059 * Default implementation of a {@link JCRAmetysObject}, which is also a {@link VersionableAmetysObject}. 060 * @param <F> the actual type of factory. 061 */ 062public class DefaultAmetysObject<F extends DefaultAmetysObjectFactory> extends SimpleAmetysObject<F> implements ModifiableDataAwareVersionableAmetysObject, ModifiableACLAmetysObject 063{ 064 065 /** Properties that are auto-created or protected, which mustn't be copied when copying a node. */ 066 protected static final List<String> PROTECTED_PROPERTIES = Arrays.asList("jcr:uuid", 067 "jcr:primaryType", "jcr:predecessors", "jcr:versionHistory", "jcr:baseVersion"); 068 /** Compare a pair of JCR version name and creation date with an other **/ 069 protected static final Comparator<Entry<String, Calendar>> __VERSION_COMPARATOR = Comparator.comparing(Entry<String, Calendar>::getValue).thenComparing(Entry::getKey); 070 071 // Root JCR Node of this content 072 private Node _baseNode; 073 074 // pointed version, or null if HEAD 075 private Version _versionNode; 076 077 // Current version, either HEAD or a given version 078 private Node _currentNode; 079 080 // The version history of the base node 081 private VersionHistory _versionHistory; 082 083 /** 084 * Creates an {@link DefaultAmetysObject}. 085 * @param node the node backing this {@link AmetysObject} 086 * @param parentPath the parentPath in the Ametys hierarchy 087 * @param factory the DefaultAmetysObjectFactory which created the AmetysObject 088 */ 089 public DefaultAmetysObject(Node node, String parentPath, F factory) 090 { 091 super(node, parentPath, factory); 092 _baseNode = node; 093 _currentNode = node; 094 } 095 096 @Override 097 public Node getNode() 098 { 099 return _currentNode; 100 } 101 102 /** 103 * Returns the JCR node backing this {@link AmetysObject} in the default JCR workspace 104 * @return the JCR node backing this {@link AmetysObject} in the default JCR workspace 105 */ 106 public Node getBaseNode () 107 { 108 return _baseNode; 109 } 110 111 // Versioning capabilities 112 113 /** 114 * Returns the JCR {@link VersionHistory} of the base node. 115 * @return the JCR {@link VersionHistory} of the base node. 116 * @throws RepositoryException if something wrong occurs retrieving the VersionHistory. 117 */ 118 protected VersionHistory getVersionHistory() throws RepositoryException 119 { 120 if (_versionHistory == null) 121 { 122 _versionHistory = _baseNode.getSession().getWorkspace().getVersionManager().getVersionHistory(_baseNode.getPath()); 123 } 124 125 return _versionHistory; 126 } 127 128 /** 129 * Returns the JCR base version of the node. 130 * @return the JCR base version of the node. 131 * @throws RepositoryException if something wrong occurs retrieving the base version. 132 */ 133 protected Version getBaseVersion() throws RepositoryException 134 { 135 return _baseNode.getSession().getWorkspace().getVersionManager().getBaseVersion(_baseNode.getPath()); 136 } 137 138 public void checkpoint() throws AmetysRepositoryException 139 { 140 try 141 { 142 getNode().getSession().getWorkspace().getVersionManager().checkpoint(getNode().getPath()); 143 } 144 catch (RepositoryException e) 145 { 146 throw new AmetysRepositoryException("Unable to checkpoint", e); 147 } 148 } 149 150 public void switchToLabel(String label) throws UnknownAmetysObjectException, AmetysRepositoryException 151 { 152 if (label == null) 153 { 154 // back to current version 155 _versionNode = null; 156 _currentNode = _baseNode; 157 } 158 else 159 { 160 try 161 { 162 VersionHistory history = getVersionHistory(); 163 _versionNode = history.getVersionByLabel(label); 164 _currentNode = _versionNode.getFrozenNode(); 165 } 166 catch (VersionException e) 167 { 168 throw new UnknownAmetysObjectException("There's no label : " + label, e); 169 } 170 catch (RepositoryException e) 171 { 172 throw new AmetysRepositoryException("Unable to switch to label : " + label, e); 173 } 174 } 175 } 176 177 public void switchToRevision(String revision) throws UnknownAmetysObjectException, AmetysRepositoryException 178 { 179 if (revision == null) 180 { 181 // back to current version 182 _versionNode = null; 183 _currentNode = _baseNode; 184 } 185 else 186 { 187 try 188 { 189 VersionHistory history = getVersionHistory(); 190 _versionNode = history.getVersion(revision); 191 _currentNode = _versionNode.getNode("jcr:frozenNode"); 192 } 193 catch (VersionException e) 194 { 195 throw new UnknownAmetysObjectException("There's no revision : " + revision, e); 196 } 197 catch (RepositoryException e) 198 { 199 throw new AmetysRepositoryException("Unable to switch to revision : " + revision, e); 200 } 201 } 202 } 203 204 public void restoreFromLabel(String label) throws UnknownAmetysObjectException, AmetysRepositoryException 205 { 206 try 207 { 208 VersionHistory history = getVersionHistory(); 209 Node versionNode = history.getVersionByLabel(label); 210 restoreFromNode(versionNode.getNode("jcr:frozenNode")); 211 } 212 catch (RepositoryException e) 213 { 214 throw new AmetysRepositoryException("Unable to restore from label: " + label, e); 215 } 216 } 217 218 public void restoreFromRevision(String revision) throws UnknownAmetysObjectException, AmetysRepositoryException 219 { 220 try 221 { 222 VersionHistory history = getVersionHistory(); 223 Node versionNode = history.getVersion(revision); 224 restoreFromNode(versionNode.getNode("jcr:frozenNode")); 225 } 226 catch (RepositoryException e) 227 { 228 throw new AmetysRepositoryException("Unable to restore from revision: " + revision, e); 229 } 230 } 231 232 /** 233 * Restore from a node 234 * @param node The node to restore 235 * @throws RepositoryException If error occurs 236 */ 237 protected void restoreFromNode(Node node) throws RepositoryException 238 { 239 // Remove all properties and nodes of the current node (except jcr and OnParentVersion=IGNORE). 240 PropertyIterator propIt = _baseNode.getProperties(); 241 while (propIt.hasNext()) 242 { 243 Property prop = propIt.nextProperty(); 244 String propName = prop.getName(); 245 if (!propName.startsWith("jcr:") && prop.getDefinition().getOnParentVersion() != OnParentVersionAction.IGNORE) 246 { 247 prop.remove(); 248 } 249 } 250 251 NodeIterator nodeIt = _baseNode.getNodes(); 252 while (nodeIt.hasNext()) 253 { 254 Node childNode = nodeIt.nextNode(); 255 String nodeName = childNode.getName(); 256 if (!nodeName.startsWith("jcr:") && childNode.getDefinition().getOnParentVersion() != OnParentVersionAction.IGNORE) 257 { 258 childNode.remove(); 259 } 260 } 261 262 // Copy all properties and nodes from the given node (except jcr). 263 PropertyIterator newPropIt = node.getProperties(); 264 while (newPropIt.hasNext()) 265 { 266 Property newProp = newPropIt.nextProperty(); 267 268 if (!newProp.getName().startsWith("jcr:")) 269 { 270 if (newProp.getDefinition().isMultiple()) 271 { 272 _baseNode.setProperty(newProp.getName(), newProp.getValues(), newProp.getType()); 273 } 274 else 275 { 276 _baseNode.setProperty(newProp.getName(), newProp.getValue(), newProp.getType()); 277 } 278 } 279 } 280 281 NodeIterator newNodeIt = node.getNodes(); 282 while (newNodeIt.hasNext()) 283 { 284 Node newNode = newNodeIt.nextNode(); 285 286 if (!newNode.getName().startsWith("jcr:")) 287 { 288 copyNode(_baseNode, newNode); 289 } 290 } 291 } 292 293 /** 294 * Copy the source node in parent node 295 * @param parentDest The dest node 296 * @param src The source node to copy 297 * @throws RepositoryException If error occurs 298 */ 299 protected void copyNode(Node parentDest, Node src) throws RepositoryException 300 { 301 Node dest; 302 if (parentDest.hasNode(src.getName())) 303 { 304 // case of auto created child 305 dest = parentDest.getNode(src.getName()); 306 } 307 else 308 { 309 String uuid = null; 310 if (src.hasProperty("jcr:frozenUuid")) 311 { 312 uuid = src.getProperty("jcr:frozenUuid").getString(); 313 } 314 315 if (uuid == null) 316 { 317 dest = parentDest.addNode(src.getName(), src.getProperty("jcr:frozenPrimaryType").getString()); 318 } 319 else 320 { 321 dest = ((NodeImpl) parentDest).addNodeWithUuid(src.getName(), src.getProperty("jcr:frozenPrimaryType").getString(), uuid); 322 } 323 } 324 325 PropertyIterator pit = src.getProperties(); 326 while (pit.hasNext()) 327 { 328 Property p = pit.nextProperty(); 329 String name = p.getName(); 330 331 // Tests for protected and/or autocreated properties 332 if (!PROTECTED_PROPERTIES.contains(name) && !name.startsWith("jcr:frozen") && !dest.hasProperty(name)) 333 { 334 if (p.getDefinition().isMultiple()) 335 { 336 dest.setProperty(name, p.getValues()); 337 } 338 else 339 { 340 dest.setProperty(name, p.getValue()); 341 } 342 } 343 } 344 345 NodeIterator nit = src.getNodes(); 346 while (nit.hasNext()) 347 { 348 copyNode(dest, nit.nextNode()); 349 } 350 } 351 352 public void addLabel(String label, boolean moveIfPresent) throws AmetysRepositoryException 353 { 354 try 355 { 356 VersionHistory history = getVersionHistory(); 357 String versionName; 358 359 if (_versionNode == null) 360 { 361 // not sticked to a particular version 362 versionName = getBaseVersion().getName(); 363 } 364 else 365 { 366 // sticked to label 367 versionName = _versionNode.getName(); 368 } 369 370 history.addVersionLabel(versionName, label, moveIfPresent); 371 } 372 catch (RepositoryException e) 373 { 374 throw new AmetysRepositoryException("Unable to add label : " + label, e); 375 } 376 } 377 378 public void removeLabel(String label) throws AmetysRepositoryException 379 { 380 try 381 { 382 VersionHistory history = getVersionHistory(); 383 history.removeVersionLabel(label); 384 } 385 catch (RepositoryException ex) 386 { 387 throw new AmetysRepositoryException("Unable to remove label : " + label, ex); 388 } 389 } 390 391 public String[] getAllLabels() throws AmetysRepositoryException 392 { 393 try 394 { 395 return getVersionHistory().getVersionLabels(); 396 } 397 catch (RepositoryException e) 398 { 399 throw new AmetysRepositoryException("Unable to retrieve list of labels", e); 400 } 401 } 402 403 public String[] getLabels() throws AmetysRepositoryException 404 { 405 try 406 { 407 Version version = _versionNode; 408 409 if (version == null) 410 { 411 // not sticked to a particular version 412 version = getBaseVersion(); 413 } 414 415 return version.getContainingHistory().getVersionLabels(version); 416 } 417 catch (RepositoryException e) 418 { 419 throw new AmetysRepositoryException("Unable to retrieve list of labels for current version", e); 420 } 421 } 422 423 424 public String[] getLabels(String revision) throws UnknownAmetysObjectException, AmetysRepositoryException 425 { 426 try 427 { 428 VersionHistory history = getVersionHistory(); 429 Version version = history.getVersion(revision); 430 return history.getVersionLabels(version); 431 } 432 catch (VersionException e) 433 { 434 throw new UnknownAmetysObjectException("There's no revision " + revision, e); 435 } 436 catch (RepositoryException e) 437 { 438 throw new AmetysRepositoryException("Unable to retrieve list of labels for current version", e); 439 } 440 } 441 442 public String getRevision() throws AmetysRepositoryException 443 { 444 if (_versionNode == null) 445 { 446 // Current version 447 return null; 448 } 449 450 try 451 { 452 return _versionNode.getName(); 453 } 454 catch (RepositoryException e) 455 { 456 throw new AmetysRepositoryException("Unable to get revision", e); 457 } 458 } 459 460 @Override 461 public Date getRevisionTimestamp() throws AmetysRepositoryException 462 { 463 if (_versionNode == null) 464 { 465 // Current version 466 return null; 467 } 468 469 try 470 { 471 return _versionNode.getCreated().getTime(); 472 } 473 catch (RepositoryException e) 474 { 475 throw new AmetysRepositoryException("Unable to get revision date", e); 476 } 477 } 478 479 @Override 480 public Date getRevisionTimestamp(String revision) throws UnknownAmetysObjectException, AmetysRepositoryException 481 { 482 try 483 { 484 VersionHistory history = getVersionHistory(); 485 Version version = history.getVersion(revision); 486 487 return version.getCreated().getTime(); 488 } 489 catch (VersionException e) 490 { 491 throw new UnknownAmetysObjectException("There's no revision " + revision, e); 492 } 493 catch (RepositoryException e) 494 { 495 throw new AmetysRepositoryException("Unable to get revision date", e); 496 } 497 } 498 499 public String[] getAllRevisions() throws AmetysRepositoryException 500 { 501 try 502 { 503 Map<String, Calendar> revisions = new HashMap<>(); 504 VersionIterator iterator = getVersionHistory().getAllVersions(); 505 506 while (iterator.hasNext()) 507 { 508 Version version = iterator.nextVersion(); 509 String name = version.getName(); 510 if (!"jcr:rootVersion".equals(name)) 511 { 512 revisions.put(name, version.getCreated()); 513 } 514 } 515 516 return revisions.entrySet().stream() 517 .sorted(__VERSION_COMPARATOR) 518 .map(Entry::getKey) 519 .toArray(String[]::new); 520 } 521 catch (RepositoryException ex) 522 { 523 throw new AmetysRepositoryException("Unable to get revisions list", ex); 524 } 525 } 526 527 @Override 528 public ModifiableModelLessDataHolder getUnversionedDataHolder() 529 { 530 try 531 { 532 ModifiableRepositoryData repositoryData = new JCRRepositoryData(_baseNode.getNode("ametys-internal:unversioned")); 533 Optional<LockableAmetysObject> lockableAmetysObject = Optional.of(this) 534 .filter(LockableAmetysObject.class::isInstance) 535 .map(LockableAmetysObject.class:: cast); 536 return new DefaultModifiableModelLessDataHolder(_getFactory().getUnversionedDataTypeExtensionPoint(), repositoryData, lockableAmetysObject); 537 } 538 catch (RepositoryException e) 539 { 540 throw new AmetysRepositoryException(e); 541 } 542 } 543 544 public Map<AnonymousOrAnyConnectedKeys, Set<String>> getProfilesForAnonymousAndAnyConnectedUser() 545 { 546 return ACLJCRAmetysObjectHelper.getProfilesForAnonymousAndAnyConnectedUser(getNode()); 547 } 548 549 public Map<GroupIdentity, Map<UserOrGroup, Set<String>>> getProfilesForGroups(Set<GroupIdentity> groups) 550 { 551 return ACLJCRAmetysObjectHelper.getProfilesForGroups(getNode(), groups); 552 } 553 554 public Map<UserIdentity, Map<UserOrGroup, Set<String>>> getProfilesForUsers(UserIdentity user) 555 { 556 return ACLJCRAmetysObjectHelper.getProfilesForUsers(getNode(), user); 557 } 558 559 public void addAllowedProfilesForAnyConnectedUser(Set<String> profileIds) 560 { 561 ACLJCRAmetysObjectHelper.addAllowedProfilesForAnyConnectedUser(getNode(), profileIds); 562 } 563 564 public void removeAllowedProfilesForAnyConnectedUser(Set<String> profileIds) 565 { 566 ACLJCRAmetysObjectHelper.removeAllowedProfilesForAnyConnectedUser(getNode(), profileIds); 567 } 568 569 public void addDeniedProfilesForAnyConnectedUser(Set<String> profileIds) 570 { 571 ACLJCRAmetysObjectHelper.addDeniedProfilesForAnyConnectedUser(getNode(), profileIds); 572 } 573 574 public void removeDeniedProfilesForAnyConnectedUser(Set<String> profileIds) 575 { 576 ACLJCRAmetysObjectHelper.removeDeniedProfilesForAnyConnectedUser(getNode(), profileIds); 577 } 578 579 public void addAllowedProfilesForAnonymous(Set<String> profileIds) 580 { 581 ACLJCRAmetysObjectHelper.addAllowedProfilesForAnonymous(getNode(), profileIds); 582 } 583 584 public void removeAllowedProfilesForAnonymous(Set<String> profileIds) 585 { 586 ACLJCRAmetysObjectHelper.removeAllowedProfilesForAnonymous(getNode(), profileIds); 587 } 588 589 public void addDeniedProfilesForAnonymous(Set<String> profileIds) 590 { 591 ACLJCRAmetysObjectHelper.addDeniedProfilesForAnonymous(getNode(), profileIds); 592 } 593 594 public void removeDeniedProfilesForAnonymous(Set<String> profileIds) 595 { 596 ACLJCRAmetysObjectHelper.removeDeniedProfilesForAnonymous(getNode(), profileIds); 597 } 598 599 public void addAllowedUsers(Set<UserIdentity> users, String profileId) 600 { 601 ACLJCRAmetysObjectHelper.addAllowedUsers(users, getNode(), profileId); 602 } 603 604 public void removeAllowedUsers(Set<UserIdentity> users, String profileId) 605 { 606 ACLJCRAmetysObjectHelper.removeAllowedUsers(users, getNode(), profileId); 607 } 608 609 public void removeAllowedUsers(Set<UserIdentity> users) 610 { 611 ACLJCRAmetysObjectHelper.removeAllowedUsers(users, getNode()); 612 } 613 614 public void addAllowedGroups(Set<GroupIdentity> groups, String profileId) 615 { 616 ACLJCRAmetysObjectHelper.addAllowedGroups(groups, getNode(), profileId); 617 } 618 619 public void removeAllowedGroups(Set<GroupIdentity> groups, String profileId) 620 { 621 ACLJCRAmetysObjectHelper.removeAllowedGroups(groups, getNode(), profileId); 622 } 623 624 public void removeAllowedGroups(Set<GroupIdentity> groups) 625 { 626 ACLJCRAmetysObjectHelper.removeAllowedGroups(groups, getNode()); 627 } 628 629 public void addDeniedUsers(Set<UserIdentity> users, String profileId) 630 { 631 ACLJCRAmetysObjectHelper.addDeniedUsers(users, getNode(), profileId); 632 } 633 634 public void removeDeniedUsers(Set<UserIdentity> users, String profileId) 635 { 636 ACLJCRAmetysObjectHelper.removeDeniedUsers(users, getNode(), profileId); 637 } 638 639 public void removeDeniedUsers(Set<UserIdentity> users) 640 { 641 ACLJCRAmetysObjectHelper.removeDeniedUsers(users, getNode()); 642 } 643 644 public void addDeniedGroups(Set<GroupIdentity> groups, String profileId) 645 { 646 ACLJCRAmetysObjectHelper.addDeniedGroups(groups, getNode(), profileId); 647 } 648 649 public void removeDeniedGroups(Set<GroupIdentity> groups, String profileId) 650 { 651 ACLJCRAmetysObjectHelper.removeDeniedGroups(groups, getNode(), profileId); 652 } 653 654 public void removeDeniedGroups(Set<GroupIdentity> groups) 655 { 656 ACLJCRAmetysObjectHelper.removeDeniedGroups(groups, getNode()); 657 } 658 659 public boolean isInheritanceDisallowed() 660 { 661 return ACLJCRAmetysObjectHelper.isInheritanceDisallowed(getNode()); 662 } 663 664 public void disallowInheritance(boolean disallow) 665 { 666 ACLJCRAmetysObjectHelper.disallowInheritance(getNode(), disallow); 667 } 668}