001/*
002 *  Copyright 2020 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.mobileapp.action;
017
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.IOException;
021import java.net.MalformedURLException;
022import java.nio.charset.StandardCharsets;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Optional;
028
029import org.apache.avalon.framework.activity.Initializable;
030import org.apache.avalon.framework.parameters.Parameters;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.cocoon.acting.ServiceableAction;
034import org.apache.cocoon.environment.ObjectModelHelper;
035import org.apache.cocoon.environment.Redirector;
036import org.apache.cocoon.environment.Request;
037import org.apache.cocoon.environment.SourceResolver;
038import org.apache.cocoon.environment.http.HttpResponse;
039import org.apache.commons.io.FileUtils;
040import org.apache.commons.lang3.StringUtils;
041import org.apache.excalibur.source.Source;
042import org.apache.excalibur.source.impl.FileSource;
043
044import org.ametys.core.cocoon.JSonReader;
045import org.ametys.core.util.JSONUtils;
046import org.ametys.runtime.config.Config;
047import org.ametys.web.repository.site.Site;
048import org.ametys.web.repository.site.SiteManager;
049
050import com.google.gson.JsonParseException;
051
052/**
053 * Returns the theme for a site
054 */
055public class GetThemeAction extends ServiceableAction implements Initializable
056{
057    /** global configuration about projects enabled/disabled */
058    protected static final String __PROJECT_ENABLED_CONF_ID = "plugin.mobileapp.project.enabled";
059    
060    /** Path of the base directory for mobileapp conf */
061    protected static final String BASE_DIR = "context://WEB-INF/param/mobileapp/";
062    
063    /** Path of the theme.json file */ 
064    public static final String THEME_JSON = BASE_DIR + "theme.json";
065
066    /** Authentication Token Manager */
067    protected SiteManager _siteManager;
068    
069    /** Source Resolver */
070    protected org.apache.excalibur.source.SourceResolver _sourceResolver;
071    
072    /** JSON Utils */
073    protected JSONUtils _jsonUtils;
074    
075    /** The home web view from the configuration */
076    protected String _homeWebView;
077    
078    @Override
079    public void service(ServiceManager smanager) throws ServiceException
080    {
081        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
082        _sourceResolver = (org.apache.excalibur.source.SourceResolver) smanager.lookup(org.apache.excalibur.source.SourceResolver.ROLE);
083        _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE);
084    }
085    
086    @Override
087    public void initialize() throws Exception
088    {
089        _homeWebView = Optional.of("plugin.mobileapp.home.webview")
090                               .map(Config.getInstance()::<String>getValue)
091                               .filter(StringUtils::isNotBlank)
092                               .orElse(null);
093    }
094    
095    @Override
096    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
097    {
098        Map<String, Object> result = new HashMap<>();
099        Request request = ObjectModelHelper.getRequest(objectModel);
100
101        String siteName = parameters.getParameter("site");
102        Site site = _siteManager.getSite(siteName);
103        
104        if (site != null)
105        {
106            try
107            {
108                Map<String, Object> jsonMap = parseJson();
109                result = transformMap(jsonMap, site);
110                // Icons first, this is where it can fail
111                
112                result.put("code", 200);
113                result.put("name", site.getTitle());
114                result.put("main_link", site.getUrl());
115                
116                boolean projectsEnabled = Config.getInstance().getValue(__PROJECT_ENABLED_CONF_ID);
117                result.put("project_enabled", projectsEnabled);
118                
119                if (_homeWebView != null)
120                {
121                    result.put("home_webview", _homeWebView);
122                }
123            }
124            catch (JsonParseException e)
125            {
126                getLogger().error("Invalid JSON file for " + THEME_JSON, e);
127                result = new HashMap<>();
128                result.put("code", 500);
129                result.put("message", "invalid-icons-json");
130                HttpResponse response = (HttpResponse) ObjectModelHelper.getResponse(objectModel);
131                response.setStatus(500);
132            }
133        }
134        else
135        {
136            result.put("code", 500);
137            result.put("message", "site-not-found");
138            HttpResponse response = (HttpResponse) ObjectModelHelper.getResponse(objectModel);
139            response.setStatus(500);
140        }
141
142        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
143        return EMPTY_MAP;
144    }
145    
146    /**
147     * Get the content of the json file as a map
148     * @return the map representing the json file
149     * @throws IOException impossible to read the json file
150     */
151    protected Map<String, Object> parseJson() throws IOException
152    {
153        Source src = _sourceResolver.resolveURI(THEME_JSON);
154        
155        File file = ((FileSource) src).getFile();
156        
157        if (!file.exists())
158        {
159            throw new JsonParseException("The file " + THEME_JSON + " does not exist.");
160        }
161        
162        try (FileInputStream is = FileUtils.openInputStream(file))
163        {
164            String body = new String(is.readAllBytes(), StandardCharsets.UTF_8);
165            Map<String, Object> jsonMap = _jsonUtils.convertJsonToMap(body);
166            return jsonMap;
167        }
168        catch (IllegalArgumentException e)
169        {
170            throw new JsonParseException("The file " + THEME_JSON + " must be a valid json containing a map with list of maps, at least : {\"icons\":[{\"src\":\"resources/img/logo96x96.png\"}]}.");
171        }
172    }
173    
174    /**
175     * Parse the input json, and transform all String called src to be relative to the site and skin used.
176     * @param jsonMap the map read from the json file
177     * @param site site to use to get it's url and skin
178     * @return the json map with src modified
179     * @throws MalformedURLException impossible to find the json file
180     * @throws IOException impossible to read the json file
181     * @throws JsonParseException json file inexistant or invalid
182     */
183    protected Map<String, Object> transformMap(Map<String, Object> jsonMap, Site site) throws MalformedURLException, IOException, JsonParseException
184    {
185        Map<String, Object> result = new HashMap<>();
186        
187        for (String key : jsonMap.keySet())
188        {
189            Object object = jsonMap.get(key);
190            Object transformedObject = transformObject(key, object, site);
191            result.put(key, transformedObject);
192        }
193        
194        return result;
195    }
196    
197    /**
198     * Parse the input json, and transform all String called src to be relative to the site and skin used.
199     * @param jsonList a list in the json file
200     * @param site site to use to get it's url and skin
201     * @return the json map with src modified
202     * @throws MalformedURLException impossible to find the json file
203     * @throws IOException impossible to read the json file
204     * @throws JsonParseException json file inexistant or invalid
205     */
206    protected List<Object> transformList(List<Object> jsonList, Site site) throws MalformedURLException, IOException, JsonParseException
207    {
208        List<Object> result = new ArrayList<>();
209        for (Object object : jsonList)
210        {
211            Object transformedObject = transformObject(null, object, site);
212            result.add(transformedObject);
213        }
214        return result;
215    }
216
217    /**
218     * Parse the input json, and transform all String called src to be relative to the site and skin used.
219     * @param key in case the item was in a map, the key associated (only src will be translated)
220     * @param object the object to read from the json
221     * @param site site to use to get it's url and skin
222     * @return the json map with src modified
223     * @throws MalformedURLException impossible to find the json file
224     * @throws IOException impossible to read the json file
225     * @throws JsonParseException json file inexistant or invalid
226     */
227    protected Object transformObject(String key, Object object, Site site) throws MalformedURLException, IOException, JsonParseException
228    {
229        Object result = object;
230        if (object instanceof String)
231        {
232            String value = (String) object;
233            if ("src".equalsIgnoreCase(key))
234            {
235                result = getImageUrl(value, site);
236            }
237        }
238        else if (object instanceof List)
239        {
240            @SuppressWarnings("unchecked")
241            List<Object> list = (List<Object>) object;
242            result = transformList(list, site);
243        }
244        else if (object instanceof Map)
245        {
246            @SuppressWarnings("unchecked")
247            Map<String, Object> map = (Map<String, Object>) object;
248            result = transformMap(map, site);
249        }
250        
251        return result;
252    }
253    
254    /**
255     * Get thu full url of the theme image, based on the site url and used skin
256     * @param relativePath path of the image in the skin
257     * @param site site used
258     * @return the image full url
259     */
260    protected String getImageUrl(String relativePath, Site site)
261    {
262        String url = site.getUrl()
263                + "/skins/"
264                + site.getSkinId()
265                + "/"
266                + relativePath;
267        
268        return url;
269    }
270}