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