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.logger.AbstractLogEnabled; 034import org.apache.avalon.framework.service.ServiceException; 035import org.apache.avalon.framework.service.ServiceManager; 036import org.apache.avalon.framework.service.Serviceable; 037import org.apache.chemistry.opencmis.client.api.CmisObject; 038import org.apache.chemistry.opencmis.client.api.Document; 039import org.apache.chemistry.opencmis.client.api.Folder; 040import org.apache.chemistry.opencmis.client.api.ObjectId; 041import org.apache.chemistry.opencmis.client.api.Session; 042import org.apache.chemistry.opencmis.client.api.SessionFactory; 043import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl; 044import org.apache.chemistry.opencmis.commons.SessionParameter; 045import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; 046import org.apache.chemistry.opencmis.commons.enums.BindingType; 047import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException; 048import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException; 049import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; 050import org.apache.commons.lang.StringUtils; 051 052import org.ametys.core.observation.Event; 053import org.ametys.core.observation.ObservationManager; 054import org.ametys.core.observation.Observer; 055import org.ametys.plugins.explorer.ObservationConstants; 056import org.ametys.plugins.repository.AmetysObject; 057import org.ametys.plugins.repository.AmetysObjectResolver; 058import org.ametys.plugins.repository.AmetysRepositoryException; 059import org.ametys.plugins.repository.RepositoryConstants; 060import org.ametys.plugins.repository.UnknownAmetysObjectException; 061import org.ametys.plugins.repository.jcr.JCRAmetysObjectFactory; 062import org.ametys.plugins.repository.provider.AbstractRepository; 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 } 137 138 root.connect(session, rootFolder); 139 } 140 catch (CmisConnectionException e) 141 { 142 getLogger().error("Connection to CMIS Atom Pub service failed", e); 143 } 144 145 return root; 146 } 147 148 @Override 149 public AmetysObject getAmetysObjectById(String id) throws AmetysRepositoryException 150 { 151 // l'id est de la forme <scheme>://uuid(/<cmis_id) 152 String uuid = id.substring(getScheme().length() + 3); 153 int index = uuid.indexOf("/"); 154 155 if (index != -1) 156 { 157 CMISRootResourcesCollection root = getCMISRootResourceCollection (getScheme() + "://" + uuid.substring(0, index)); 158 Session session = root.getSession(); 159 if (session == null) 160 { 161 throw new UnknownAmetysObjectException("Connection to CMIS server failed"); 162 } 163 164 ObjectId cmisID = session.createObjectId(uuid.substring(index + 1)); 165 CmisObject cmisObject = session.getObject(cmisID); 166 167 BaseTypeId baseTypeId = cmisObject.getBaseTypeId(); 168 169 if (baseTypeId.equals(BaseTypeId.CMIS_FOLDER)) 170 { 171 return new CMISResourcesCollection((Folder) cmisObject, root, null); 172 } 173 else if (baseTypeId.equals(BaseTypeId.CMIS_DOCUMENT)) 174 { 175 Document cmisDoc = (Document) cmisObject; 176 try 177 { 178 // EXPLORER-243 alfresco's id point to a version, not to the real "live" document 179 if (!cmisDoc.isLatestVersion()) 180 { 181 cmisDoc = cmisDoc.getObjectOfLatestVersion(false); 182 } 183 } 184 catch (CmisBaseException e) 185 { 186 // EXPLORER-269 does nothing, nuxeo sometimes throws a CmisRuntimeException here 187 } 188 189 return new CMISResource(cmisDoc, root, null); 190 } 191 else 192 { 193 throw new IllegalArgumentException("Unhandled CMIS type: " + baseTypeId); 194 } 195 } 196 else 197 { 198 return getCMISRootResourceCollection (id); 199 } 200 } 201 202 @Override 203 public AmetysObject getAmetysObjectById(String id, javax.jcr.Session session) throws AmetysRepositoryException, RepositoryException 204 { 205 return getAmetysObjectById(id); 206 } 207 208 /** 209 * Retrieves an {@link CMISRootResourcesCollection}, given its id.<br> 210 * @param id the identifier. 211 * @return the corresponding {@link CMISRootResourcesCollection}. 212 * @throws AmetysRepositoryException if an error occurs. 213 */ 214 protected CMISRootResourcesCollection getCMISRootResourceCollection (String id) throws AmetysRepositoryException 215 { 216 try 217 { 218 Node node = getNode(id); 219 220 if (!node.getPath().startsWith('/' + AmetysObjectResolver.ROOT_REPO)) 221 { 222 throw new AmetysRepositoryException("Cannot resolve a Node outside Ametys tree"); 223 } 224 225 return getAmetysObject(node, null); 226 } 227 catch (RepositoryException e) 228 { 229 throw new AmetysRepositoryException("Unable to get AmetysObject for id: " + id, e); 230 } 231 } 232 233 @Override 234 public boolean hasAmetysObjectForId(String id) throws AmetysRepositoryException 235 { 236 // l'id est de la forme <scheme>://uuid(/<cmis_id) 237 String uuid = id.substring(getScheme().length() + 3); 238 int index = uuid.indexOf("/"); 239 240 if (index != -1) 241 { 242 CMISRootResourcesCollection root = getCMISRootResourceCollection (uuid.substring(0, index)); 243 Session session = root.getSession(); 244 if (session == null) 245 { 246 return false; 247 } 248 249 ObjectId cmisID = session.createObjectId(uuid.substring(index + 1)); 250 251 return session.getObject(cmisID) == null; 252 } 253 else 254 { 255 try 256 { 257 getNode(id); 258 return true; 259 } 260 catch (UnknownAmetysObjectException e) 261 { 262 return false; 263 } 264 } 265 } 266 267 public String getScheme() 268 { 269 return _scheme; 270 } 271 272 public Collection<String> getNodetypes() 273 { 274 return Collections.singletonList(_nodetype); 275 } 276 277 /** 278 * Returns the parent of the given {@link AmetysObject} . 279 * @param object a {@link AmetysObject}. 280 * @return the parent of the given {@link AmetysObject}. 281 * @throws AmetysRepositoryException if an error occurs. 282 */ 283 public AmetysObject getParent(CMISRootResourcesCollection object) throws AmetysRepositoryException 284 { 285 try 286 { 287 Node node = object.getNode(); 288 Node parentNode = node.getParent(); 289 290 return _resolver.resolve(parentNode, false); 291 } 292 catch (RepositoryException e) 293 { 294 throw new AmetysRepositoryException("Unable to retrieve parent object of object " + object.getName(), e); 295 } 296 } 297 298 /** 299 * Returns the JCR Node associated with the given object id.<br> 300 * This implementation assumes that the id is like <code><scheme>://<uuid></code> 301 * @param id the unique id of the object 302 * @return the JCR Node associated with the given id 303 */ 304 protected Node getNode(String id) 305 { 306 // id = <scheme>://<uuid> 307 String uuid = id.substring(getScheme().length() + 3); 308 309 javax.jcr.Session session = null; 310 try 311 { 312 session = _repository.login(); 313 Node node = session.getNodeByIdentifier(uuid); 314 return node; 315 } 316 catch (ItemNotFoundException e) 317 { 318 if (session != null) 319 { 320 session.logout(); 321 } 322 323 throw new UnknownAmetysObjectException("There's no node for id " + id, e); 324 } 325 catch (RepositoryException e) 326 { 327 if (session != null) 328 { 329 session.logout(); 330 } 331 332 throw new AmetysRepositoryException("Unable to get AmetysObject for id: " + id, e); 333 } 334 } 335 336 /** 337 * Opening a Atom Pub Connection 338 * @param root the JCR root folder 339 * @return The created session or <code>null</code> if connection to CMIS server failed 340 */ 341 public Session getAtomPubSession(CMISRootResourcesCollection root) 342 { 343 String rootId = root.getId(); 344 if (_sessionCache.containsKey(rootId)) 345 { 346 return _sessionCache.get(rootId); 347 } 348 349 try 350 { 351 String url = root.getRepositoryUrl(); 352 String user = root.getUser(); 353 String password = root.getPassword(); 354 String repositoryId = root.getRepositoryId(); 355 356 Map<String, String> params = new HashMap<>(); 357 358 // user credentials 359 params.put(SessionParameter.USER, user); 360 params.put(SessionParameter.PASSWORD, password); 361 362 // connection settings 363 params.put(SessionParameter.ATOMPUB_URL, url); 364 params.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value()); 365 366 if (StringUtils.isEmpty(repositoryId)) 367 { 368 SessionFactory f = SessionFactoryImpl.newInstance(); 369 List<org.apache.chemistry.opencmis.client.api.Repository> repositories = f.getRepositories(params); 370 repositoryId = repositories.listIterator().next().getId(); 371 372 // save repository id for next times 373 root.setRepositoryId(repositoryId); 374 root.saveChanges(); 375 } 376 377 params.put(SessionParameter.REPOSITORY_ID, repositoryId); 378 379 // create session 380 SessionFactory f = SessionFactoryImpl.newInstance(); 381 Session session = f.createSession(params); 382 _sessionCache.put(rootId, session); 383 return session; 384 } 385 catch (CmisObjectNotFoundException e) 386 { 387 getLogger().error("Connection to CMIS Atom Pub service failed", e); 388 return null; 389 } 390 catch (CmisConnectionException e) 391 { 392 getLogger().error("CMIS Atom Pub service is unreacheable", e); 393 return null; 394 } 395 catch (CmisBaseException e) 396 { 397 getLogger().error("CMIS Atom Pub service is unreacheable", e); 398 return null; 399 } 400 } 401 402 public int getPriority(Event event) 403 { 404 return Observer.MAX_PRIORITY; 405 } 406 407 public boolean supports(Event event) 408 { 409 String eventType = event.getId(); 410 return ObservationConstants.EVENT_COLLECTION_DELETED.equals(eventType) || ObservationConstants.EVENT_CMIS_COLLECTION_UPDATED.equals(eventType); 411 } 412 413 public void observe(Event event, Map<String, Object> transientVars) throws Exception 414 { 415 String rootId = (String) event.getArguments().get(ObservationConstants.ARGS_ID); 416 if (_sessionCache.containsKey(rootId)) 417 { 418 _sessionCache.remove(rootId); 419 } 420 } 421}