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