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.resources.generators;
017
018import java.io.IOException;
019
020import org.apache.avalon.framework.service.ServiceException;
021import org.apache.avalon.framework.service.ServiceManager;
022import org.apache.cocoon.ProcessingException;
023import org.apache.cocoon.environment.ObjectModelHelper;
024import org.apache.cocoon.environment.Request;
025import org.apache.cocoon.generation.ServiceableGenerator;
026import org.apache.cocoon.xml.AttributesImpl;
027import org.apache.cocoon.xml.XMLUtils;
028import org.apache.commons.lang3.ArrayUtils;
029import org.xml.sax.SAXException;
030
031import org.ametys.core.user.User;
032import org.ametys.core.user.UserIdentity;
033import org.ametys.core.user.UserManager;
034import org.ametys.core.util.DateUtils;
035import org.ametys.plugins.explorer.ExplorerNode;
036import org.ametys.plugins.explorer.ModifiableExplorerNode;
037import org.ametys.plugins.explorer.resources.Resource;
038import org.ametys.plugins.explorer.resources.ResourceCollection;
039import org.ametys.plugins.repository.AmetysObject;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.plugins.repository.ModifiableAmetysObject;
042import org.ametys.plugins.repository.TraversableAmetysObject;
043
044/**
045 * Generates a subtree of {@link ExplorerNode}.<br>The subnodes are SAXed to a depth of 0 by default.
046 * <ul>
047 *   <li>Depth of -1 means generate all</li>
048 *   <li>0 means only the given node and its children (resources and folders)</li>
049 *   <li>1 means the given node, its children and their children, and so on.</li>
050 * </ul>
051 */
052public class ResourcesExplorerGenerator extends ServiceableGenerator
053{
054    /** Constant for resource collection */
055    public static final String RESOURCE_COLLECTION = "collection";
056    /** Constant for resource */
057    public static final String RESOURCE = "resource";
058    
059    /** The resolver for ametys object */
060    protected AmetysObjectResolver _resolver;
061    /** The user manager */
062    protected UserManager _userManager;
063    
064    @Override
065    public void service(ServiceManager sManager) throws ServiceException
066    {
067        super.service(sManager);
068        
069        _resolver = (AmetysObjectResolver) sManager.lookup(AmetysObjectResolver.ROLE);
070        _userManager = (UserManager) sManager.lookup(UserManager.ROLE);
071    }
072    
073    @Override
074    public void generate() throws IOException, SAXException, ProcessingException
075    {
076        Request request = ObjectModelHelper.getRequest(objectModel);
077        
078        String id = parameters.getParameter("node", request.getParameter("node"));
079        int depth = parameters.getParameterAsInteger("depth", 0);
080        if (depth == -1)
081        {
082            // -1 means no limit
083            depth = Integer.MAX_VALUE;
084        }
085        
086        String[] allowedExtensions = request.getParameterValues("allowedExtensions");
087        
088        ExplorerNode node = _resolver.resolveById(id);
089        
090        contentHandler.startDocument();
091        
092        long t0 = System.currentTimeMillis();
093        
094        XMLUtils.startElement(contentHandler, "Nodes");
095        saxCollection(node, depth, allowedExtensions);
096        XMLUtils.endElement(contentHandler, "Nodes");
097        
098        if (getLogger().isDebugEnabled())
099        {
100            getLogger().debug("SAXed collection " + node.getExplorerPath() + " in " + (System.currentTimeMillis() - t0) + " ms");
101        }
102        
103        contentHandler.endDocument();
104    }
105    
106    /**
107     * SAX a {@link ExplorerNode}
108     * @param node The node to sax
109     * @throws SAXException If an error occurred while saxing
110     */
111    protected void saxCollection (ExplorerNode node) throws SAXException
112    {
113        saxCollection(node, 0, null);
114    }
115
116    /**
117     * SAX a {@link ExplorerNode}
118     * @param node The node to sax
119     * @param saxNode True to wrap the collection with an XML node representing the current node.
120     * @throws SAXException If an error occurred while saxing
121     */
122    protected void saxCollection (ExplorerNode node, boolean saxNode) throws SAXException
123    {
124        saxCollection(node, 0, saxNode, "Node");
125    }
126    
127    /**
128     * SAX a {@link ExplorerNode}
129     * @param node The node to sax
130     * @param saxNode True to wrap the collection with an XML node representing the current node.
131     * @param nodeTag The tag to use
132     * @throws SAXException If an error occurred while saxing
133     */
134    protected void saxCollection (ExplorerNode node, boolean saxNode, String nodeTag) throws SAXException
135    {
136        saxCollection(node, 0, saxNode, nodeTag);
137    }
138    
139    /**
140     * SAX a {@link ExplorerNode}
141     * @param node The node to sax
142     * @param depth The recursive depth to sax
143     * @param saxNode True to wrap the collection with an XML node representing the current node.
144     * @param nodeTag The tag to use
145     * @throws SAXException If an error occurred while saxing
146     */
147    protected void saxCollection(ExplorerNode node, int depth, boolean saxNode, String nodeTag) throws SAXException
148    {
149        if (saxNode)
150        {
151            AttributesImpl childAtts = getExplorerNodeAttributes(node);
152            
153            XMLUtils.startElement(contentHandler, nodeTag, childAtts);
154            saxCollection(node, depth, null);
155            XMLUtils.endElement(contentHandler, nodeTag);
156        }
157
158    }
159    
160    /**
161     * SAX a {@link ExplorerNode}
162     * @param node The node to sax
163     * @param depth The recursive depth to sax
164     * @param allowedExtensions The allowed file extensions (lower-case). Can be null or empty to not filter on file extensions
165     * @throws SAXException If an error occurred while saxing
166     */
167    protected void saxCollection (ExplorerNode node, int depth, String[] allowedExtensions) throws SAXException
168    {
169        // Traverse the child nodes depth >= 0 (we're not in the last level).
170        if (depth >= 0)
171        {
172            for (AmetysObject child : ((TraversableAmetysObject) node).getChildren())
173            {
174                if (child instanceof Resource)
175                {
176                    if (_matchFilter((Resource) child, allowedExtensions))
177                    {
178                        saxResource((Resource) child);
179                    }
180                }
181                else if (child instanceof ExplorerNode)
182                {
183                    saxExplorerNode((ExplorerNode) child, depth);
184                }
185            }
186        }
187    }
188    
189    /**
190     * Determines if the resource matches the allowed extensions
191     * @param resource The resource 
192     * @param allowedExtensions allowed file extensions
193     * @return true if the allowed extensions are empty or null, or if the resource's name matches the allowed extensions
194     */
195    protected boolean _matchFilter(Resource resource, String[] allowedExtensions)
196    {
197        if (allowedExtensions == null || allowedExtensions.length == 0)
198        {
199            // No filter
200            return true;
201        }
202        
203        String filename = resource.getName();
204        String extension = filename.lastIndexOf(".") > 0 ? filename.substring(filename.lastIndexOf(".") + 1) : "";
205        
206        return ArrayUtils.contains(allowedExtensions, extension.toLowerCase());
207    }
208
209    /**
210     * SAX a {@link ExplorerNode}
211     * @param node The explorer node to SAX.
212     * @param depth The recursive depth to sax
213     * @throws SAXException If an error occurred while SAXing
214     */
215    protected void saxExplorerNode(ExplorerNode node, int depth) throws SAXException
216    {
217        saxExplorerNode(node, depth, null);
218    }
219    
220    /**
221     * SAX a {@link ExplorerNode}
222     * @param node The explorer node to SAX.
223     * @param depth The recursive depth to sax
224     * @param allowedExtensions allowed file extensions
225     * @throws SAXException If an error occurred while SAXing
226     */
227    protected void saxExplorerNode(ExplorerNode node, int depth, String[] allowedExtensions) throws SAXException
228    {
229        AttributesImpl childAtts = getExplorerNodeAttributes(node);
230        
231        XMLUtils.startElement(contentHandler, "Node", childAtts);
232        saxCollection(node, depth - 1, allowedExtensions);
233        XMLUtils.endElement(contentHandler, "Node");
234    }
235    
236    /**
237     * Returns the attributes corresponding to an Explorer node.
238     * @param node The explorer node to SAX.
239     * @return the attributes
240     */
241    protected AttributesImpl getExplorerNodeAttributes(ExplorerNode node)
242    {
243        AttributesImpl childAtts = new AttributesImpl();
244        
245        childAtts.addCDATAAttribute("id", node.getId());
246        childAtts.addCDATAAttribute("name", node.getName());
247        
248        String iconCls = node.getIconCls();
249        if (iconCls != null)
250        {
251            childAtts.addCDATAAttribute("iconCls", iconCls);
252        }
253        childAtts.addCDATAAttribute("applicationId", node.getApplicationId());
254        
255        String relPath = node.getExplorerPath();
256        childAtts.addCDATAAttribute("path", relPath.startsWith("/") ? relPath.substring(1) : relPath);
257        childAtts.addCDATAAttribute("type", RESOURCE_COLLECTION);
258        
259        boolean hasResources = false;
260        if (node instanceof ResourceCollection)
261        {
262            hasResources = ((ResourceCollection) node).hasChildResources();
263        }
264        boolean hasChildNodes = node.hasChildExplorerNodes();
265        
266        if (hasChildNodes)
267        {
268            childAtts.addCDATAAttribute("hasChildNodes", "true");
269        }
270        
271        if (hasResources)
272        {
273            childAtts.addCDATAAttribute("hasResources", "true");
274        }
275        
276        if (node instanceof ModifiableAmetysObject)
277        {
278            childAtts.addCDATAAttribute("isModifiable", "true");
279        }
280        
281        if (node instanceof ModifiableExplorerNode)
282        {
283            childAtts.addCDATAAttribute("canCreateChild", "true");
284        }
285        
286        return childAtts;
287    }
288    
289    /**
290     * SAX a {@link Resource}
291     * @param resource The resource to SAX
292     * @throws SAXException If an error occurred while SAXing
293     */
294    protected void saxResource (Resource resource) throws SAXException
295    {
296        AttributesImpl childAtts = getResourceAttributes(resource);
297        
298        XMLUtils.createElement(contentHandler, "Node", childAtts);
299    }
300    
301    /**
302     * Returns the attributes corresponding to a Resource.
303     * @param resource The resource to SAX
304     * @return the attributes
305     */
306    protected AttributesImpl getResourceAttributes(Resource resource)
307    {
308        UserIdentity userIdentity = resource.getCreator();
309        User user = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin());
310        String name = user == null ? userIdentity.getLogin() : user.getFullName() + " (" + userIdentity.getLogin() + ")"; 
311        
312        AttributesImpl childAtts = new AttributesImpl();
313        childAtts.addCDATAAttribute("id", resource.getId());
314        String iconCls = getResourcesIconCls(resource);
315        if (iconCls != null)
316        {
317            childAtts.addCDATAAttribute("iconCls", getResourcesIconCls(resource));
318        }
319        childAtts.addCDATAAttribute("name", resource.getName());
320        childAtts.addCDATAAttribute("mimetype", resource.getMimeType());
321        childAtts.addCDATAAttribute("lastModified", DateUtils.dateToString(resource.getLastModified()));
322        childAtts.addCDATAAttribute("size", String.valueOf(resource.getLength()));
323        childAtts.addCDATAAttribute("author", name);
324        childAtts.addCDATAAttribute("keywords", resource.getKeywordsAsString());
325        childAtts.addCDATAAttribute("type", RESOURCE);
326        
327        if (resource instanceof ModifiableAmetysObject)
328        {
329            childAtts.addCDATAAttribute("isModifiable", "true");
330        }
331        
332        getAdditionalAttributes(childAtts, resource);
333        
334        String relPath = resource.getResourcePath();
335        childAtts.addCDATAAttribute("path", relPath.startsWith("/") ? relPath.substring(1) : relPath);
336        
337        return childAtts;
338    }
339
340    /**
341     * CSS suffix class name getter for the icon resource.
342     * @param resource The resource
343     * @return The suffix of the css class for the icon of this resource.
344     */
345    protected String getResourcesIconCls(Resource resource)
346    {
347        // let the js do its job
348        return null;
349    }
350    
351    /**
352     * Get the additional attributes
353     * @param attrs The attributes
354     * @param resource The resource
355     */
356    protected void getAdditionalAttributes (AttributesImpl attrs, Resource resource)
357    {
358        // Nothing
359    }
360}