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