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.cms.transformation;
017
018import java.io.InputStream;
019import java.util.Collections;
020import java.util.regex.Matcher;
021import java.util.regex.Pattern;
022
023import org.apache.avalon.framework.context.Context;
024import org.apache.avalon.framework.context.ContextException;
025import org.apache.avalon.framework.context.Contextualizable;
026import org.apache.avalon.framework.logger.AbstractLogEnabled;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.cocoon.components.ContextHelper;
031import org.apache.cocoon.environment.Request;
032
033import org.ametys.cms.URIPrefixHandler;
034import org.ametys.cms.repository.Content;
035import org.ametys.cms.transformation.ConsistencyChecker.CHECK;
036import org.ametys.core.util.FilenameUtils;
037import org.ametys.core.util.URIUtils;
038import org.ametys.plugins.explorer.resources.Resource;
039import org.ametys.plugins.repository.AmetysObject;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.plugins.repository.UnknownAmetysObjectException;
042import org.ametys.runtime.i18n.I18nizableText;
043
044/**
045 * {@link URIResolver} for type "attachment".<br>
046 * These links point to a file from the attachments of the current Content.
047 */
048public class AttachmentURIResolver extends AbstractLogEnabled implements URIResolver, Serviceable, Contextualizable
049{
050    /** Regular expression for contents stored under plugins */
051    protected static final Pattern __PLUGIN_CONTENT_PATTERN = Pattern.compile("^/ametys:plugins/([^/]+)/ametys:contents/(.*)$");
052    /** The ametys resolver */
053    protected AmetysObjectResolver _resolver;
054    /** The avalon context */
055    protected Context _context;
056    /** The URI prefix handler */
057    protected URIPrefixHandler _prefixHandler;
058    
059    @Override
060    public void contextualize(Context context) throws ContextException
061    {
062        _context = context;
063    }
064
065    @Override
066    public void service(ServiceManager manager) throws ServiceException
067    {
068        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
069        _prefixHandler = (URIPrefixHandler) manager.lookup(URIPrefixHandler.ROLE);
070    }
071    
072    @Override
073    public String getType()
074    {
075        return "attachment-content";
076    }
077    
078    @Override
079    public String resolve(String uri, boolean download, boolean absolute, boolean internal)
080    {
081        Request request = ContextHelper.getRequest(_context);
082        String path;
083        Content content = null;
084        String contentName = null;
085        
086        try
087        {
088            Resource resource = (Resource) _resolver.resolveById(uri);
089            path = resource.getResourcePath();
090            
091            content = (Content) request.getAttribute(Content.class.getName());
092            contentName = content.getName();
093        }
094        catch (UnknownAmetysObjectException e)
095        {
096            getLogger().warn("Link to unexisting resource " + uri);
097            return "";
098        }
099        catch (Exception e)
100        {
101            throw new IllegalStateException(e);
102        }
103        
104        StringBuilder resultPath = new StringBuilder();
105        resultPath.append(getUriPrefix(content, download, absolute, internal));
106        
107        Matcher m = __PLUGIN_CONTENT_PATTERN.matcher(content.getPath());
108        if (m.matches())
109        {
110            String pluginName = m.group(1);
111            resultPath.append("/")
112                .append(pluginName)
113                .append("/_plugin-attachments/");
114        }
115        else
116        {
117            resultPath.append("/_attachments/");
118        }
119        
120        resultPath.append(contentName)
121            .append(path);
122        
123        String encodePath = FilenameUtils.encodePath(resultPath.toString());
124        return URIUtils.encodeURI(encodePath, download ? Collections.singletonMap("download", "true") : null);
125    }
126    
127    /**
128     * Get the URI prefix
129     * @param object The object
130     * @param download true if the pointed resource is to be downloaded.
131     * @param absolute true if the url must be absolute
132     * @param internal true to get an internal URI.
133     * @return the URI prefix
134     */
135    protected String getUriPrefix (AmetysObject object, boolean download, boolean internal, boolean absolute)
136    {
137        if (internal)
138        {
139            return "cocoon://";
140        }
141        else if (absolute)
142        {
143            return _prefixHandler.getAbsoluteUriPrefix();
144        }
145        else
146        {
147            return _prefixHandler.getUriPrefix();
148        }
149    }
150    
151    @Override
152    public String resolveImage(String uri, int height, int width, boolean download, boolean absolute, boolean internal)
153    {
154        if (height == 0 && width == 0)
155        {
156            return resolve(uri, download, absolute, internal);
157        }
158        StringBuilder uriArgument = new StringBuilder();
159        uriArgument.append("_").append(height).append("x").append(width);
160        return _resolveImage(uri, uriArgument.toString(), download, absolute, internal);
161    }
162    
163    @Override
164    public String resolveImageAsBase64(String uri, int height, int width)
165    {
166        return resolveImageAsBase64(uri, height, width, 0, 0, 0, 0);
167    }
168    
169    @Override
170    public String resolveBoundedImage(String uri, int maxHeight, int maxWidth, boolean download, boolean absolute, boolean internal)
171    {
172        if (maxHeight == 0 && maxWidth == 0)
173        {
174            return resolve(uri, download, absolute, internal);
175        }
176        StringBuilder uriArgument = new StringBuilder();
177        uriArgument.append("_max").append(maxHeight).append("x").append(maxWidth);
178        return _resolveImage(uri, uriArgument.toString(), download, absolute, internal);
179    }
180    
181    @Override
182    public String resolveBoundedImageAsBase64(String uri, int maxHeight, int maxWidth)
183    {
184        return resolveImageAsBase64(uri, 0, 0, maxHeight, maxWidth, 0, 0);
185    }
186    
187    @Override
188    public String resolveCroppedImage(String uri, int cropHeight, int cropWidth, boolean download, boolean absolute, boolean internal)
189    {
190        if (cropHeight == 0 && cropWidth == 0)
191        {
192            return resolve(uri, download, absolute, internal);
193        }
194        StringBuilder uriArgument = new StringBuilder();
195        uriArgument.append("_crop").append(cropHeight).append("x").append(cropWidth);
196        return _resolveImage(uri, uriArgument.toString(), download, absolute, internal);
197    }
198
199    /**
200     * Resolves a link URI for rendering image.<br>
201     * The output must be a properly encoded path, relative to the webapp context, accessible from a browser.
202     * @param uri the link URI.
203     * @param uriArgument the argument to append to the uri
204     * @param download true if the pointed resource is to be downloaded.
205     * @param absolute true if the url must be absolute
206     * @param internal true to get an internal URI.
207     * @return the path to the image.
208     */
209    protected String _resolveImage(String uri, String uriArgument, boolean download, boolean absolute, boolean internal)
210    {
211        Request request = ContextHelper.getRequest(_context);
212        String path;
213        Content content = null;
214        String contentName = null;
215        try
216        {
217            Resource resource = (Resource) _resolver.resolveById(uri);
218            path = resource.getResourcePath();
219            
220            content = (Content) request.getAttribute(Content.class.getName());
221            contentName = content.getName();
222           
223        }
224        catch (UnknownAmetysObjectException e)
225        {
226            getLogger().warn("Link to unexisting resource " + uri);
227            return "";
228        }
229        catch (Exception e)
230        {
231            throw new IllegalStateException(e);
232        }
233        
234        int i = path.lastIndexOf(".");
235        String extension = path.substring(i);
236        path = path.substring(0, i);
237        
238        StringBuilder result = new StringBuilder();
239        result.append(getUriPrefix(content, download, internal, absolute));
240        
241        Matcher m = __PLUGIN_CONTENT_PATTERN.matcher(content.getPath());
242        if (m.matches())
243        {
244            String pluginName = m.group(1);
245            result.append("/")
246                .append(pluginName)
247                .append("/_plugin-attachments-images/");
248        }
249        else
250        {
251            result.append("/_attachments-images/");
252        }
253        
254        
255        result.append(contentName)
256              .append(path)
257              .append(uriArgument)
258              .append(extension);
259        
260        String encodePath = URIUtils.encodePath(result.toString());
261        return URIUtils.encodeURI(encodePath, download ? Collections.singletonMap("download", "true") : null);
262    }
263    
264    @Override
265    public String resolveCroppedImageAsBase64(String uri, int cropHeight, int cropWidth)
266    {
267        return resolveImageAsBase64(uri, 0, 0, 0, 0, cropHeight, cropWidth);
268    }
269    
270    /**
271     * Get an image's bytes encoded as base64, optionally resized. 
272     * @param uri the image URI.
273     * @param height the specified height. Ignored if negative.
274     * @param width the specified width. Ignored if negative.
275     * @param maxHeight the maximum image height. Ignored if height or width is specified.
276     * @param maxWidth the maximum image width. Ignored if height or width is specified.
277     * @param cropHeight The cropping height. Ignored if negative.
278     * @param cropWidth The cropping width. Ignored if negative.
279     * @return the image bytes encoded as base64.
280     */
281    protected String resolveImageAsBase64(String uri, int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth)
282    {
283        try
284        {
285            Resource resource = (Resource) _resolver.resolveById(uri);
286            
287            try (InputStream dataIs = resource.getInputStream())
288            {
289                return ImageResolverHelper.resolveImageAsBase64(dataIs, resource.getMimeType(), height, width, maxHeight, maxWidth, cropHeight, cropWidth);
290            }
291        }
292        catch (UnknownAmetysObjectException e)
293        {
294            getLogger().warn("Link to unexisting resource " + uri);
295            return "";
296        }
297        catch (Exception e)
298        {
299            throw new IllegalStateException(e);
300        }
301    }
302    
303    @Override
304    public CHECK checkLink(String uri, boolean shortTest)
305    {
306        try
307        {
308            _resolver.resolveById(uri);
309            return CHECK.SUCCESS;
310        }
311        catch (UnknownAmetysObjectException e)
312        {
313            return CHECK.NOT_FOUND;
314        }
315    }
316    
317    @Override
318    public I18nizableText getLabel(String uri)
319    {
320        try
321        {
322            Resource resource = (Resource) _resolver.resolveById(uri);
323            return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_ATTACHMENT_LABEL", Collections.singletonList(resource.getResourcePath()));
324        }
325        catch (UnknownAmetysObjectException e)
326        {
327            return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_ATTACHMENT_UNKNOWN");
328        }
329    }
330}