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.link;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.avalon.framework.parameters.Parameters;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.cocoon.acting.ServiceableAction;
029import org.apache.cocoon.environment.ObjectModelHelper;
030import org.apache.cocoon.environment.Redirector;
031import org.apache.cocoon.environment.Request;
032import org.apache.cocoon.environment.Response;
033import org.apache.cocoon.environment.SourceResolver;
034import org.apache.commons.lang3.StringUtils;
035import org.apache.http.HttpEntity;
036import org.apache.http.client.config.RequestConfig;
037import org.apache.http.client.methods.CloseableHttpResponse;
038import org.apache.http.client.methods.HttpGet;
039import org.apache.http.entity.ContentType;
040import org.apache.http.impl.client.CloseableHttpClient;
041import org.apache.http.impl.client.HttpClientBuilder;
042
043import org.ametys.core.cocoon.JSonReader;
044import org.ametys.core.upload.Upload;
045import org.ametys.core.upload.UploadManager;
046import org.ametys.core.user.CurrentUserProvider;
047import org.ametys.core.user.UserIdentity;
048import org.ametys.core.util.JSONUtils;
049import org.ametys.plugins.linkdirectory.DirectoryHelper;
050import org.ametys.plugins.linkdirectory.Link;
051import org.ametys.plugins.linkdirectory.Link.LinkType;
052import org.ametys.plugins.repository.RepositoryConstants;
053import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
054import org.ametys.web.repository.site.SiteManager;
055import org.ametys.web.url.UrlPreviewComponent;
056
057/**
058 * Adds a new user link in Ametys.
059 */
060public class AddUserLinkAction extends ServiceableAction
061{
062    private static int _ICO_REQUEST_TIMEOUT = 3000;
063    
064    /** The DAO for {@link Link}s */
065    protected LinkDAO _linkDAO;
066    /** The current user provider */
067    protected CurrentUserProvider _currentUserProvider;
068    /** The directory helper */
069    protected DirectoryHelper _directoryHelper;
070    /** The site manager */
071    protected SiteManager _siteManager;
072    /** The url preview component */
073    protected UrlPreviewComponent _urlPreviewComponent;
074    /** The upload manager */
075    protected UploadManager _uploadManager;
076    /** The JSON utils */
077    protected JSONUtils _jsonUtils;
078    
079    @Override
080    public void service(ServiceManager smanager) throws ServiceException
081    {
082        super.service(smanager);
083        _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
084        _linkDAO = (LinkDAO) smanager.lookup(LinkDAO.ROLE);
085        _directoryHelper = (DirectoryHelper) smanager.lookup(DirectoryHelper.ROLE);
086        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
087        _urlPreviewComponent = (UrlPreviewComponent) smanager.lookup(UrlPreviewComponent.ROLE);
088        _uploadManager = (UploadManager) smanager.lookup(UploadManager.ROLE);
089        _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE);
090    }
091    
092    @Override
093    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
094    {
095        Request request = ObjectModelHelper.getRequest(objectModel);
096        Map<String, Object> result = new HashMap<>();
097        
098        UserIdentity user = _currentUserProvider.getUser();
099        
100        // Check user authenticated
101        if (user != null)
102        {
103            // Create link
104            Map<String, Object> createParameters = new HashMap<>();
105            String siteName = request.getParameter("siteName");
106            String lang = request.getParameter("lang");
107            String url = request.getParameter("url");
108            //The color key, default color will be used if it is not present or empty
109            String color = request.getParameter("color");
110            
111            createParameters.put("siteName", siteName);
112            createParameters.put("lang", lang);
113            createParameters.put("url-type", LinkType.URL.toString());
114            createParameters.put("url", url);
115            createParameters.put("title", request.getParameter("title"));
116            createParameters.put("color", color);
117            
118            String userAgent = request.getHeader("User-Agent");
119            Upload upload = _getLinkFavicon(url, user, userAgent);
120            if (upload != null)
121            {
122                Map<String, Object> linkMap = new HashMap<>();
123                linkMap.put("id", upload.getId());
124                linkMap.put("type", "external");
125                createParameters.put("picture", _jsonUtils.convertObjectToJson(linkMap));
126            }
127            
128            _handleTheme(request, createParameters, siteName, lang);
129            
130            Map<String, Object> createdLinkresult = _createUserLink(request, createParameters);
131            
132            if (createdLinkresult.containsKey("already-exists"))
133            {
134                result.put("error", "already-exists");
135            }
136            else
137            {
138                result.put("error", "none");
139                result.putAll(createdLinkresult);
140            }
141        }
142        else
143        {
144            result.put("error", "unauthenticated-user");
145        }
146        
147        Response response = ObjectModelHelper.getResponse(objectModel);
148        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
149        response.setHeader("Access-Control-Allow-Credentials", "true");
150        
151        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
152        return EMPTY_MAP;
153    }
154    
155    private void _handleTheme(Request request, Map<String, Object> createParameters, String siteName, String lang)
156    {
157        String themeAsString = request.getParameter("themes");
158        
159        List<String> themes = new ArrayList<>();
160        // We have a theme name ...
161        if (StringUtils.isNotBlank(themeAsString))
162        {
163            // ... retrieve JCR id from theme name
164            String themeId = _directoryHelper.getThemeIdFromThemeName(_siteManager.getSite(siteName), lang, themeAsString);
165            if (StringUtils.isNotBlank(themeId))
166            {
167                themes.add(themeId);
168            }
169        }
170        // if not, just send an empty list for the user link creation
171        createParameters.put("themes", themes);
172    }
173    
174    private Map<String, Object> _createUserLink(Request request, Map<String, Object> parameters)
175    {
176        // Retrieve the current workspace.
177        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
178        
179        try
180        {
181            // Force the workspace.
182            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE);
183            
184            return _linkDAO.createUserLink(parameters);
185        }
186        finally
187        {
188            // Restore context
189            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
190        }
191    }
192    
193    /**
194     * Set the favicon of the link url
195     * @param url the url of the favicon
196     * @param user the user
197     * @param userAgent The user agent
198     * @return the favicon upload map
199     */
200    protected Upload _getLinkFavicon(String url, UserIdentity user, String userAgent)
201    {
202        Upload upload = null;
203        try
204        {
205            String favIconPath = _urlPreviewComponent.getFavicon(url);
206            String favIconName = StringUtils.substringAfterLast(favIconPath, "/");
207            if (StringUtils.isNotBlank(favIconPath))
208            {
209                RequestConfig requestConfig = RequestConfig.custom()
210                        .setConnectTimeout(_ICO_REQUEST_TIMEOUT)
211                        .setConnectionRequestTimeout(_ICO_REQUEST_TIMEOUT)
212                        .setSocketTimeout(_ICO_REQUEST_TIMEOUT)
213                        .build();
214                
215                try (CloseableHttpClient httpclient = HttpClientBuilder.create()
216                                                                       .setDefaultRequestConfig(requestConfig)
217                                                                       .useSystemProperties()
218                                                                       .setUserAgent(userAgent)
219                                                                       .build())
220                {
221                    // Prepare a request object
222                    HttpGet get = new HttpGet(favIconPath);
223                    try (CloseableHttpResponse httpResponse = httpclient.execute(get))
224                    {
225                        int statusCode = httpResponse.getStatusLine().getStatusCode();
226                        if (statusCode != 200)
227                        {
228                            getLogger().warn("Error " + statusCode + ". Can't set the favicon with url " + favIconPath + " to the link " + url);
229                            return upload;
230                        }
231                        
232                        try (InputStream is = httpResponse.getEntity().getContent())
233                        {
234                            HttpEntity entity = httpResponse.getEntity();
235                            String mimeType = ContentType.get(entity).getMimeType();
236                            if (mimeType.equals("image/x-icon") || mimeType.equals("image/vnd.microsoft.icon"))
237                            {
238                                // If the image is .ico, convert it in .png
239                                InputStream convertedIcoToPngIS = _urlPreviewComponent.convertIcoToPng(is);
240                                upload = _uploadManager.storeUpload(user, StringUtils.replace(favIconName, ".ico", ".png"), convertedIcoToPngIS);
241                            }
242                            else
243                            {
244                                upload = _uploadManager.storeUpload(user, favIconName, is);
245                            }
246                        }
247                    }
248                }
249                catch (IOException e)
250                {
251                    getLogger().warn("Unable to download favicon from url " + favIconPath + " to the link " + url, e);
252                }
253            } 
254        }
255        catch (Exception e) 
256        {
257            getLogger().warn("Unable to get favicon from link url " + url, e);
258        }
259        
260        return upload;
261    }
262}