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