001/*
002 *  Copyright 2016 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.workspaces.documents;
017
018import java.io.InputStream;
019import java.util.Collections;
020
021import org.apache.avalon.framework.context.Context;
022import org.apache.avalon.framework.context.ContextException;
023import org.apache.avalon.framework.context.Contextualizable;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.avalon.framework.service.Serviceable;
027import org.apache.commons.lang.StringUtils;
028
029import org.ametys.cms.transformation.ConsistencyChecker.CHECK;
030import org.ametys.cms.transformation.ImageResolverHelper;
031import org.ametys.cms.transformation.URIResolver;
032import org.ametys.core.util.FilenameUtils;
033import org.ametys.core.util.URIUtils;
034import org.ametys.plugins.explorer.resources.Resource;
035import org.ametys.plugins.repository.AmetysObject;
036import org.ametys.plugins.repository.AmetysObjectResolver;
037import org.ametys.plugins.repository.UnknownAmetysObjectException;
038import org.ametys.plugins.repository.version.VersionableAmetysObject;
039import org.ametys.plugins.workspaces.project.objects.Project;
040import org.ametys.runtime.i18n.I18nizableText;
041import org.ametys.runtime.plugin.component.AbstractLogEnabled;
042import org.ametys.runtime.plugin.component.PluginAware;
043import org.ametys.web.URIPrefixHandler;
044
045/**
046 * {@link URIResolver} for type "project-resource". <br>
047 * These links point to a file from the resources of a project.
048 */
049// FIXME refactor this class by subclassing kernel's ResourceURIResolver as soon as Workspaces is only compatible with 4.4
050// see WORKSPACES-645
051public class ProjectResourceURIResolver extends AbstractLogEnabled implements URIResolver, PluginAware, Serviceable, Contextualizable
052{
053    /** The ametys object resolver. */
054    protected AmetysObjectResolver _resolver;
055    /** The avalon context. */
056    protected Context _context;
057    /** plugin name */
058    protected String _pluginName;
059    /** The URI prefix handler */
060    protected URIPrefixHandler _webPrefixHandler;
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        _webPrefixHandler = (URIPrefixHandler) manager.lookup(URIPrefixHandler.ROLE);
073    }
074    
075    public void setPluginInfo(String pluginName, String featureName, String id)
076    {
077        _pluginName = pluginName;
078    }
079    
080    @Override
081    public String getType()
082    {
083        return "project-resource";
084    }
085    
086    @Override
087    public String resolve(String uri, boolean download, boolean absolute, boolean internal)
088    {
089        return _resolve(uri, download, absolute, internal, "resources", null);
090    }
091    
092    @Override
093    public String resolveImage(String uri, int height, int width, boolean download, boolean absolute, boolean internal)
094    {
095        if (height == 0 && width == 0)
096        {
097            return resolve(uri, download, absolute, internal);
098        }
099        
100        
101        return _resolve(uri, download, absolute, internal, "resources-images", "_" + height + "x" + width);
102    }
103    
104    @Override
105    public String resolveImageAsBase64(String uri, int height, int width)
106    {
107        return resolveImageAsBase64(uri, height, width, 0, 0, 0, 0);
108    }
109
110    @Override
111    public String resolveBoundedImage(String uri, int maxHeight, int maxWidth, boolean download, boolean absolute, boolean internal)
112    {
113        if (maxHeight == 0 && maxWidth == 0)
114        {
115            return resolve(uri, download, absolute, internal);
116        }
117        
118        return _resolve(uri, download, absolute, internal, "resources-images", "_max" + maxHeight + "x" + maxWidth);
119    }
120    
121    @Override
122    public String resolveBoundedImageAsBase64(String uri, int maxHeight, int maxWidth)
123    {
124        return resolveImageAsBase64(uri, 0, 0, maxHeight, maxWidth, 0, 0);
125    }
126
127    @Override
128    public String resolveCroppedImage(String uri, int cropHeight, int cropWidth, boolean download, boolean absolute, boolean internal)
129    {
130        if (cropHeight == 0 && cropWidth == 0)
131        {
132            return resolve(uri, download, absolute, internal);
133        }
134        
135        return _resolve(uri, download, absolute, internal, "resources-images", "_crop" + cropHeight + "x" + cropWidth);
136    }
137    
138    @Override
139    public String resolveCroppedImageAsBase64(String uri, int cropHeight, int cropWidth)
140    {
141        return resolveImageAsBase64(uri, 0, 0, 0, 0, cropHeight, cropWidth);
142    }
143
144    /**
145     * Creates a full uri
146     * @param uri the base uri, ie the resource id
147     * @param download true to create a forced download uri
148     * @param absolute true to create an absolute uri (if internal is false)
149     * @param internal true to create an internal uri
150     * @param prefix Prefix on the uri
151     * @param suffix Suffix on the uri
152     * @return The created uri
153     */
154    protected String _resolve(String uri, boolean download, boolean absolute, boolean internal, String prefix, String suffix)
155    {
156        String version = null;
157        String path;
158        Resource resource = null;
159        try
160        {
161            String resourceId = uri;
162            int i = uri.indexOf(";");
163            if (i != -1)
164            {
165                resourceId = uri.substring(0, i);
166                version = uri.substring(i + 1);
167            }
168            
169            resource = (Resource) _resolver.resolveById(resourceId);
170            path = getResourcePath(resource);
171        }
172        catch (UnknownAmetysObjectException e)
173        {
174            getLogger().warn("Link to unexisting resource " + uri);
175            return "";
176        }
177        
178        int i = path.lastIndexOf(".");
179        String extension = i != -1 ? path.substring(i) : null;
180        path = i != -1 ? path.substring(0, i) : path; 
181        
182        StringBuilder result = new StringBuilder();
183        
184        result.append(getUriPrefix(resource, download, absolute, internal));
185        
186        String realPrefix = getRealPrefix(resource, prefix);
187        if (StringUtils.isNotEmpty(realPrefix))
188        {
189            result.append("/_").append(realPrefix);
190        }
191        
192        if (StringUtils.isNotEmpty(version) && resource instanceof VersionableAmetysObject)
193        {
194            result.append("/__version").append(version);
195        }
196        
197        result.append(FilenameUtils.encodePath(path));
198        
199        if (suffix != null)
200        {
201            result.append(suffix);
202        }
203        
204        if (extension != null)
205        {
206            result.append(extension);
207        }
208        
209        return URIUtils.encodeURI(result.toString(), download ? Collections.singletonMap("download", "true") : null);
210    }
211    
212    /**
213     * Get an image's bytes encoded as base64, optionally resized. 
214     * @param uri the image URI.
215     * @param height the specified height. Ignored if negative.
216     * @param width the specified width. Ignored if negative.
217     * @param maxHeight the maximum image height. Ignored if height or width is specified.
218     * @param maxWidth the maximum image width. Ignored if height or width is specified.
219     * @param cropHeight The cropping height. Ignored if negative.
220     * @param cropWidth The cropping width. Ignored if negative.
221     * @return the image bytes encoded as base64.
222     */
223    protected String resolveImageAsBase64(String uri, int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth)
224    {
225        try
226        {
227            Resource resource = (Resource) _resolver.resolveById(uri);
228            try (InputStream dataIs = resource.getInputStream())
229            {
230                return ImageResolverHelper.resolveImageAsBase64(dataIs, resource.getMimeType(), height, width, maxHeight, maxWidth);
231            }
232        }
233        catch (UnknownAmetysObjectException e)
234        {
235            getLogger().warn("Link to unexisting resource " + uri);
236            return "";
237        }
238        catch (Exception e)
239        {
240            throw new IllegalStateException(e);
241        }
242    }
243
244    @Override
245    public CHECK checkLink(String uri, boolean shortTest)
246    {
247        try
248        {
249            _resolver.resolveById(uri);
250            return CHECK.SUCCESS;
251        }
252        catch (UnknownAmetysObjectException e)
253        {
254            return CHECK.NOT_FOUND;
255        }
256    }
257    
258    @Override
259    public I18nizableText getLabel(String uri)
260    {
261        try
262        {
263            Resource resource = (Resource) _resolver.resolveById(uri);
264            return new I18nizableText("plugin" + _pluginName, "PLUGINS_WORKSPACES_LINK_RESOURCE_LABEL", Collections.singletonList(resource.getResourcePath()));
265        }
266        catch (UnknownAmetysObjectException e)
267        {
268            return new I18nizableText("plugin" + _pluginName, "PLUGINS_WORKSPACES_LINK_RESOURCE_UNKNOWN");
269        }
270    }
271
272    /**
273     * Get the URI prefix
274     * @param object The object
275     * @param download true if the pointed resource is to be downloaded.
276     * @param absolute true if the url must be absolute
277     * @param internal true to get an internal URI.
278     * @return the URI prefix
279     */
280    protected String getUriPrefix (AmetysObject object, boolean download, boolean absolute, boolean internal)
281    {
282        Project project = null;
283        String projectName = null;
284        String siteName = null;
285        
286        if (object instanceof Resource)
287        {
288            Resource resource = (Resource) object;
289            project = _getProject(resource);
290            projectName = project.getName();
291            
292            siteName = project.getSites().iterator().next().getName();
293        }
294        
295        if (internal)
296        {
297            return "cocoon://plugins/" + _pluginName + "/" + projectName;
298        }
299        else if (absolute)
300        {
301            return _webPrefixHandler.getAbsoluteUriPrefix(siteName) + "/_plugins/" + _pluginName + "/" + projectName;
302        }
303        else
304        {
305            return _webPrefixHandler.getUriPrefix(siteName) + "/_plugins/" + _pluginName + "/" + projectName;
306        }
307    }
308    
309    /**
310     * Get the resource path 
311     * @param resource the resource
312     * @return the path
313     */
314    protected String getResourcePath (Resource resource)
315    {
316        return resource.getResourcePath();
317    }
318    
319    /**
320     * Get the real prefix
321     * @param resource the resource
322     * @param prefix the initial prefix
323     * @return the real prefix
324     */
325    protected String getRealPrefix (Resource resource, String prefix)
326    {
327        return prefix;
328    }
329    
330    /**
331     * Retrieves parent project
332     * @param resource The resource which belongs to a project
333     * @return The parent project
334     */
335    protected Project _getProject (AmetysObject resource)
336    {
337        AmetysObject parent = resource.getParent();
338        while (parent != null)
339        {
340            if (parent instanceof Project)
341            {
342                return (Project) parent;
343            }
344            
345            parent = parent.getParent();
346        }
347        return null;
348    }
349}