001/*
002 *  Copyright 2017 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.linkdirectory;
017
018import java.io.InputStream;
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.Map;
022import java.util.regex.Matcher;
023import java.util.regex.Pattern;
024
025import org.apache.avalon.framework.context.Context;
026import org.apache.avalon.framework.context.ContextException;
027import org.apache.avalon.framework.context.Contextualizable;
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.components.ContextHelper;
032import org.apache.cocoon.environment.Request;
033
034import org.ametys.cms.data.Binary;
035import org.ametys.cms.transformation.AbstractURIResolver;
036import org.ametys.cms.transformation.ConsistencyChecker.CHECK;
037import org.ametys.cms.transformation.URIResolver;
038import org.ametys.core.util.FilenameUtils;
039import org.ametys.core.util.ImageResolverHelper;
040import org.ametys.core.util.URIUtils;
041import org.ametys.plugins.linkdirectory.repository.DefaultLink;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.plugins.repository.UnknownAmetysObjectException;
044import org.ametys.runtime.i18n.I18nizableText;
045import org.ametys.runtime.plugin.component.PluginAware;
046import org.ametys.web.URIPrefixHandler;
047import org.ametys.web.renderingcontext.RenderingContext;
048import org.ametys.web.renderingcontext.RenderingContextHandler;
049import org.ametys.web.repository.site.Site;
050import org.ametys.web.repository.site.SiteManager;
051
052/**
053 * {@link URIResolver} for type "link-data".<br>
054 * These links or images point to a file from the data of a {@link Link}.
055 */
056public class LinkMetadataURIResolver extends AbstractURIResolver implements Serviceable, Contextualizable, PluginAware
057{
058    private static final Pattern _OBJECT_URI_PATTERN = Pattern.compile("([^?]*)\\?objectId=(.*)");
059    
060    private AmetysObjectResolver _resolver;
061    private URIPrefixHandler _prefixHandler;
062    private RenderingContextHandler _renderingContexthandler;
063    private SiteManager _siteManager;
064    
065    /** The context */
066    private Context _context;
067
068    private String _pluginName;
069    
070    public void setPluginInfo(String pluginName, String featureName, String id)
071    {
072        _pluginName = pluginName;
073    }
074    
075    @Override
076    public void contextualize(Context context) throws ContextException
077    {
078        _context = context;
079    }
080    
081    @Override
082    public void service(ServiceManager manager) throws ServiceException
083    {
084        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
085        _prefixHandler = (URIPrefixHandler) manager.lookup(URIPrefixHandler.ROLE);
086        _renderingContexthandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE);
087        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
088    }
089    
090    @Override
091    public String getType()
092    {
093        return "link-data";
094    }
095    
096    @Override
097    protected String _resolve(String uri, String uriArgument, boolean download, boolean absolute, boolean internal)
098    {
099        try
100        {
101            Request request = ContextHelper.getRequest(_context);
102            
103            Info info = _getInfo(uri, request);
104            
105            DefaultLink link = info.getLink();
106            String dataPath = info.getDataPath();
107            
108            if (link == null)
109            {
110                throw new IllegalStateException("Cannot resolve a local link to an unknown link for uri " + request.getRequestURI());
111            }
112            
113            Binary binary = link.getValue(dataPath);
114            
115            String filename = FilenameUtils.encodeName(binary.getFilename());
116            
117            String baseName = org.apache.commons.io.FilenameUtils.getBaseName(filename);
118            String extension = org.apache.commons.io.FilenameUtils.getExtension(filename);
119            
120            StringBuilder resultPath = new StringBuilder();
121            
122            resultPath.append("/_plugins/")
123                      .append(_pluginName)
124                      .append("/_links")
125                      .append(FilenameUtils.encodePath(link.getPath()))
126                      .append("/_data/")
127                      .append(dataPath)
128                      .append("/")
129                      .append(baseName)
130                      .append(uriArgument)
131                      .append(extension.isEmpty() ? "" : "." + extension);
132              
133            String result = _getUri(resultPath.toString(), link, absolute, internal);
134              
135            Map<String, String> params = new HashMap<>();
136            params.put("objectId", link.getId());
137            if (download)
138            {
139                params.put("download", "true");
140            }
141            
142            return URIUtils.encodeURI(result, params);
143        }
144        catch (Exception e)
145        {
146            throw new IllegalStateException(e);
147        }
148    }
149
150    @Override
151    protected String resolveImageAsBase64(String uri, int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth)
152    {
153        try
154        {
155            Request request = ContextHelper.getRequest(_context);
156            
157            Info info = _getInfo(uri, request);
158            
159            DefaultLink link = info.getLink();
160            String dataPath = info.getDataPath();
161            
162            if (link == null)
163            {
164                throw new IllegalStateException("Cannot resolve a local link to an unknown link for uri " + request.getRequestURI());
165            }
166            
167            Binary binary = link.getValue(dataPath);
168            
169            try (InputStream dataIs = binary.getInputStream())
170            {
171                return ImageResolverHelper.resolveImageAsBase64(dataIs, binary.getMimeType(), height, width, maxHeight, maxWidth, cropHeight, cropWidth);
172            }
173        }
174        catch (Exception e)
175        {
176            throw new IllegalStateException(e);
177        }
178    }
179    
180    public String getMimeType(String uri)
181    {
182        try
183        {
184            Request request = ContextHelper.getRequest(_context);
185            
186            Info info = _getInfo(uri, request);
187            
188            DefaultLink link = info.getLink();
189            String dataPath = info.getDataPath();
190            
191            if (link == null)
192            {
193                throw new IllegalStateException("Cannot resolve a local link to an unknown link for uri " + request.getRequestURI());
194            }
195            
196            Binary binary = link.getValue(dataPath);
197            
198            return binary.getMimeType();
199        }
200        catch (Exception e)
201        {
202            throw new IllegalStateException(e);
203        }
204    }
205    
206    private String _getUri(String path, DefaultLink object, boolean absolute, boolean internal)
207    {
208        String siteName = object.getSiteName();
209        
210        if (internal)
211        {
212            return "cocoon://" + siteName + path;
213        }
214        else if (absolute)
215        {
216            if (_renderingContexthandler.getRenderingContext() == RenderingContext.FRONT)
217            {
218                Site site = _siteManager.getSite(siteName);
219                
220                String[] aliases = site.getUrlAliases();
221                return aliases[Math.abs(path.hashCode()) % aliases.length] + path;
222            }
223            
224            return _prefixHandler.getAbsoluteUriPrefix() + "/" + siteName + path; 
225        }
226        else
227        {
228            return _prefixHandler.getUriPrefix(siteName) + path;
229        }
230    }
231    
232    @Override
233    public CHECK checkLink(String uri, boolean shortTest)
234    {
235        return CHECK.SUCCESS;
236    }
237    
238    @Override
239    public I18nizableText getLabel(String uri)
240    {
241        try
242        {
243            Request request = ContextHelper.getRequest(_context);
244            Info info = _getInfo(uri, request);
245            return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_METADATA_LABEL", Collections.singletonList(info.getDataPath()));
246        }
247        catch (UnknownAmetysObjectException e)
248        {
249            return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_METADATA_UNKNOWN");
250        }
251    }
252    
253    private Info _getInfo(String uri, Request request)
254    {
255        Info info = new Info();
256        
257        Matcher matcher = _OBJECT_URI_PATTERN.matcher(uri);
258        
259        // Test if the URI contains an object ID.
260        if (matcher.matches())
261        {
262            info.setDataPath(matcher.group(1));
263            String objectId = matcher.group(2);
264            
265            DefaultLink link = _resolver.resolveById(objectId);
266            info.setLink(link);
267        }
268        else
269        {
270            throw new IllegalStateException("Missing objectId parameter to resolve a local link for uri " + request.getRequestURI());
271        }
272        
273        return info;
274    }
275
276    /**
277     * data information.
278     */
279    protected class Info
280    {
281        private String _dataPath;
282        private DefaultLink _link;
283        
284        /**
285         * Get the data path.
286         * @return the data path.
287         */
288        public String getDataPath()
289        {
290            return _dataPath;
291        }
292        
293        /**
294         * Set the data path.
295         * @param path the data path to set
296         */
297        public void setDataPath(String path)
298        {
299            _dataPath = path;
300        }
301        
302        /**
303         * Get the link.
304         * @return the link
305         */
306        public DefaultLink getLink()
307        {
308            return _link;
309        }
310        
311        /**
312         * Set the link.
313         * @param link the link to set
314         */
315        public void setLink(DefaultLink link)
316        {
317            _link = link;
318        }
319    }
320}