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