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.readers;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.io.Serializable;
021import java.io.UnsupportedEncodingException;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.net.URLDecoder;
025import java.util.Map;
026
027import org.apache.avalon.framework.parameters.Parameters;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.cocoon.ProcessingException;
032import org.apache.cocoon.ResourceNotFoundException;
033import org.apache.cocoon.caching.CacheableProcessingComponent;
034import org.apache.cocoon.environment.SourceResolver;
035import org.apache.commons.lang.StringUtils;
036import org.apache.excalibur.source.SourceValidity;
037import org.apache.excalibur.source.impl.validity.TimeStampValidity;
038import org.xml.sax.SAXException;
039
040import org.ametys.core.right.RightManager;
041import org.ametys.core.user.CurrentUserProvider;
042import org.ametys.core.user.UserIdentity;
043import org.ametys.core.util.cocoon.AbstractResourceReader;
044import org.ametys.plugins.explorer.resources.Resource;
045import org.ametys.plugins.repository.AmetysObjectResolver;
046import org.ametys.plugins.repository.UnknownAmetysObjectException;
047import org.ametys.plugins.repository.version.VersionableAmetysObject;
048import org.ametys.runtime.authentication.AccessDeniedException;
049import org.ametys.runtime.authentication.AuthorizationRequiredException;
050
051/**
052 * Reader for {@link Resource}
053 */
054public class AmetysResourceReader extends AbstractResourceReader implements Serviceable, CacheableProcessingComponent
055{
056    /** The Ametys object resolver */
057    protected AmetysObjectResolver _resolver;
058    /** The resource */
059    protected Resource _object;
060    /** The right manager */
061    protected RightManager _rightManager;
062    /** The current user provider */
063    protected CurrentUserProvider _currentUserProvider;
064    
065    @Override
066    public void service(ServiceManager sManager) throws ServiceException
067    {
068        _resolver = (AmetysObjectResolver) sManager.lookup(AmetysObjectResolver.ROLE);
069        _rightManager = (RightManager) sManager.lookup(RightManager.ROLE);
070        _currentUserProvider = (CurrentUserProvider) sManager.lookup(CurrentUserProvider.ROLE);
071    }
072    
073    @Override
074    protected void doSetup(SourceResolver res, Map objModel, String src, Parameters par) throws ProcessingException, IOException
075    {
076        String id = par.getParameter("id", null);
077        String path = par.getParameter("path", null);
078        String version = par.getParameter("version", null);
079        
080        try
081        {
082            if (id != null)
083            {
084                _object = _resolver.resolveById(id);
085            }
086            else
087            {
088                _object = _resolver.resolveByPath(_decodePath(path.substring(1)));
089            }
090
091            // Check user access
092            checkUserAccess();
093            
094            if (!StringUtils.isEmpty(version) && _object instanceof VersionableAmetysObject)
095            {
096                ((VersionableAmetysObject) _object).switchToRevision(version);
097            }
098        }
099        catch (UnknownAmetysObjectException e)
100        {
101            if (id != null)
102            {
103                throw new ResourceNotFoundException(String.format("The resource with id '%s' does not exist", id));
104            }
105            else
106            {
107                throw new ResourceNotFoundException(String.format("The resource at path '%s' does not exist", path));
108            }
109        }
110    }
111    
112    @Override
113    protected InputStream getInputStream()
114    {
115        return _object.getInputStream();
116    }
117    
118    @Override
119    protected String getFilename()
120    {
121        String name = _object.getName();
122        name = name.replaceAll("\\\\", "\\\\\\\\");
123        name = name.replaceAll("\\\"", "\\\\\\\"");
124        
125        return name;
126    }
127    
128    @Override
129    protected String getEncodedFilename()
130    {
131        String name = _object.getName();
132        String encodedName = null;
133        try
134        {
135            URI uri = new URI(null, null, name, null);
136            encodedName = uri.toASCIIString();
137            // EXPLORER-358 : Fix for Chrome which does not support comma in filename
138            encodedName = encodedName.replaceAll(",", "%2C");
139        }
140        catch (URISyntaxException e)
141        {
142            // do nothing and send no encoded name
143        }
144        
145        return encodedName;
146    }
147    
148    @Override
149    protected long getLength()
150    {
151        return _object.getLength();
152    }
153    
154    /**
155     * Check the user access
156     * @throws AuthorizationRequiredException if authorization is required
157     * @throws AccessDeniedException if user has no access
158     */
159    protected void checkUserAccess() throws AuthorizationRequiredException, AccessDeniedException
160    {
161        UserIdentity user = _currentUserProvider.getUser();
162        
163        if (user == null)
164        {
165            // Check anonymous access
166            if (!_rightManager.hasAnonymousReadAccess(_object.getParent()))
167            {
168                throw new AuthorizationRequiredException(null);
169            }
170        }
171        else if (!_rightManager.hasReadAccess(user, _object.getParent()))
172        {
173            throw new AccessDeniedException("User " + user + " has no right to access the resource " + _object.getId());
174        }
175    }
176    
177    /**
178     * Decode the resource path
179     * @param path the resource path
180     * @return the decoded resource path
181     * @throws UnsupportedEncodingException if UTF-8 encoding is not supported
182     */
183    protected String _decodePath (String path) throws UnsupportedEncodingException
184    {
185        StringBuffer sb = new StringBuffer();
186        
187        String[] parts = path.split("/");
188        for (String part : parts)
189        {
190            sb.append("/");
191            sb.append(URLDecoder.decode(part, "utf-8"));
192        }
193        return sb.toString();
194    }
195
196    @Override
197    public Serializable getKey()
198    {
199        return _object.getId() + getKeySuffix();
200    }
201
202    @Override
203    public SourceValidity getValidity()
204    {
205        return new TimeStampValidity(getLastModified());
206    }
207    
208    @Override
209    public long getLastModified()
210    {
211        return _object.getLastModified().getTime();
212    }
213    
214    @Override
215    public String getMimeType()
216    {
217        return _object.getMimeType();
218    }
219    
220    @Override
221    public void recycle()
222    {
223        super.recycle();
224        _object = null;
225    }
226}