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;
020import java.util.List;
021import java.util.Map;
022import java.util.Optional;
023
024import org.apache.chemistry.opencmis.client.api.CmisObject;
025import org.apache.chemistry.opencmis.client.api.Document;
026import org.apache.chemistry.opencmis.client.api.Folder;
027import org.apache.chemistry.opencmis.client.api.ItemIterable;
028import org.apache.chemistry.opencmis.client.api.Session;
029import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
030import org.apache.commons.lang3.StringUtils;
031import org.apache.commons.lang3.Strings;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034import org.xml.sax.ContentHandler;
035import org.xml.sax.SAXException;
036
037import org.ametys.plugins.explorer.ExplorerNode;
038import org.ametys.plugins.explorer.resources.ResourceCollection;
039import org.ametys.plugins.repository.AmetysObject;
040import org.ametys.plugins.repository.AmetysRepositoryException;
041import org.ametys.plugins.repository.CollectionIterable;
042import org.ametys.plugins.repository.UnknownAmetysObjectException;
043import org.ametys.plugins.repository.data.UnknownDataException;
044import org.ametys.plugins.repository.data.holder.DataHolder;
045import org.ametys.plugins.repository.data.holder.ModifiableDataHolder;
046import org.ametys.plugins.repository.data.holder.group.Composite;
047import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
048import org.ametys.plugins.repository.data.type.ModelItemTypeExtensionPoint;
049import org.ametys.runtime.model.exception.BadItemTypeException;
050import org.ametys.runtime.model.exception.NotUniqueTypeException;
051import org.ametys.runtime.model.exception.UnknownTypeException;
052import org.ametys.runtime.model.type.DataContext;
053import org.ametys.runtime.model.type.ModelItemType;
054
055/**
056 * Implementation of an {@link ExplorerNode}, backed by a CMIS server.<br>
057 */
058public class CMISResourcesCollection implements ResourceCollection
059{
060    /** Application id for resources collections. */
061    public static final String APPLICATION_ID = "Ametys.plugins.explorer.applications.resources.Resources";
062
063    private static final Logger __LOGGER = LoggerFactory.getLogger(CMISResourcesCollection.class);
064    
065    private final Folder _cmisFolder;
066    private final CMISRootResourcesCollection _root;
067    private AmetysObject _parent;
068    
069    /**
070     * Creates a {@link CMISResourcesCollection}
071     * @param folder The CMIS folder
072     * @param root The root of the virtual tree
073     * @param parent the parent {@link AmetysObject} if known
074     */
075    public CMISResourcesCollection(Folder folder, CMISRootResourcesCollection root, AmetysObject parent)
076    {
077        _cmisFolder = folder;
078        _root = root;
079        _parent = parent;
080    }
081    
082    @Override
083    public String getApplicationId()
084    {
085        return APPLICATION_ID;
086    }
087
088    @Override
089    public String getIconCls()
090    {
091        return "ametysicon-folder249";
092    }
093
094    @Override
095    public String getId() throws AmetysRepositoryException
096    {
097        return getCmisRoot().getId() + "/" + getCmisFolder().getId();
098    }
099
100    @Override
101    public String getName() throws AmetysRepositoryException
102    {
103        return getCmisFolder().getName();
104    }
105    
106    /**
107     * Retrieves the {@link CMISRootResourcesCollection}
108     * @return the {@link CMISRootResourcesCollection}
109     */
110    public CMISRootResourcesCollection getCmisRoot ()
111    {
112        return _root;
113    }
114    
115    /**
116     * Retrieves the {@link Folder}
117     * @return the {@link Folder}
118     */
119    public Folder getCmisFolder ()
120    {
121        return _cmisFolder;
122    }
123
124    @SuppressWarnings("unchecked")
125    @Override
126    public AmetysObject getParent() throws AmetysRepositoryException
127    {
128        if (_parent != null)
129        {
130            return _parent;
131        }
132        
133        // iterate on all parents and returns the first valid parent
134        String rootPath = _root.getRootFolder().getPath();
135        String rootId = _root.getRootFolder().getId();
136        for (Folder parent : _cmisFolder.getParents())
137        {
138            // check if the parent is valid by making sure that it is below the root folder
139            // This is necessary to unsure that the parent is included in the mount point
140            if (Strings.CS.contains(parent.getPath(), rootPath))
141            {
142                if (Strings.CS.equals(parent.getId(), rootId))
143                {
144                    _parent = getCmisRoot();
145                }
146                else
147                {
148                    _parent = new CMISResourcesCollection(parent, _root, null);
149                }
150                return _parent;
151            }
152        }
153        
154        throw new AmetysRepositoryException("Failed to retrieve the parent of folder " + getName() + ". This is most likely due to the fact that we couldn't find one in the CMIS mount point.");
155    }
156
157    @Override
158    public String getParentPath() throws AmetysRepositoryException
159    {
160        return getParent().getPath();
161    }
162
163    @Override
164    public String getPath() throws AmetysRepositoryException
165    {
166        return getParentPath() + "/" + getName();
167    }
168    
169    @Override
170    @SuppressWarnings("unchecked")
171    public AmetysObject getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException
172    {
173        Session session = getCmisRoot().getSession();
174        if (session == null)
175        {
176            throw new UnknownAmetysObjectException("Failed to connect to CMIS server");
177        }
178        
179        CmisObject child = session.getObjectByPath(path);
180        BaseTypeId baseTypeId = child.getBaseType().getBaseTypeId();
181        
182        if (baseTypeId.equals(BaseTypeId.CMIS_FOLDER))
183        {
184            return new CMISResourcesCollection((Folder) child, _root, this);
185        }
186        if (baseTypeId.equals(BaseTypeId.CMIS_DOCUMENT))
187        {
188            Document cmisDoc = (Document) child;
189            // Check if CMIS document has content, if not ignore it
190            if (cmisDoc.getContentStream() != null)
191            {
192                return new CMISResource((Document) child, _root, this);
193            }
194            throw new UnknownAmetysObjectException("The CMIS document " + path + " has no content");
195        }
196        else
197        {
198            throw new UnknownAmetysObjectException("Unhandled CMIS type '" + baseTypeId + "', cannot get child at path " + path);
199        }
200    }
201
202    
203    @Override
204    public CollectionIterable<AmetysObject> getChildren() throws AmetysRepositoryException
205    {
206        Collection<AmetysObject> aoChildren = new ArrayList<>();
207        
208        ItemIterable<CmisObject> children = getCmisFolder().getChildren();
209        for (CmisObject child : children)
210        {
211            BaseTypeId baseTypeId = child.getBaseTypeId();
212            if (baseTypeId.equals(BaseTypeId.CMIS_FOLDER))
213            {
214                aoChildren.add(new CMISResourcesCollection((Folder) child, _root, this));
215            }
216            else if (baseTypeId.equals(BaseTypeId.CMIS_DOCUMENT))
217            {
218                Document cmisDoc = (Document) child;
219                
220                // Check if CMIS document has content, if not ignore it
221                if (StringUtils.isNotEmpty(cmisDoc.getContentStreamFileName()))
222                {
223                    aoChildren.add(new CMISResource(cmisDoc, _root, this));
224                }
225            }
226            else
227            {
228                __LOGGER.warn("Unhandled CMIS type {}. It will be ignored.", baseTypeId);
229            }
230        }
231
232        return new CollectionIterable<>(aoChildren);
233    }
234
235    @Override
236    public boolean hasChild(String name) throws AmetysRepositoryException
237    {
238        ItemIterable<CmisObject> children = _cmisFolder.getChildren();
239        for (CmisObject child : children)
240        {
241            if (child.getName().equals(name))
242            {
243                return true;
244            }
245        }
246        
247        return false;
248    }
249    
250    public boolean hasChildResources() throws AmetysRepositoryException
251    {
252        // we don't actually know if there are children or not,
253        // but it's an optimization to don't make another CMIS request
254        return true;
255    }
256    
257    public boolean hasChildExplorerNodes() throws AmetysRepositoryException
258    {
259        // we don't actually know if there are children or not,
260        // but it's an optimization to don't make another CMIS request
261        return true;
262    }
263    
264    @Override
265    public String getExplorerPath()
266    {
267        AmetysObject parent = getParent();
268        
269        if (parent instanceof ExplorerNode)
270        {
271            return ((ExplorerNode) parent).getExplorerPath() + "/" + getName();
272        }
273        else
274        {
275            return "";
276        }
277    }
278    
279    @Override
280    public String getResourcePath() throws AmetysRepositoryException
281    {
282        return getExplorerPath();
283    }
284    
285    @Override
286    public String getDescription()
287    {
288        return null;
289    }
290    
291    // Data holder methods //
292    
293    public Composite getComposite(String compositePath) throws IllegalArgumentException, UnknownTypeException, BadItemTypeException
294    {
295        return null;
296    }
297    
298    public boolean hasValue(String dataPath) throws IllegalArgumentException
299    {
300        return false;
301    }
302    
303    public boolean hasValue(String dataPath, String dataTypeId) throws IllegalArgumentException
304    {
305        return false;
306    }
307    
308    public boolean hasValueOrEmpty(String dataPath) throws IllegalArgumentException
309    {
310        return false;
311    }
312
313    public Collection<String> getDataNames()
314    {
315        return List.of();
316    }
317    
318    public <T> T getValue(String dataPath) throws IllegalArgumentException, UnknownTypeException, NotUniqueTypeException, BadItemTypeException
319    {
320        return null;
321    }
322    
323    public <T> T getValueOrDefault(String dataPath, T defaultValue) throws IllegalArgumentException, UnknownTypeException, NotUniqueTypeException, BadItemTypeException
324    {
325        return null;
326    }
327
328    public <T> T getValueOfType(String dataPath, String dataTypeId) throws IllegalArgumentException, UnknownTypeException, BadItemTypeException
329    {
330        return null;
331    }
332    
333    public <T> T getValueOfTypeOrDefault(String dataPath, String dataTypeId, T defaultValue) throws IllegalArgumentException, UnknownTypeException, BadItemTypeException
334    {
335        return null;
336    }
337    
338    public boolean isMultiple(String dataPath) throws IllegalArgumentException, UnknownDataException, UnknownTypeException, NotUniqueTypeException, BadItemTypeException
339    {
340        return false;
341    }
342    
343    public boolean isMultiple(String dataPath, String dataTypeId) throws IllegalArgumentException, UnknownDataException, UnknownTypeException, BadItemTypeException
344    {
345        return false;
346    }
347    
348    public <X extends ModelItemType> X getType(String dataPath) throws IllegalArgumentException, UnknownDataException, UnknownTypeException, NotUniqueTypeException
349    {
350        return null;
351    }
352    
353    public ModelItemTypeExtensionPoint getModelItemTypeExtensionPoint()
354    {
355        return null;
356    }
357    
358    public void copyTo(ModifiableDataHolder dataHolder) throws UnknownTypeException, NotUniqueTypeException, BadItemTypeException
359    {
360        // Do nothing
361    }
362    
363    public void copyTo(ModifiableDataHolder dataHolder, DataContext context) throws UnknownTypeException, NotUniqueTypeException, BadItemTypeException
364    {
365        // Do nothing
366    }
367    
368    public void dataToSAX(ContentHandler contentHandler, DataContext context) throws SAXException, UnknownTypeException, NotUniqueTypeException, BadItemTypeException
369    {
370        // Do nothing
371    }
372    
373    public void dataToSAX(ContentHandler contentHandler, String dataPath, DataContext context) throws SAXException, UnknownTypeException, NotUniqueTypeException, BadItemTypeException
374    {
375        // Do nothing
376    }
377    
378    public Map<String, Object> dataToJSON(DataContext context) throws UnknownTypeException, NotUniqueTypeException, BadItemTypeException
379    {
380        return null;
381    }
382    
383    public Object dataToJSON(String dataPath, DataContext context) throws UnknownTypeException, NotUniqueTypeException, BadItemTypeException
384    {
385        return null;
386    }
387    
388    public boolean hasDifferences(Map<String, Object> values) throws UnknownTypeException, NotUniqueTypeException, BadItemTypeException
389    {
390        return false;
391    }
392    
393    public RepositoryData getRepositoryData()
394    {
395        return null;
396    }
397    
398    public Optional<? extends DataHolder> getParentDataHolder()
399    {
400        return Optional.empty();
401    }
402    
403    public DataHolder getRootDataHolder()
404    {
405        return null;
406    }
407}