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 "ametysicon-share40 decorator-ametysicon-sign-caution a-tree-decorator-error-color";
226        }
227        
228        String productName = _session.getRepositoryInfo().getProductName();
229        if (productName.toLowerCase().indexOf("alfresco") != -1)
230        {
231            // FIXME EXPLORER-494 Use a dedicated Alfresco glyph
232            return "ametysicon-share40";
233        }
234        else if (productName.toLowerCase().indexOf("nuxeo") != -1)
235        {
236            // FIXME EXPLORER-494 Use a dedicated Nuxeo glyph
237            return "ametysicon-share40";
238        }
239        
240        // FIXME EXPLORER-494 Use a dedicated CMIS glyph
241        return "ametysicon-share40";
242    }
243
244    @Override
245    public String getApplicationId()
246    {
247        return APPLICATION_ID;
248    }
249
250    @Override
251    public String getName() throws AmetysRepositoryException
252    {
253        return _name;
254    }
255
256    @Override
257    public String getParentPath() throws AmetysRepositoryException
258    {
259        if (_parentPath == null)
260        {
261            _parentPath = getParent().getPath();
262        }
263        
264        return _parentPath;
265    }
266
267    @Override
268    public String getPath() throws AmetysRepositoryException
269    {
270        return getParentPath() + "/" + getName();
271    }
272    
273    @Override
274    public Node getNode()
275    {
276        return _node;
277    }
278    
279    @Override
280    public String getId()
281    {
282        try
283        {
284            return _factory.getScheme() + "://" + _node.getIdentifier();
285        }
286        catch (RepositoryException e)
287        {
288            throw new AmetysRepositoryException("Unable to get node UUID", e);
289        }
290    }
291    
292    public boolean hasChildResources() 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    public boolean hasChildExplorerNodes() throws AmetysRepositoryException
300    {
301        // we don't actually know if there are children or not, 
302        // but it's an optimization to don't make another CMIS request
303        return true;
304    }
305    
306    @Override
307    public void rename(String newName) throws AmetysRepositoryException
308    {
309        try
310        {
311            getNode().getSession().move(getNode().getPath(), getNode().getParent().getPath() + "/" + newName);
312        }
313        catch (RepositoryException e)
314        {
315            throw new AmetysRepositoryException(e);
316        }
317    }
318
319    @Override
320    public void remove() throws AmetysRepositoryException, RepositoryIntegrityViolationException
321    {
322        try
323        {
324            getNode().remove();
325        }
326        catch (ConstraintViolationException e)
327        {
328            throw new RepositoryIntegrityViolationException(e);
329        }
330        catch (RepositoryException e)
331        {
332            throw new AmetysRepositoryException(e);
333        }
334    }
335
336    @SuppressWarnings("unchecked")
337    @Override
338    public <A extends AmetysObject> A getParent() throws AmetysRepositoryException
339    {
340        return (A) _factory.getParent(this);
341    }
342
343    @Override
344    public void saveChanges() throws AmetysRepositoryException
345    {
346        try
347        {
348            getNode().getSession().save();
349        }
350        catch (javax.jcr.RepositoryException e)
351        {
352            throw new AmetysRepositoryException("Unable to save changes", e);
353        }
354    }
355    
356    @Override
357    public void revertChanges() throws AmetysRepositoryException
358    {
359        try
360        {
361            getNode().refresh(false);
362        }
363        catch (javax.jcr.RepositoryException e)
364        {
365            throw new AmetysRepositoryException("Unable to revert changes.", e);
366        }
367    }
368    
369    @Override
370    public boolean needsSave() throws AmetysRepositoryException
371    {
372        try
373        {
374            return _node.getSession().hasPendingChanges();
375        }
376        catch (RepositoryException e)
377        {
378            throw new AmetysRepositoryException(e);
379        }
380    }
381    
382    @Override
383    public String getResourcePath() throws AmetysRepositoryException
384    {
385        return getExplorerPath();
386    }
387    
388    @Override
389    public String getExplorerPath()
390    {
391        AmetysObject parent = getParent();
392        
393        if (parent instanceof ExplorerNode)
394        {
395            return ((ExplorerNode) parent).getExplorerPath() + "/" + getName();
396        }
397        else
398        {
399            return "";
400        }
401    }
402    
403    /**
404     * Get the user to connect to CMIS repository
405     * @return the user login
406     * @throws AmetysRepositoryException if an error occurred
407     */
408    public String getUser() throws AmetysRepositoryException
409    {
410        return getMetadataHolder().getString(METADATA_USER);
411    }
412    
413    /**
414     * Get the password to connect to CMIS repository
415     * @return the user password
416     * @throws AmetysRepositoryException if an error occurred
417     */
418    public String getPassword() throws AmetysRepositoryException
419    {
420        return getMetadataHolder().getString(METADATA_PASSWORD);
421    }
422    
423    /**
424     * Get the CMIS repository URL
425     * @return the CMIS repository URL
426     * @throws AmetysRepositoryException if an error occurred
427     */
428    public String getRepositoryUrl() throws AmetysRepositoryException
429    {
430        return getMetadataHolder().getString(METADATA_REPOSITORY_URL);
431    }
432    
433    /**
434     * Get the CMIS repository id
435     * @return the CMIS repository id
436     * @throws AmetysRepositoryException if an error occurred
437     */
438    public String getRepositoryId () throws AmetysRepositoryException
439    {
440        return getMetadataHolder().getString(METADATA_REPOSITORY_ID);
441    }
442    
443    /**
444     * Set the URL of the CMIS repository
445     * @param url the CMIS repository URL
446     * @throws AmetysRepositoryException if an error occurred
447     */
448    public void setRepositoryUrl(String url) throws AmetysRepositoryException
449    {
450        getMetadataHolder().setMetadata(METADATA_REPOSITORY_URL, url);
451    }
452    
453    /**
454     * Set the id of the CMIS repository
455     * @param id the CMIS repository id
456     * @throws AmetysRepositoryException if an error occurred
457     */
458    public void setRepositoryId(String id) throws AmetysRepositoryException
459    {
460        getMetadataHolder().setMetadata(METADATA_REPOSITORY_ID, id);
461    }
462        
463    /**
464     * Set a user name for the CMIS Repository
465     * @param user the login
466     */
467    public void setUser(String user) 
468    {
469        getMetadataHolder().setMetadata(METADATA_USER, user);
470    }
471
472    /**
473     * Set a password for the CMIS Repository
474     * @param password the password
475     */
476    public void setPassword(String password) 
477    {
478        getMetadataHolder().setMetadata(METADATA_PASSWORD, password);
479    }
480
481    @Override
482    public String getDescription()
483    {
484        return null;
485    }
486}