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}