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 themeName = request.getParameter("themes");
158        
159        List<String> themes = new ArrayList<>();
160        // We have a theme name ...
161        if (StringUtils.isNotBlank(themeName))
162        {
163            // ... retrieve JCR id from theme name
164            if (_directoryHelper.themeExists(themeName, siteName, lang))
165            {
166                themes.add(themeName);
167            }
168        }
169        // if not, just send an empty list for the user link creation
170        createParameters.put("themes", themes);
171    }
172    
173    private Map<String, Object> _createUserLink(Request request, Map<String, Object> parameters)
174    {
175        // Retrieve the current workspace.
176        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
177        
178        try
179        {
180            // Force the workspace.
181            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE);
182            
183            return _linkDAO.createUserLink(parameters);
184        }
185        finally
186        {
187            // Restore context
188            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
189        }
190    }
191    
192    /**
193     * Set the favicon of the link url
194     * @param url the url of the favicon
195     * @param user the user
196     * @param userAgent The user agent
197     * @return the favicon upload map
198     */
199    protected Upload _getLinkFavicon(String url, UserIdentity user, String userAgent)
200    {
201        Upload upload = null;
202        try
203        {
204            String favIconPath = _urlPreviewComponent.getFavicon(url);
205            String favIconName = StringUtils.substringAfterLast(favIconPath, "/");
206            if (StringUtils.isNotBlank(favIconPath))
207            {
208                RequestConfig requestConfig = RequestConfig.custom()
209                        .setConnectTimeout(_ICO_REQUEST_TIMEOUT)
210                        .setConnectionRequestTimeout(_ICO_REQUEST_TIMEOUT)
211                        .setSocketTimeout(_ICO_REQUEST_TIMEOUT)
212                        .build();
213                
214                try (CloseableHttpClient httpclient = HttpClientBuilder.create()
215                                                                       .setDefaultRequestConfig(requestConfig)
216                                                                       .useSystemProperties()
217                                                                       .setUserAgent(userAgent)
218                                                                       .build())
219                {
220                    // Prepare a request object
221                    HttpGet get = new HttpGet(favIconPath);
222                    try (CloseableHttpResponse httpResponse = httpclient.execute(get))
223                    {
224                        int statusCode = httpResponse.getStatusLine().getStatusCode();
225                        if (statusCode != 200)
226                        {
227                            getLogger().warn("Error " + statusCode + ". Can't set the favicon with url " + favIconPath + " to the link " + url);
228                            return upload;
229                        }
230                        
231                        try (InputStream is = httpResponse.getEntity().getContent())
232                        {
233                            HttpEntity entity = httpResponse.getEntity();
234                            String mimeType = ContentType.get(entity).getMimeType();
235                            if (mimeType.equals("image/x-icon") || mimeType.equals("image/vnd.microsoft.icon"))
236                            {
237                                // If the image is .ico, convert it in .png
238                                InputStream convertedIcoToPngIS = _urlPreviewComponent.convertIcoToPng(is);
239                                upload = _uploadManager.storeUpload(user, StringUtils.replace(favIconName, ".ico", ".png"), convertedIcoToPngIS);
240                            }
241                            else
242                            {
243                                upload = _uploadManager.storeUpload(user, favIconName, is);
244                            }
245                        }
246                    }
247                }
248                catch (IOException e)
249                {
250                    getLogger().warn("Unable to download favicon from url " + favIconPath + " to the link " + url, e);
251                }
252            } 
253        }
254        catch (Exception e) 
255        {
256            getLogger().warn("Unable to get favicon from link url " + url, e);
257        }
258        
259        return upload;
260    }
261}