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