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