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