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