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