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