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