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