001/*
002 *  Copyright 2019 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.newsletter.testsending;
017
018import java.io.InputStream;
019import java.util.HashMap;
020import java.util.Map;
021import java.util.Optional;
022
023import javax.jcr.Repository;
024import javax.jcr.RepositoryException;
025import javax.jcr.Session;
026
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.cocoon.components.ContextHelper;
030import org.apache.cocoon.environment.Request;
031import org.apache.commons.lang3.StringUtils;
032
033import org.ametys.cms.repository.Content;
034import org.ametys.cms.transformation.ImageResolverHelper;
035import org.ametys.core.util.URLEncoder;
036import org.ametys.plugins.repository.metadata.File;
037import org.ametys.plugins.repository.metadata.Resource;
038import org.ametys.plugins.repository.metadata.RichText;
039import org.ametys.plugins.repository.version.VersionableAmetysObject;
040import org.ametys.web.WebConstants;
041import org.ametys.web.WebHelper;
042import org.ametys.web.editor.LocalURIResolver;
043
044/**
045 * Resolver for local uri in newsletters
046 */
047public class NewsletterLocalURIResolver extends LocalURIResolver
048{
049    /** resolver data type for newsletter local */
050    public static final String NEWSLETTER_LOCAL_DATA_TYPE = "newsletter-local";
051    private Repository _repository;
052
053    @Override
054    public void service(ServiceManager manager) throws ServiceException
055    {
056        super.service(manager);
057        _repository = (Repository) manager.lookup(Repository.class.getName());
058    }
059    
060    @Override
061    public String getType()
062    {
063        return NEWSLETTER_LOCAL_DATA_TYPE;
064    }
065    
066    @Override
067    public String resolve(String uri, boolean download, boolean absolute, boolean internal)
068    {
069        return _resolve(uri, download, absolute, internal, "_contents", null);
070    }
071    
072    @Override
073    public String resolveImage(String uri, int height, int width, boolean download, boolean absolute, boolean internal)
074    {
075        String suffix = height != 0 || width != 0 ? "_" + height + "x" + width : null;
076        return _resolve(uri, download, absolute, internal, "_contents-images", suffix);
077    }
078    
079    @Override
080    public String resolveImageAsBase64(String uri, int height, int width)
081    {
082        return _resolveImageAsBase64(uri, height, width, 0, 0, 0, 0);
083    }
084    
085    @Override
086    public String resolveBoundedImage(String uri, int maxHeight, int maxWidth, boolean download, boolean absolute, boolean internal)
087    {
088        String suffix = maxHeight != 0 || maxWidth != 0 ? "_max" + maxHeight + "x" + maxWidth : null;
089        return _resolve(uri, download, absolute, internal, "_contents-images", suffix);
090    }
091    
092    @Override
093    public String resolveBoundedImageAsBase64(String uri, int maxHeight, int maxWidth)
094    {
095        return _resolveImageAsBase64(uri, 0, 0, maxHeight, maxWidth, 0, 0);
096    }
097    
098    @Override
099    public String resolveCroppedImage(String uri, int cropHeight, int cropWidth, boolean download, boolean absolute, boolean internal)
100    {
101        String suffix = cropHeight != 0 || cropWidth != 0 ? "_crop" + cropHeight + "x" + cropWidth : null;
102        return _resolve(uri, download, absolute, internal, "_contents-images", suffix);
103    }
104    
105    @Override
106    public String resolveCroppedImageAsBase64(String uri, int cropHeight, int cropWidth)
107    {
108        return _resolveImageAsBase64(uri, 0, 0, 0, 0, cropHeight, cropWidth);
109    }
110    
111    /**
112     * Resolves a link URI for rendering purposes.<br>
113     * The output must be a properly encoded path, relative to the webapp context, accessible from a browser.
114     * @param uri the link URI.
115     * @param download true if the pointed resource is to be downloaded.
116     * @param absolute true if the url must be absolute
117     * @param internal true to get an internal URI.
118     * @param pipeline The pipeline to use to server the content
119     * @param suffix The suffix to add to the resolved path
120     * @return the path to the resource.
121     */
122    protected String _resolve(String uri, boolean download, boolean absolute, boolean internal, String pipeline, String suffix)
123    {
124        // uri are like content://UUID@metadata;data/file.ext
125        int i = uri.indexOf('@');
126        int j = uri.indexOf(';', i);
127        String id = uri.substring(0, i);
128        String metadata = uri.substring(i + 1, j);
129        String path = uri.substring(j + 1);
130        
131        Request request = ContextHelper.getRequest(_context);
132        
133        Session liveSession = null;
134        try
135        {
136            liveSession = _repository.login(WebConstants.LIVE_WORKSPACE);
137            Content content = _getContent(id, request, liveSession);
138            
139            RichText richText = _getMeta(content.getMetadataHolder(), metadata);
140            File file = _getFile(richText.getAdditionalDataFolder(), path);
141            if (file == null)
142            {
143                getLogger().warn("Cannot resolve link " + uri);
144                return "";
145            }
146            
147            StringBuilder resultPath = new StringBuilder();
148            resultPath.append(_getUriPrefix(request, content, download, absolute, internal))
149                    .append("/")
150                    .append(pipeline)
151                    .append(content.getPath().replaceAll(":", "%3A"))
152                    .append("/_metadata/")
153                    .append(metadata)
154                    .append("/_data/")
155                    .append(URLEncoder.encodePath(path));
156            if (StringUtils.isNotEmpty(suffix))
157            {
158                resultPath.append(suffix);
159            }
160            
161            Map<String, String> params = new HashMap<>();
162            if (download)
163            {
164                params.put("download", "true");
165            }
166            Optional.of(content)
167                    .filter(VersionableAmetysObject.class::isInstance)
168                    .map(VersionableAmetysObject.class::cast)
169                    .map(VersionableAmetysObject::getRevision)
170                    .ifPresent(contentVersion -> params.put("contentVersion", contentVersion));
171            
172            return URLEncoder.encodeURI(resultPath.toString(), params);
173        }
174        catch (Exception e)
175        {
176            throw new IllegalStateException(e);
177        }
178        finally
179        {
180            if (liveSession != null)
181            {
182                liveSession.logout();
183            }
184        }
185    }
186    
187    /**
188     * Retrieve the content, using the request attribute to avoid an additional resolve
189     * @param id The content id
190     * @param request The request
191     * @param session The session into which the content should be retrieved
192     * @return The content
193     * @throws RepositoryException If an error occurred
194     */
195    protected Content _getContent(String id, Request request, Session session) throws RepositoryException
196    {
197        Content content = (Content) request.getAttribute(Content.class.getName()); 
198        if (content == null || !id.equals(content.getId()))
199        {
200            content = _ametysObjectResolver.resolveById(id, session);
201        }
202        return content;
203    }
204    
205    /**
206     * Resolve image as base 64
207     * @param uri the link URI.
208     * @param height the specified height. Ignored if 0.
209     * @param width the specified width. Ignored if 0.
210     * @param maxHeight the maximum image height. Ignored if height or width is specified.
211     * @param maxWidth the maximum image width. Ignored if height or width is specified.
212     * @param cropHeight the cropping height. Ignored if 0.
213     * @param cropWidth the cropping width. Ignored if 0.
214     * @return a base64-encoded string representing the image.
215     */
216    protected String _resolveImageAsBase64(String uri, int height, int width, int maxHeight, int maxWidth, int cropHeight, int cropWidth)
217    {
218        // uri are like content://UUID@metadata;data/file.ext
219        int i = uri.indexOf('@');
220        int j = uri.indexOf(';', i);
221        String id = uri.substring(0, i);
222        String metadata = uri.substring(i + 1, j);
223        String path = uri.substring(j + 1);
224        
225        Request request = ContextHelper.getRequest(_context);
226        
227        Session liveSession = null;
228        try
229        {
230            liveSession = _repository.login(WebConstants.LIVE_WORKSPACE);
231            
232            Content content = _getContent(id, request, liveSession);
233            RichText richText = _getMeta(content.getMetadataHolder(), metadata);
234            File file = _getFile(richText.getAdditionalDataFolder(), path);
235            Resource resource = file.getResource();
236            
237            try (InputStream dataIs = resource.getInputStream())
238            {
239                return ImageResolverHelper.resolveImageAsBase64(dataIs, resource.getMimeType(), height, width, maxHeight, maxWidth, cropHeight, cropWidth);
240            }
241        }
242        catch (Exception e)
243        {
244            throw new IllegalStateException(e);
245        }
246        finally
247        {
248            if (liveSession != null)
249            {
250                liveSession.logout();
251            }
252        }
253    }
254    
255    private String _getUriPrefix(Request request, Content content, boolean download, boolean absolute, boolean internal)
256    {
257        String siteName = WebHelper.getSiteName(request, content);
258        if (StringUtils.isEmpty(siteName))
259        {
260            return getUriPrefix(download, absolute, internal);
261        }
262        
263        if (internal)
264        {
265            return "cocoon://" + siteName;
266        }
267        else if (absolute)
268        {
269            return _prefixHandler.getAbsoluteUriPrefix(siteName);
270        }
271        else
272        {
273            return _prefixHandler.getUriPrefix(siteName);
274        }
275    }
276}