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.explorer.cmis; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Map; 021import java.util.Optional; 022 023import javax.jcr.Node; 024import javax.jcr.RepositoryException; 025import javax.jcr.nodetype.ConstraintViolationException; 026 027import org.apache.chemistry.opencmis.client.api.CmisObject; 028import org.apache.chemistry.opencmis.client.api.Document; 029import org.apache.chemistry.opencmis.client.api.Folder; 030import org.apache.chemistry.opencmis.client.api.ItemIterable; 031import org.apache.chemistry.opencmis.client.api.Session; 032import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; 033import org.apache.commons.lang3.StringUtils; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037import org.ametys.plugins.explorer.ExplorerNode; 038import org.ametys.plugins.explorer.resources.ResourceCollection; 039import org.ametys.plugins.repository.AbstractAmetysObject; 040import org.ametys.plugins.repository.AmetysObject; 041import org.ametys.plugins.repository.AmetysRepositoryException; 042import org.ametys.plugins.repository.CollectionIterable; 043import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 044import org.ametys.plugins.repository.UnknownAmetysObjectException; 045import org.ametys.plugins.repository.data.UnknownDataException; 046import org.ametys.plugins.repository.data.holder.ModifiableDataHolder; 047import org.ametys.plugins.repository.data.holder.group.ModifiableComposite; 048import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableDataHolder; 049import org.ametys.plugins.repository.data.holder.values.SynchronizationResult; 050import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 051import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 052import org.ametys.plugins.repository.jcr.JCRAmetysObject; 053import org.ametys.plugins.repository.jcr.SimpleAmetysObjectFactory; 054import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata; 055import org.ametys.plugins.repository.metadata.jcr.JCRCompositeMetadata; 056import org.ametys.runtime.model.exception.BadItemTypeException; 057import org.ametys.runtime.model.exception.NotUniqueTypeException; 058import org.ametys.runtime.model.exception.UnknownTypeException; 059 060/** 061 * {@link AmetysObject} implementing the root of {@link CMISResourcesCollection}s 062 */ 063public class CMISRootResourcesCollection extends AbstractAmetysObject implements JCRAmetysObject, ResourceCollection 064{ 065 /** application id for resources collections. */ 066 public static final String APPLICATION_ID = "Ametys.plugins.explorer.applications.resources.Resources"; 067 068 /** Metadata for repository id */ 069 public static final String DATA_REPOSITORY_ID = "repositoryId"; 070 /** Metadata for repository url */ 071 public static final String DATA_REPOSITORY_URL = "repositoryUrl"; 072 /** Attribute for repository root path */ 073 public static final String DATA_MOUNT_POINT = "mountPoint"; 074 /** Metadata for user login */ 075 public static final String DATA_USER = "user"; 076 /** Metadata for user password */ 077 public static final String DATA_PASSWORD = "password"; 078 079 private static final Logger __LOGGER = LoggerFactory.getLogger(CMISResourcesCollection.class); 080 081 /** The corresponding {@link SimpleAmetysObjectFactory} */ 082 private final CMISTreeFactory _factory; 083 084 private final Node _node; 085 086 private String _name; 087 private String _parentPath; 088 089 private Session _session; 090 private Folder _root; 091 092 /** 093 * Creates a {@link CMISRootResourcesCollection}. 094 * @param node the node backing this {@link AmetysObject} 095 * @param parentPath the parentPath in the Ametys hierarchy 096 * @param factory the CMISRootResourcesCollectionFactory which created this AmetysObject 097 */ 098 public CMISRootResourcesCollection(Node node, String parentPath, CMISTreeFactory factory) 099 { 100 _node = node; 101 _parentPath = parentPath; 102 _factory = factory; 103 104 try 105 { 106 _name = _node.getName(); 107 } 108 catch (RepositoryException e) 109 { 110 throw new AmetysRepositoryException("Unable to get node name", e); 111 } 112 } 113 114 void connect(Session session, Folder root) 115 { 116 _session = session; 117 _root = root; 118 } 119 120 Session getSession() 121 { 122 return _session; 123 } 124 125 Folder getRootFolder() 126 { 127 return _root; 128 } 129 130 @SuppressWarnings("unchecked") 131 @Override 132 public AmetysObject getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException 133 { 134 if (_session == null) 135 { 136 throw new UnknownAmetysObjectException("Failed to connect to CMIS server"); 137 } 138 139 String mountPoint = StringUtils.defaultIfBlank(getMountPoint(), "/"); 140 // ensure mount point start with "/" (Chemistry restriction) 141 if (!mountPoint.startsWith("/")) 142 { 143 mountPoint = "/" + mountPoint; 144 } 145 // add trailing slash if needed 146 if (!mountPoint.endsWith("/")) 147 { 148 mountPoint = mountPoint + "/"; 149 } 150 151 String remotePath = mountPoint + path; 152 // ensure path does not ends with "/" (Chemistry restriction) 153 if (path.endsWith("/") && remotePath.length() != 1) 154 { 155 remotePath = remotePath.substring(0, remotePath.length() - 1); 156 } 157 158 CmisObject entry = _session.getObjectByPath(remotePath); 159 CmisObject object = _session.getObject(entry); 160 161 BaseTypeId baseTypeId = object.getBaseType().getBaseTypeId(); 162 163 if (baseTypeId.equals(BaseTypeId.CMIS_FOLDER)) 164 { 165 return new CMISResourcesCollection((Folder) object, this, null); 166 } 167 else if (baseTypeId.equals(BaseTypeId.CMIS_DOCUMENT)) 168 { 169 return new CMISResource((Document) object, this, null); 170 } 171 else 172 { 173 throw new UnknownAmetysObjectException("Unhandled CMIS type '" + baseTypeId + "', cannot get child at path " + path); 174 } 175 } 176 177 @Override 178 public CollectionIterable<AmetysObject> getChildren() throws AmetysRepositoryException 179 { 180 Collection<AmetysObject> aoChildren = new ArrayList<>(); 181 182 if (_session == null) 183 { 184 return new CollectionIterable<>(aoChildren); 185 } 186 187 ItemIterable<CmisObject> children = _root.getChildren(); 188 189 for (CmisObject child : children) 190 { 191 BaseTypeId typeId = child.getBaseTypeId(); 192 193 if (typeId.equals(BaseTypeId.CMIS_FOLDER)) 194 { 195 aoChildren.add(new CMISResourcesCollection((Folder) child, this, this)); 196 } 197 else if (typeId.equals(BaseTypeId.CMIS_DOCUMENT)) 198 { 199 Document cmisDoc = (Document) child; 200 201 // Check if CMIS document has content, if not ignore it 202 if (StringUtils.isNotEmpty(cmisDoc.getContentStreamFileName())) 203 { 204 aoChildren.add(new CMISResource(cmisDoc, this, this)); 205 } 206 } 207 else 208 { 209 __LOGGER.warn("Unhandled CMIS type {}. It will be ignored.", typeId); 210 } 211 } 212 213 return new CollectionIterable<>(aoChildren); 214 } 215 216 @Override 217 public boolean hasChild(String name) throws AmetysRepositoryException 218 { 219 if (_session == null) 220 { 221 return false; 222 } 223 224 ItemIterable<CmisObject> children = _root.getChildren(); 225 for (CmisObject child : children) 226 { 227 if (child.getName().equals(name)) 228 { 229 return true; 230 } 231 } 232 233 return false; 234 } 235 236 @Override 237 protected ModifiableDataHolder getDataHolder() 238 { 239 ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode()); 240 return new DefaultModifiableDataHolder(_factory.getDataTypesExtensionPoint(), repositoryData); 241 } 242 243 @Override 244 public ModifiableCompositeMetadata getMetadataHolder() 245 { 246 return new JCRCompositeMetadata(getNode(), _factory._resolver); 247 } 248 249 @Override 250 public String getIconCls() 251 { 252 if (_session == null) 253 { 254 return "ametysicon-share40 decorator-ametysicon-sign-caution a-tree-decorator-error-color"; 255 } 256 257 String productName = _session.getRepositoryInfo().getProductName(); 258 if (productName.toLowerCase().indexOf("alfresco") != -1) 259 { 260 // FIXME EXPLORER-494 Use a dedicated Alfresco glyph 261 return "ametysicon-share40"; 262 } 263 else if (productName.toLowerCase().indexOf("nuxeo") != -1) 264 { 265 // FIXME EXPLORER-494 Use a dedicated Nuxeo glyph 266 return "ametysicon-share40"; 267 } 268 269 // FIXME EXPLORER-494 Use a dedicated CMIS glyph 270 return "ametysicon-share40"; 271 } 272 273 @Override 274 public String getApplicationId() 275 { 276 return APPLICATION_ID; 277 } 278 279 @Override 280 public String getName() throws AmetysRepositoryException 281 { 282 return _name; 283 } 284 285 @Override 286 public String getParentPath() throws AmetysRepositoryException 287 { 288 if (_parentPath == null) 289 { 290 _parentPath = getParent().getPath(); 291 } 292 293 return _parentPath; 294 } 295 296 @Override 297 public String getPath() throws AmetysRepositoryException 298 { 299 return getParentPath() + "/" + getName(); 300 } 301 302 @Override 303 public Node getNode() 304 { 305 return _node; 306 } 307 308 @Override 309 public String getId() 310 { 311 try 312 { 313 return _factory.getScheme() + "://" + _node.getIdentifier(); 314 } 315 catch (RepositoryException e) 316 { 317 throw new AmetysRepositoryException("Unable to get node UUID", e); 318 } 319 } 320 321 public boolean hasChildResources() throws AmetysRepositoryException 322 { 323 // we don't actually know if there are children or not, 324 // but it's an optimization to don't make another CMIS request 325 return true; 326 } 327 328 public boolean hasChildExplorerNodes() throws AmetysRepositoryException 329 { 330 // we don't actually know if there are children or not, 331 // but it's an optimization to don't make another CMIS request 332 return true; 333 } 334 335 @Override 336 public void rename(String newName) throws AmetysRepositoryException 337 { 338 try 339 { 340 getNode().getSession().move(getNode().getPath(), getNode().getParent().getPath() + "/" + newName); 341 } 342 catch (RepositoryException e) 343 { 344 throw new AmetysRepositoryException(e); 345 } 346 } 347 348 @Override 349 public void remove() throws AmetysRepositoryException, RepositoryIntegrityViolationException 350 { 351 try 352 { 353 getNode().remove(); 354 } 355 catch (ConstraintViolationException e) 356 { 357 throw new RepositoryIntegrityViolationException(e); 358 } 359 catch (RepositoryException e) 360 { 361 throw new AmetysRepositoryException(e); 362 } 363 } 364 365 @SuppressWarnings("unchecked") 366 @Override 367 public <A extends AmetysObject> A getParent() throws AmetysRepositoryException 368 { 369 return (A) _factory.getParent(this); 370 } 371 372 @Override 373 public void saveChanges() throws AmetysRepositoryException 374 { 375 try 376 { 377 getNode().getSession().save(); 378 } 379 catch (javax.jcr.RepositoryException e) 380 { 381 throw new AmetysRepositoryException("Unable to save changes", e); 382 } 383 } 384 385 @Override 386 public void revertChanges() throws AmetysRepositoryException 387 { 388 try 389 { 390 getNode().refresh(false); 391 } 392 catch (javax.jcr.RepositoryException e) 393 { 394 throw new AmetysRepositoryException("Unable to revert changes.", e); 395 } 396 } 397 398 @Override 399 public boolean needsSave() throws AmetysRepositoryException 400 { 401 try 402 { 403 return _node.getSession().hasPendingChanges(); 404 } 405 catch (RepositoryException e) 406 { 407 throw new AmetysRepositoryException(e); 408 } 409 } 410 411 @Override 412 public String getResourcePath() throws AmetysRepositoryException 413 { 414 return getExplorerPath(); 415 } 416 417 @Override 418 public String getExplorerPath() 419 { 420 AmetysObject parent = getParent(); 421 422 if (parent instanceof ExplorerNode) 423 { 424 return ((ExplorerNode) parent).getExplorerPath() + "/" + getName(); 425 } 426 else 427 { 428 return ""; 429 } 430 } 431 432 /** 433 * Get the user to connect to CMIS repository 434 * @return the user login 435 * @throws AmetysRepositoryException if an error occurred 436 */ 437 public String getUser() throws AmetysRepositoryException 438 { 439 return getValue(DATA_USER); 440 } 441 442 /** 443 * Get the password to connect to CMIS repository 444 * @return the user password 445 * @throws AmetysRepositoryException if an error occurred 446 */ 447 public String getPassword() throws AmetysRepositoryException 448 { 449 return getValue(DATA_PASSWORD); 450 } 451 452 /** 453 * Get the CMIS repository URL 454 * @return the CMIS repository URL 455 * @throws AmetysRepositoryException if an error occurred 456 */ 457 public String getRepositoryUrl() throws AmetysRepositoryException 458 { 459 return getValue(DATA_REPOSITORY_URL); 460 } 461 462 /** 463 * Get the CMIS repository id 464 * @return the CMIS repository id 465 * @throws AmetysRepositoryException if an error occurred 466 */ 467 public String getRepositoryId () throws AmetysRepositoryException 468 { 469 return getValue(DATA_REPOSITORY_ID); 470 } 471 472 /** 473 * Get the CMIS mount point 474 * @return the mount point 475 * @throws AmetysRepositoryException if an error occurred 476 */ 477 public String getMountPoint() throws AmetysRepositoryException 478 { 479 return getValue(DATA_MOUNT_POINT); 480 } 481 482 /** 483 * Set the URL of the CMIS repository 484 * @param url the CMIS repository URL 485 * @throws AmetysRepositoryException if an error occurred 486 */ 487 public void setRepositoryUrl(String url) throws AmetysRepositoryException 488 { 489 setValue(DATA_REPOSITORY_URL, url); 490 } 491 492 /** 493 * Set the id of the CMIS repository 494 * @param id the CMIS repository id 495 * @throws AmetysRepositoryException if an error occurred 496 */ 497 public void setRepositoryId(String id) throws AmetysRepositoryException 498 { 499 setValue(DATA_REPOSITORY_ID, id); 500 } 501 502 /** 503 * Set a mount point for the CMIS Repository 504 * @param mountPoint the mount point path 505 */ 506 public void setMountPoint(String mountPoint) 507 { 508 setValue(DATA_MOUNT_POINT, mountPoint); 509 } 510 511 /** 512 * Set a user name for the CMIS Repository 513 * @param user the login 514 */ 515 public void setUser(String user) 516 { 517 setValue(DATA_USER, user); 518 } 519 520 /** 521 * Set a password for the CMIS Repository 522 * @param password the password 523 */ 524 public void setPassword(String password) 525 { 526 setValue(DATA_PASSWORD, password); 527 } 528 529 @Override 530 public String getDescription() 531 { 532 return null; 533 } 534 535 @Override 536 public ModifiableComposite getComposite(String compositePath) throws IllegalArgumentException, UnknownTypeException, BadItemTypeException 537 { 538 return getDataHolder().getComposite(compositePath); 539 } 540 541 public ModifiableComposite getComposite(String compositePath, boolean createNew) throws IllegalArgumentException, UnknownTypeException, BadItemTypeException 542 { 543 return getDataHolder().getComposite(compositePath, createNew); 544 } 545 546 public <T extends SynchronizationResult> T synchronizeValues(Map<String, Object> values) throws UnknownTypeException, NotUniqueTypeException, BadItemTypeException 547 { 548 return getDataHolder().synchronizeValues(values); 549 } 550 551 public void setValue(String dataPath, Object value) throws IllegalArgumentException, UnknownDataException, UnknownTypeException, NotUniqueTypeException, BadItemTypeException 552 { 553 getDataHolder().setValue(dataPath, value); 554 } 555 556 public void setValue(String dataPath, Object value, String dataType) throws IllegalArgumentException, UnknownDataException, UnknownTypeException, BadItemTypeException 557 { 558 getDataHolder().setValue(dataPath, value, dataType); 559 } 560 561 public void removeValue(String dataPath) throws IllegalArgumentException, UnknownTypeException, BadItemTypeException 562 { 563 getDataHolder().removeValue(dataPath); 564 } 565 566 @Override 567 public ModifiableRepositoryData getRepositoryData() 568 { 569 return getDataHolder().getRepositoryData(); 570 } 571 572 @Override 573 public Optional<? extends ModifiableDataHolder> getParentDataHolder() 574 { 575 return getDataHolder().getParentDataHolder(); 576 } 577 578 @Override 579 public ModifiableDataHolder getRootDataHolder() 580 { 581 return getDataHolder().getRootDataHolder(); 582 } 583}