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.Collection; 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023 024import javax.jcr.ItemNotFoundException; 025import javax.jcr.Node; 026import javax.jcr.Repository; 027import javax.jcr.RepositoryException; 028 029import org.apache.avalon.framework.activity.Initializable; 030import org.apache.avalon.framework.configuration.Configurable; 031import org.apache.avalon.framework.configuration.Configuration; 032import org.apache.avalon.framework.configuration.ConfigurationException; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.avalon.framework.service.Serviceable; 036import org.apache.chemistry.opencmis.client.api.CmisObject; 037import org.apache.chemistry.opencmis.client.api.Document; 038import org.apache.chemistry.opencmis.client.api.Folder; 039import org.apache.chemistry.opencmis.client.api.ObjectId; 040import org.apache.chemistry.opencmis.client.api.Session; 041import org.apache.chemistry.opencmis.client.api.SessionFactory; 042import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl; 043import org.apache.chemistry.opencmis.commons.SessionParameter; 044import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; 045import org.apache.chemistry.opencmis.commons.enums.BindingType; 046import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException; 047import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException; 048import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; 049import org.apache.commons.lang.StringUtils; 050 051import org.ametys.core.observation.Event; 052import org.ametys.core.observation.ObservationManager; 053import org.ametys.core.observation.Observer; 054import org.ametys.plugins.explorer.ObservationConstants; 055import org.ametys.plugins.repository.AmetysObject; 056import org.ametys.plugins.repository.AmetysObjectResolver; 057import org.ametys.plugins.repository.AmetysRepositoryException; 058import org.ametys.plugins.repository.RepositoryConstants; 059import org.ametys.plugins.repository.UnknownAmetysObjectException; 060import org.ametys.plugins.repository.jcr.JCRAmetysObjectFactory; 061import org.ametys.plugins.repository.provider.AbstractRepository; 062import org.ametys.runtime.plugin.component.AbstractLogEnabled; 063 064/** 065 * Create the Root of CMIS Resources Collections 066 */ 067public class CMISTreeFactory extends AbstractLogEnabled implements JCRAmetysObjectFactory<AmetysObject>, Configurable, Serviceable, Initializable, Observer 068{ 069 /** Nodetype for resources collection */ 070 public static final String CMIS_ROOT_COLLECTION_NODETYPE = RepositoryConstants.NAMESPACE_PREFIX + ":cmis-root-collection"; 071 072 /** The application {@link AmetysObjectResolver} */ 073 protected AmetysObjectResolver _resolver; 074 075 /** The configured scheme */ 076 protected String _scheme; 077 078 /** The configured nodetype */ 079 protected String _nodetype; 080 081 /** JCR Repository */ 082 protected Repository _repository; 083 084 private ObservationManager _observationManager; 085 086 private Map<String, Session> _sessionCache = new HashMap<>(); 087 088 @Override 089 public void service(ServiceManager manager) throws ServiceException 090 { 091 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 092 _repository = (Repository) manager.lookup(AbstractRepository.ROLE); 093 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 094 } 095 096 @Override 097 public void configure(Configuration configuration) throws ConfigurationException 098 { 099 _scheme = configuration.getChild("scheme").getValue(); 100 101 Configuration[] nodetypesConf = configuration.getChildren("nodetype"); 102 103 if (nodetypesConf.length != 1) 104 { 105 throw new ConfigurationException("A SimpleAmetysObjectFactory must have one and only one associated nodetype. " 106 + "The '" + configuration.getAttribute("id") + "' component has " + nodetypesConf.length); 107 } 108 109 _nodetype = nodetypesConf[0].getValue(); 110 } 111 112 public void initialize() throws Exception 113 { 114 _observationManager.registerObserver(this); 115 } 116 117 @Override 118 public CMISRootResourcesCollection getAmetysObject(Node node, String parentPath) throws AmetysRepositoryException, RepositoryException 119 { 120 CMISRootResourcesCollection root = new CMISRootResourcesCollection(node, parentPath, this); 121 122 if (!root.getMetadataHolder().hasMetadata(CMISRootResourcesCollection.METADATA_REPOSITORY_URL)) 123 { 124 // Object just created, can't connect right now 125 return root; 126 } 127 128 try 129 { 130 Session session = getAtomPubSession(root); 131 132 Folder rootFolder = null; 133 if (session != null) 134 { 135 rootFolder = session.getRootFolder(); 136 root.connect(session, rootFolder); 137 } 138 } 139 catch (CmisConnectionException e) 140 { 141 getLogger().error("Connection to CMIS Atom Pub service failed", e); 142 } 143 catch (CmisObjectNotFoundException e) 144 { 145 getLogger().error("The CMIS Atom Pub service url refers to a non-existent repository", e); 146 } 147 catch (CmisBaseException e) 148 { 149 // all others CMIS errors 150 getLogger().error("An error occured during call of CMIS Atom Pub service", e); 151 } 152 153 return root; 154 } 155 156 @Override 157 public AmetysObject getAmetysObjectById(String id) throws AmetysRepositoryException 158 { 159 // l'id est de la forme <scheme>://uuid(/<cmis_id) 160 String uuid = id.substring(getScheme().length() + 3); 161 int index = uuid.indexOf("/"); 162 163 if (index != -1) 164 { 165 CMISRootResourcesCollection root = getCMISRootResourceCollection (getScheme() + "://" + uuid.substring(0, index)); 166 Session session = root.getSession(); 167 if (session == null) 168 { 169 throw new UnknownAmetysObjectException("Connection to CMIS server failed"); 170 } 171 172 ObjectId cmisID = session.createObjectId(uuid.substring(index + 1)); 173 CmisObject cmisObject = session.getObject(cmisID); 174 175 BaseTypeId baseTypeId = cmisObject.getBaseTypeId(); 176 177 if (baseTypeId.equals(BaseTypeId.CMIS_FOLDER)) 178 { 179 return new CMISResourcesCollection((Folder) cmisObject, root, null); 180 } 181 else if (baseTypeId.equals(BaseTypeId.CMIS_DOCUMENT)) 182 { 183 Document cmisDoc = (Document) cmisObject; 184 try 185 { 186 // EXPLORER-243 alfresco's id point to a version, not to the real "live" document 187 if (!cmisDoc.isLatestVersion()) 188 { 189 cmisDoc = cmisDoc.getObjectOfLatestVersion(false); 190 } 191 } 192 catch (CmisBaseException e) 193 { 194 // EXPLORER-269 does nothing, nuxeo sometimes throws a CmisRuntimeException here 195 } 196 197 return new CMISResource(cmisDoc, root, null); 198 } 199 else 200 { 201 throw new IllegalArgumentException("Unhandled CMIS type: " + baseTypeId); 202 } 203 } 204 else 205 { 206 return getCMISRootResourceCollection (id); 207 } 208 } 209 210 @Override 211 public AmetysObject getAmetysObjectById(String id, javax.jcr.Session session) throws AmetysRepositoryException, RepositoryException 212 { 213 return getAmetysObjectById(id); 214 } 215 216 /** 217 * Retrieves an {@link CMISRootResourcesCollection}, given its id.<br> 218 * @param id the identifier. 219 * @return the corresponding {@link CMISRootResourcesCollection}. 220 * @throws AmetysRepositoryException if an error occurs. 221 */ 222 protected CMISRootResourcesCollection getCMISRootResourceCollection (String id) throws AmetysRepositoryException 223 { 224 try 225 { 226 Node node = getNode(id); 227 228 if (!node.getPath().startsWith('/' + AmetysObjectResolver.ROOT_REPO)) 229 { 230 throw new AmetysRepositoryException("Cannot resolve a Node outside Ametys tree"); 231 } 232 233 return getAmetysObject(node, null); 234 } 235 catch (RepositoryException e) 236 { 237 throw new AmetysRepositoryException("Unable to get AmetysObject for id: " + id, e); 238 } 239 } 240 241 @Override 242 public boolean hasAmetysObjectForId(String id) throws AmetysRepositoryException 243 { 244 // l'id est de la forme <scheme>://uuid(/<cmis_id) 245 String uuid = id.substring(getScheme().length() + 3); 246 int index = uuid.indexOf("/"); 247 248 if (index != -1) 249 { 250 CMISRootResourcesCollection root = getCMISRootResourceCollection (uuid.substring(0, index)); 251 Session session = root.getSession(); 252 if (session == null) 253 { 254 return false; 255 } 256 257 ObjectId cmisID = session.createObjectId(uuid.substring(index + 1)); 258 259 return session.getObject(cmisID) == null; 260 } 261 else 262 { 263 try 264 { 265 getNode(id); 266 return true; 267 } 268 catch (UnknownAmetysObjectException e) 269 { 270 return false; 271 } 272 } 273 } 274 275 public String getScheme() 276 { 277 return _scheme; 278 } 279 280 public Collection<String> getNodetypes() 281 { 282 return Collections.singletonList(_nodetype); 283 } 284 285 /** 286 * Returns the parent of the given {@link AmetysObject} . 287 * @param object a {@link AmetysObject}. 288 * @return the parent of the given {@link AmetysObject}. 289 * @throws AmetysRepositoryException if an error occurs. 290 */ 291 public AmetysObject getParent(CMISRootResourcesCollection object) throws AmetysRepositoryException 292 { 293 try 294 { 295 Node node = object.getNode(); 296 Node parentNode = node.getParent(); 297 298 return _resolver.resolve(parentNode, false); 299 } 300 catch (RepositoryException e) 301 { 302 throw new AmetysRepositoryException("Unable to retrieve parent object of object " + object.getName(), e); 303 } 304 } 305 306 /** 307 * Returns the JCR Node associated with the given object id.<br> 308 * This implementation assumes that the id is like <code><scheme>://<uuid></code> 309 * @param id the unique id of the object 310 * @return the JCR Node associated with the given id 311 */ 312 protected Node getNode(String id) 313 { 314 // id = <scheme>://<uuid> 315 String uuid = id.substring(getScheme().length() + 3); 316 317 javax.jcr.Session session = null; 318 try 319 { 320 session = _repository.login(); 321 Node node = session.getNodeByIdentifier(uuid); 322 return node; 323 } 324 catch (ItemNotFoundException e) 325 { 326 if (session != null) 327 { 328 session.logout(); 329 } 330 331 throw new UnknownAmetysObjectException("There's no node for id " + id, e); 332 } 333 catch (RepositoryException e) 334 { 335 if (session != null) 336 { 337 session.logout(); 338 } 339 340 throw new AmetysRepositoryException("Unable to get AmetysObject for id: " + id, e); 341 } 342 } 343 344 /** 345 * Opening a Atom Pub Connection 346 * @param root the JCR root folder 347 * @return The created session or <code>null</code> if connection to CMIS server failed 348 */ 349 public Session getAtomPubSession(CMISRootResourcesCollection root) 350 { 351 String rootId = root.getId(); 352 if (_sessionCache.containsKey(rootId)) 353 { 354 return _sessionCache.get(rootId); 355 } 356 357 String url = root.getRepositoryUrl(); 358 String user = root.getUser(); 359 String password = root.getPassword(); 360 String repositoryId = root.getRepositoryId(); 361 362 try 363 { 364 Map<String, String> params = new HashMap<>(); 365 366 // user credentials 367 params.put(SessionParameter.USER, user); 368 params.put(SessionParameter.PASSWORD, password); 369 370 // connection settings 371 params.put(SessionParameter.ATOMPUB_URL, url); 372 params.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value()); 373 374 if (StringUtils.isEmpty(repositoryId)) 375 { 376 SessionFactory f = SessionFactoryImpl.newInstance(); 377 List<org.apache.chemistry.opencmis.client.api.Repository> repositories = f.getRepositories(params); 378 repositoryId = repositories.listIterator().next().getId(); 379 380 // save repository id for next times 381 root.setRepositoryId(repositoryId); 382 root.saveChanges(); 383 } 384 385 params.put(SessionParameter.REPOSITORY_ID, repositoryId); 386 387 // create session 388 SessionFactory f = SessionFactoryImpl.newInstance(); 389 Session session = f.createSession(params); 390 _sessionCache.put(rootId, session); 391 return session; 392 } 393 catch (CmisConnectionException e) 394 { 395 getLogger().error("Connection to CMIS Atom Pub service ({}) failed", url, e); 396 } 397 catch (CmisObjectNotFoundException e) 398 { 399 getLogger().error("The CMIS Atom Pub service url ({}) refers to a non-existent repository ({})", url, repositoryId, e); 400 } 401 catch (CmisBaseException e) 402 { 403 // all others CMIS errors 404 getLogger().error("An error occured during call of CMIS Atom Pub service ({})", url, e); 405 } 406 407 return null; 408 } 409 410 public int getPriority(Event event) 411 { 412 return Observer.MAX_PRIORITY; 413 } 414 415 public boolean supports(Event event) 416 { 417 String eventType = event.getId(); 418 return ObservationConstants.EVENT_COLLECTION_DELETED.equals(eventType) || ObservationConstants.EVENT_CMIS_COLLECTION_UPDATED.equals(eventType); 419 } 420 421 public void observe(Event event, Map<String, Object> transientVars) throws Exception 422 { 423 String rootId = (String) event.getArguments().get(ObservationConstants.ARGS_ID); 424 if (_sessionCache.containsKey(rootId)) 425 { 426 _sessionCache.remove(rootId); 427 } 428 } 429}