001/* 002 * Copyright 2015 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.runtime.plugins.admin.system; 017 018import java.io.File; 019import java.io.FileInputStream; 020import java.io.FileOutputStream; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.util.HashMap; 024import java.util.Locale; 025import java.util.Map; 026import java.util.Properties; 027 028import javax.xml.transform.OutputKeys; 029import javax.xml.transform.TransformerFactory; 030import javax.xml.transform.sax.SAXTransformerFactory; 031import javax.xml.transform.sax.TransformerHandler; 032import javax.xml.transform.stream.StreamResult; 033 034import org.apache.avalon.framework.component.Component; 035import org.apache.avalon.framework.configuration.Configuration; 036import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 037import org.apache.avalon.framework.context.Context; 038import org.apache.avalon.framework.context.ContextException; 039import org.apache.avalon.framework.context.Contextualizable; 040import org.apache.avalon.framework.logger.AbstractLogEnabled; 041import org.apache.avalon.framework.service.ServiceException; 042import org.apache.avalon.framework.service.ServiceManager; 043import org.apache.avalon.framework.service.Serviceable; 044import org.apache.cocoon.ProcessingException; 045import org.apache.cocoon.components.ContextHelper; 046import org.apache.cocoon.xml.XMLUtils; 047import org.apache.commons.lang3.StringUtils; 048import org.xml.sax.helpers.AttributesImpl; 049 050import org.ametys.core.ui.Callable; 051import org.ametys.core.util.I18nUtils; 052import org.ametys.runtime.i18n.I18nizableText; 053import org.ametys.runtime.servlet.RuntimeConfig; 054 055/** 056 * Helper for manipulating system announcement 057 */ 058public class SystemHelper extends AbstractLogEnabled implements Component, Serviceable, Contextualizable 059{ 060 /** The relative path to the file where system information are saved (announcement, maintenance...) */ 061 public static final String ADMINISTRATOR_SYSTEM_FILE = "administrator/system.xml"; 062 /** Avalon role */ 063 public static final String ROLE = SystemHelper.class.getName(); 064 065 private I18nUtils _i18nUtils; 066 private Context _context; 067 068 @Override 069 public void service(ServiceManager serviceManager) throws ServiceException 070 { 071 _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE); 072 } 073 074 @Override 075 public void contextualize(Context context) throws ContextException 076 { 077 _context = context; 078 } 079 080 /** 081 * Enables or disable system announcement 082 * @param available true to enable system announcement 083 * @throws ProcessingException if an error occurred 084 */ 085 @Callable 086 public void setAnnouncementAvailable (boolean available) throws ProcessingException 087 { 088 SystemAnnouncement systemAnnouncement = readValues(); 089 090 _save(available, systemAnnouncement.getMessages()); 091 } 092 093 /** 094 * Add or edit a system announcement 095 * @param language the language typed in by the user or "*" if modifying the default message 096 * @param message the message to add nor edit 097 * @param override true to override the existing value if exists 098 * @return the result map 099 * @throws Exception if an exception occurs 100 */ 101 @Callable 102 public Map<String, Object> editAnnouncement(String language, String message, boolean override) throws Exception 103 { 104 Map<String, Object> result = new HashMap<> (); 105 106 SystemAnnouncement sytemAnnouncement = readValues(); 107 108 Map<String, String> messages = sytemAnnouncement.getMessages(); 109 if (messages.containsKey(language) && !override) 110 { 111 result.put("already-exists", true); 112 return result; 113 } 114 115 // Add or edit message 116 messages.put(language, message); 117 118 _save(sytemAnnouncement.isAvailable(), messages); 119 120 return result; 121 } 122 123 /** 124 * Delete a announcement 125 * @param language the language of the announcement to delete 126 * @throws ProcessingException if an exception occurs 127 * @return an empty map 128 */ 129 @Callable 130 public Map deleteAnnouncement(String language) throws ProcessingException 131 { 132 Map<String, Object> result = new HashMap<> (); 133 134 SystemAnnouncement sytemAnnouncement = readValues(); 135 136 Map<String, String> messages = sytemAnnouncement.getMessages(); 137 if (messages.containsKey(language)) 138 { 139 messages.remove(language); 140 141 _save(sytemAnnouncement.isAvailable(), messages); 142 } 143 144 return result; 145 } 146 147 /** 148 * Saves the system announcement's values 149 * @param state true to enable system announcement 150 * @param messages the messages 151 * @throws ProcessingException if an error ocurred 152 */ 153 private void _save (boolean state, Map<String, String> messages) throws ProcessingException 154 { 155 File systemFile = new File(RuntimeConfig.getInstance().getAmetysHome(), ADMINISTRATOR_SYSTEM_FILE); 156 157 try 158 { 159 // Create file if not exists 160 if (!systemFile.exists()) 161 { 162 systemFile.getParentFile().mkdirs(); 163 systemFile.createNewFile(); 164 } 165 166 // create a transformer for saving sax into a file 167 TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); 168 169 // create the result where to write 170 try (OutputStream os = new FileOutputStream(systemFile)) 171 { 172 StreamResult sResult = new StreamResult(os); 173 th.setResult(sResult); 174 175 // create the format of result 176 Properties format = new Properties(); 177 format.put(OutputKeys.METHOD, "xml"); 178 format.put(OutputKeys.INDENT, "yes"); 179 format.put(OutputKeys.ENCODING, "UTF-8"); 180 th.getTransformer().setOutputProperties(format); 181 182 // Send SAX events 183 th.startDocument(); 184 185 AttributesImpl announcementsAttrs = new AttributesImpl(); 186 announcementsAttrs.addAttribute("", "state", "state", "CDATA", state ? "on" : "off"); 187 XMLUtils.startElement(th, "announcements", announcementsAttrs); 188 189 for (String id : messages.keySet()) 190 { 191 AttributesImpl announcementAttrs = new AttributesImpl(); 192 if (!"*".equals(id)) 193 { 194 announcementAttrs.addAttribute("", "lang", "lang", "CDATA", id); 195 } 196 197 XMLUtils.createElement(th, "announcement", announcementAttrs, messages.get(id)); 198 } 199 200 XMLUtils.endElement(th, "announcements"); 201 202 th.endDocument(); 203 } 204 } 205 catch (Exception e) 206 { 207 throw new ProcessingException("Unable to save system announcement values", e); 208 } 209 } 210 211 /** 212 * Tests if system announcements are active. 213 * @return true if system announcements are active. 214 */ 215 @Callable 216 public boolean isSystemAnnouncementAvailable() 217 { 218 SystemAnnouncement systemAnnouncement = readValues(); 219 return systemAnnouncement.isAvailable(); 220 } 221 222 /** 223 * Return the date of the last modification of the annonce 224 * @return The date of the last modification or 0 if there is no announce file 225 */ 226 public long getSystemAnnoucementLastModificationDate() 227 { 228 try 229 { 230 File systemFile = new File(RuntimeConfig.getInstance().getAmetysHome(), ADMINISTRATOR_SYSTEM_FILE); 231 if (!systemFile.exists() || !systemFile.isFile()) 232 { 233 return 0; 234 } 235 236 return systemFile.lastModified(); 237 } 238 catch (Exception e) 239 { 240 throw new RuntimeException("Unable to get system announcements", e); 241 } 242 } 243 244 /** 245 * Returns the system announcement for the given language code, or for the default language code if there is no specified announcement for the given language code.<br> 246 * Returns null if the system announcements are not activated. 247 * @param languageCode the desired language code of the system announcement 248 * @return the system announcement in the specified language code, or in the default language code, or null if announcements are not active. 249 */ 250 public String getSystemAnnouncement(String languageCode) 251 { 252 SystemAnnouncement systemAnnouncement = readValues(); 253 254 if (!systemAnnouncement.isAvailable()) 255 { 256 return null; 257 } 258 259 Map<String, String> messages = systemAnnouncement.getMessages(); 260 261 String announcement = null; 262 if (messages.containsKey(languageCode)) 263 { 264 announcement = messages.get(languageCode); 265 } 266 267 if (StringUtils.isEmpty(announcement)) 268 { 269 String defaultAnnouncement = messages.containsKey("*") ? messages.get("*") : null; 270 if (StringUtils.isEmpty(defaultAnnouncement)) 271 { 272 throw new IllegalStateException("There must be a default announcement."); 273 } 274 275 return defaultAnnouncement; 276 } 277 278 return announcement; 279 } 280 281 /** 282 * Read the system announcement's values 283 * @return The system announcement values; 284 */ 285 public SystemAnnouncement readValues () 286 { 287 SystemAnnouncement announcement = new SystemAnnouncement(); 288 289 try 290 { 291 File systemFile = new File(RuntimeConfig.getInstance().getAmetysHome(), ADMINISTRATOR_SYSTEM_FILE); 292 if (!systemFile.exists() || !systemFile.isFile()) 293 { 294 _setDefaultValues(); 295 } 296 297 Configuration configuration; 298 try (InputStream is = new FileInputStream(systemFile)) 299 { 300 configuration = new DefaultConfigurationBuilder().build(is); 301 } 302 303 // State 304 String state = configuration.getAttribute("state", "off"); 305 boolean isAvailable = "on".equals(state); 306 announcement.setAvailable(isAvailable); 307 308 // Announcements 309 for (Configuration announcementConfiguration : configuration.getChildren("announcement")) 310 { 311 String lang = announcementConfiguration.getAttribute("lang", "*"); 312 String message = announcementConfiguration.getValue(); 313 314 announcement.addMessage(lang, message); 315 } 316 317 return announcement; 318 } 319 catch (Exception e) 320 { 321 throw new RuntimeException("Unable to get system announcements", e); 322 } 323 } 324 325 private void _setDefaultValues () throws ProcessingException 326 { 327 Map objectModel = ContextHelper.getObjectModel(_context); 328 Locale locale = org.apache.cocoon.i18n.I18nUtils.findLocale(objectModel, "locale", null, Locale.getDefault(), true); 329 String defaultMessage = _i18nUtils.translate(new I18nizableText("plugin.admin", "PLUGINS_ADMIN_SYSTEM_DEFAULTMESSAGE"), locale.getLanguage()); 330 331 Map<String, String> messages = new HashMap<> (); 332 messages.put("*", defaultMessage); 333 334 _save(false, messages); 335 } 336 337 /** 338 * Class representing the system announcement file 339 */ 340 public class SystemAnnouncement 341 { 342 private boolean _available; 343 private Map<String, String> _messages; 344 345 /** 346 * Constructor 347 */ 348 public SystemAnnouncement() 349 { 350 _available = false; 351 _messages = new HashMap<>(); 352 } 353 354 /** 355 * Is the system announcement available ? 356 * @return true if the system announcement is available, false otherwise 357 */ 358 public boolean isAvailable() 359 { 360 return _available; 361 } 362 363 /** 364 * Get the messages by language 365 * @return the messages by languaga 366 */ 367 public Map<String, String> getMessages () 368 { 369 return _messages; 370 } 371 372 /** 373 * Set the availability of the system announcement 374 * @param available true to set the system announcement available, false otherwise 375 */ 376 public void setAvailable (boolean available) 377 { 378 _available = available; 379 } 380 381 /** 382 * Add a message to the list of announcements 383 * @param lang the language of the message 384 * @param message the message itself 385 */ 386 public void addMessage (String lang, String message) 387 { 388 _messages.put(lang, message); 389 } 390 391 } 392}