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.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    private String _getUri(String path, DefaultLink object, boolean absolute, boolean internal)
181    {
182        String siteName = object.getSiteName();
183        
184        if (internal)
185        {
186            return "cocoon://" + siteName + path;
187        }
188        else if (absolute)
189        {
190            if (_renderingContexthandler.getRenderingContext() == RenderingContext.FRONT)
191            {
192                Site site = _siteManager.getSite(siteName);
193                
194                String[] aliases = site.getUrlAliases();
195                return aliases[Math.abs(path.hashCode()) % aliases.length] + path;
196            }
197            
198            return _prefixHandler.getAbsoluteUriPrefix() + "/" + siteName + path; 
199        }
200        else
201        {
202            return _prefixHandler.getUriPrefix(siteName) + path;
203        }
204    }
205    
206    @Override
207    public CHECK checkLink(String uri, boolean shortTest)
208    {
209        return CHECK.SUCCESS;
210    }
211    
212    @Override
213    public I18nizableText getLabel(String uri)
214    {
215        try
216        {
217            Request request = ContextHelper.getRequest(_context);
218            Info info = _getInfo(uri, request);
219            return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_METADATA_LABEL", Collections.singletonList(info.getDataPath()));
220        }
221        catch (UnknownAmetysObjectException e)
222        {
223            return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_METADATA_UNKNOWN");
224        }
225    }
226    
227    private Info _getInfo(String uri, Request request)
228    {
229        Info info = new Info();
230        
231        Matcher matcher = _OBJECT_URI_PATTERN.matcher(uri);
232        
233        // Test if the URI contains an object ID.
234        if (matcher.matches())
235        {
236            info.setDataPath(matcher.group(1));
237            String objectId = matcher.group(2);
238            
239            DefaultLink link = _resolver.resolveById(objectId);
240            info.setLink(link);
241        }
242        else
243        {
244            throw new IllegalStateException("Missing objectId parameter to resolve a local link for uri " + request.getRequestURI());
245        }
246        
247        return info;
248    }
249
250    /**
251     * data information.
252     */
253    protected class Info
254    {
255        private String _dataPath;
256        private DefaultLink _link;
257        
258        /**
259         * Get the data path.
260         * @return the data path.
261         */
262        public String getDataPath()
263        {
264            return _dataPath;
265        }
266        
267        /**
268         * Set the data path.
269         * @param path the data path to set
270         */
271        public void setDataPath(String path)
272        {
273            _dataPath = path;
274        }
275        
276        /**
277         * Get the link.
278         * @return the link
279         */
280        public DefaultLink getLink()
281        {
282            return _link;
283        }
284        
285        /**
286         * Set the link.
287         * @param link the link to set
288         */
289        public void setLink(DefaultLink link)
290        {
291            _link = link;
292        }
293    }
294}