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