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