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.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.repository.Content;
036import org.ametys.cms.transformation.ConsistencyChecker.CHECK;
037import org.ametys.core.util.FilenameUtils;
038import org.ametys.core.util.URIUtils;
039import org.ametys.plugins.repository.AmetysObjectResolver;
040import org.ametys.plugins.repository.version.VersionableAmetysObject;
041import org.ametys.runtime.i18n.I18nizableText;
042import org.ametys.runtime.workspace.WorkspaceMatcher;
043
044/**
045 * {@link URIResolver} for type "attribute".<br>
046 * These links or images point to a file from the attribute of the current Content.
047 */
048public class AttributeURIResolver extends AbstractURIResolver implements Serviceable, Contextualizable
049{
050    private static final Pattern _OBJECT_URI_PATTERN = Pattern.compile("([^?]*)\\?objectId=(.*)");
051    private static final Pattern _CONTENT_URI_PATTERN = Pattern.compile("([^?]*)\\?contentId=(.*)");
052    
053    /** The ametys object resolver */
054    protected AmetysObjectResolver _resolver;
055    
056    /** The context */
057    protected Context _context;
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    }
070    
071    @Override
072    public String getType()
073    {
074        return "attribute";
075    }
076
077    @Override
078    protected String _resolve(String uri, String uriArgument, boolean download, boolean absolute, boolean internal)
079    {
080        Request request = ContextHelper.getRequest(_context);
081        
082        AttributeInfo info = _getAttributeInfo(uri, request);
083        
084        Content content = info.getContent();
085        String path = info.getPath();
086        
087        if (content == null)
088        {
089            throw new IllegalStateException("Cannot resolve a local link to an unknown content for uri " + request.getRequestURI());
090        }
091        
092        String contentVersion = "";
093        if (content instanceof VersionableAmetysObject)
094        {
095            contentVersion = ((VersionableAmetysObject) content).getRevision();
096        }
097        
098        Binary binary = content.getValue(path);
099        
100        String filename = FilenameUtils.encodeName(binary.getFilename());
101        
102        String baseName = org.apache.commons.io.FilenameUtils.getBaseName(filename);
103        String extension = org.apache.commons.io.FilenameUtils.getExtension(filename);
104        
105        StringBuilder resultPath = new StringBuilder();
106        
107        resultPath.append("/_contents")
108            .append(FilenameUtils.encodePath(content.getPath()))
109            .append("/_attribute/")
110            .append(path)
111            .append("/")
112            .append(baseName)
113            .append(uriArgument)
114            .append(extension.isEmpty() ? "" : "." + extension);
115  
116        String resultUri = getUri(resultPath.toString(), content, download, absolute, internal);
117
118        Map<String, String> params = new HashMap<>();
119        params.put("objectId", content.getId());
120        
121        if (download)
122        {
123            params.put("download", "true");
124        }
125        
126        if (contentVersion != null)
127        {
128            params.put("contentVersion", contentVersion);
129        }
130        
131        return internal ? URIUtils.buildURI(resultUri, params) : URIUtils.encodeURI(resultUri, params);
132    }
133    
134    @Override
135    protected String resolveImageAsBase64(String uri, int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth)
136    {
137        try
138        {
139            Request request = ContextHelper.getRequest(_context);
140            
141            AttributeInfo info = _getAttributeInfo(uri, request);
142            
143            Content content = info.getContent();
144            String path = info.getPath();
145            
146            if (content == null)
147            {
148                throw new IllegalStateException("Cannot resolve a local link to an unknown content for uri " + request.getRequestURI());
149            }
150            
151            Binary binary = content.getValue(path);
152            
153            try (InputStream dataIs = binary.getInputStream())
154            {
155                return ImageResolverHelper.resolveImageAsBase64(dataIs, binary.getMimeType(), height, width, maxHeight, maxWidth, cropHeight, cropWidth);
156            }
157        }
158        catch (Exception e)
159        {
160            throw new IllegalStateException(e);
161        }
162    }
163    
164    /**
165     * Get the URI prefix
166     * @param path the resource path
167     * @param object The object
168     * @param download true if the pointed resource is to be downloaded.
169     * @param absolute true if the url must be absolute
170     * @param internal true to get an internal URI.
171     * @return the URI prefix
172     */
173    protected String getUri(String path, Content object, boolean download, boolean absolute, boolean internal)
174    {
175        if (internal)
176        {
177            return "cocoon://" + path;
178        }
179        else 
180        {
181            Request request = ContextHelper.getRequest(_context);
182            String workspaceURI = (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_URI);
183            String uriPrefix = request.getContextPath() + workspaceURI;
184            
185            if (absolute && !uriPrefix.startsWith(request.getScheme()))
186            {
187                uriPrefix = request.getScheme() + "://" + request.getServerName() + (request.getServerPort() != 80 ? ":" + request.getServerPort() : "") + uriPrefix;
188            }
189            
190            return uriPrefix + path;
191        }
192    }
193    
194    @Override
195    public CHECK checkLink(String uri, boolean shortTest)
196    {
197        return CHECK.SUCCESS;
198    }
199    
200    @Override
201    public I18nizableText getLabel(String uri)
202    {
203        Request request = ContextHelper.getRequest(_context);
204        
205        AttributeInfo info = _getAttributeInfo(uri, request);
206        
207        return new I18nizableText("plugin.cms", "PLUGINS_CMS_LINK_METADATA_LABEL", Collections.singletonList(info.getPath()));
208    }
209    
210    /**
211     * Get attribute path and content.
212     * @param uri the attribute URI.
213     * @param request the request.
214     * @return the attribute info.
215     */
216    protected AttributeInfo _getAttributeInfo(String uri, Request request)
217    {
218        AttributeInfo info = new AttributeInfo();
219        
220        Matcher matcher = _OBJECT_URI_PATTERN.matcher(uri);
221        Matcher contentMatcher = _CONTENT_URI_PATTERN.matcher(uri);
222        
223        // Test if the URI contains an object ID.
224        if (matcher.matches())
225        {
226            info.setPath(matcher.group(1));
227            String objectId = matcher.group(2);
228            
229            Content object = _resolver.resolveById(objectId);
230            info.setContent(object);
231        }
232        else if (contentMatcher.matches())
233        {
234            // Legacy: handle content ID.
235            info.setPath(contentMatcher.group(1));
236            String objectId = contentMatcher.group(2);
237            
238            Content object = _resolver.resolveById(objectId);
239            info.setContent(object);
240        }
241        else
242        {
243            // URI without object ID, take the content in the request attributes.
244            info.setPath(uri);
245            info.setContent((Content) request.getAttribute(Content.class.getName()));
246        }
247        
248        return info;
249    }
250
251    /**
252     * Attribute information.
253     */
254    protected class AttributeInfo
255    {
256        private String _path;
257        private Content _content;
258        
259        /**
260         * Get the attribute path.
261         * @return the attribute path
262         */
263        public String getPath()
264        {
265            return _path;
266        }
267        
268        /**
269         * Set the attribute path.
270         * @param path the attribute path to set
271         */
272        public void setPath(String path)
273        {
274            _path = path;
275        }
276        
277        /**
278         * Get the content.
279         * @return the content
280         */
281        public Content getContent()
282        {
283            return _content;
284        }
285        
286        /**
287         * Set the content.
288         * @param content the {@link Content} to set
289         */
290        public void setContent(Content content)
291        {
292            _content = content;
293        }
294    }
295}