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}