001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.i18n; 029 030import org.opencms.db.CmsPublishedResource; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.file.CmsResourceFilter; 034import org.opencms.file.types.I_CmsResourceType; 035import org.opencms.main.CmsEvent; 036import org.opencms.main.CmsException; 037import org.opencms.main.CmsLog; 038import org.opencms.main.I_CmsEventListener; 039import org.opencms.main.OpenCms; 040import org.opencms.util.CmsStringUtil; 041import org.opencms.util.CmsUUID; 042 043import java.util.Collection; 044import java.util.HashSet; 045import java.util.List; 046import java.util.Locale; 047import java.util.Set; 048 049import org.apache.commons.logging.Log; 050 051import com.google.common.collect.Lists; 052 053/** 054 * Manages message bundles loaded from the VFS.<p> 055 */ 056public class CmsVfsBundleManager implements I_CmsEventListener { 057 058 /** 059 * Data holder for a base name and locale of a message bundle.<p> 060 */ 061 public static class NameAndLocale { 062 063 /** The locale. */ 064 private Locale m_locale; 065 066 /** The base name. */ 067 private String m_name; 068 069 /** 070 * Creates a new instance.<p> 071 * 072 * @param name the base name 073 * @param locale the locale 074 */ 075 public NameAndLocale(String name, Locale locale) { 076 077 m_name = name; 078 m_locale = locale; 079 } 080 081 /** 082 * Gets the locale.<p> 083 * 084 * @return the locale 085 */ 086 public Locale getLocale() { 087 088 return m_locale; 089 } 090 091 /** 092 * Gets the base name.<p> 093 * 094 * @return the base name 095 */ 096 public String getName() { 097 098 return m_name; 099 } 100 } 101 102 /** Resource type name for plain-text properties files containing messages. */ 103 public static final String TYPE_PROPERTIES_BUNDLE = "propertyvfsbundle"; 104 105 /** Resource type name for XML contents containing messages. */ 106 public static final String TYPE_XML_BUNDLE = "xmlvfsbundle"; 107 108 /** The logger instance for this class. */ 109 protected static final Log LOG = CmsLog.getLog(CmsVfsBundleManager.class); 110 111 /** The set of bundle base names. */ 112 private Set<String> m_bundleBaseNames; 113 114 /** The CMS context to use. */ 115 private CmsObject m_cms; 116 117 /** Indicated if a reload is already scheduled. */ 118 private boolean m_reloadIsScheduled; 119 120 /** Thread generation counter. */ 121 private int m_threadCount; 122 123 /** 124 * Creates a new instance.<p> 125 * 126 * @param cms the CMS context to use 127 */ 128 public CmsVfsBundleManager(CmsObject cms) { 129 130 m_cms = cms; 131 m_bundleBaseNames = new HashSet<String>(); 132 CmsVfsResourceBundle.setCmsObject(cms); 133 OpenCms.getEventManager().addCmsEventListener( 134 this, 135 new int[] {I_CmsEventListener.EVENT_PUBLISH_PROJECT, I_CmsEventListener.EVENT_CLEAR_CACHES}); 136 // immediately load all bundles for the first time 137 reload(true); 138 } 139 140 /** 141 * Extracts the locale and base name from a resource's file name.<p> 142 * 143 * @param bundleRes the resource for which to get the base name and locale 144 * @return a bean containing the base name and locale 145 */ 146 public static NameAndLocale getNameAndLocale(CmsResource bundleRes) { 147 148 String fileName = bundleRes.getName(); 149 if (TYPE_PROPERTIES_BUNDLE.equals(OpenCms.getResourceManager().getResourceType(bundleRes).getTypeName())) { 150 String localeSuffix = CmsStringUtil.getLocaleSuffixForName(fileName); 151 if (localeSuffix == null) { 152 return new NameAndLocale(fileName, null); 153 } else { 154 String base = fileName.substring( 155 0, 156 fileName.lastIndexOf(localeSuffix) - (1 /* cut off trailing underscore, too*/)); 157 Locale locale = CmsLocaleManager.getLocale(localeSuffix); 158 return new NameAndLocale(base, locale); 159 } 160 } else { 161 return new NameAndLocale(fileName, null); 162 } 163 } 164 165 /** 166 * Collects all locales possibly used in the system.<p> 167 * 168 * @return the collection of all locales 169 */ 170 private static Collection<Locale> getAllLocales() { 171 172 Set<Locale> result = new HashSet<Locale>(); 173 result.addAll(OpenCms.getWorkplaceManager().getLocales()); 174 result.addAll(OpenCms.getLocaleManager().getAvailableLocales()); 175 return result; 176 } 177 178 /** 179 * @see org.opencms.main.I_CmsEventListener#cmsEvent(org.opencms.main.CmsEvent) 180 */ 181 public void cmsEvent(CmsEvent event) { 182 183 // wrap in try-catch so that errors don't affect other handlers 184 try { 185 handleEvent(event); 186 } catch (Throwable t) { 187 LOG.error(t.getLocalizedMessage(), t); 188 } 189 } 190 191 /** 192 * Indicates if a reload thread is currently scheduled. 193 * 194 * @return <code>true</code> if a reload is currently scheduled 195 */ 196 public boolean isReloadScheduled() { 197 198 return m_reloadIsScheduled; 199 } 200 201 /** 202 * Re-initializes the resource bundles.<p> 203 * 204 * @param isStartup true when this is called during startup 205 */ 206 public synchronized void reload(boolean isStartup) { 207 208 if ((OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) 209 && OpenCms.getResourceManager().hasResourceType(TYPE_XML_BUNDLE)) { 210 List<CmsResource> xmlBundles = Lists.newArrayList(); 211 List<CmsResource> propertyBundles = Lists.newArrayList(); 212 try { 213 I_CmsResourceType xmlType = OpenCms.getResourceManager().getResourceType(TYPE_XML_BUNDLE); 214 xmlBundles = m_cms.readResources("/", CmsResourceFilter.ALL.addRequireType(xmlType), true); 215 } catch (Exception e) { 216 logError(e, isStartup); 217 } 218 try { 219 I_CmsResourceType propType = OpenCms.getResourceManager().getResourceType(TYPE_PROPERTIES_BUNDLE); 220 propertyBundles = m_cms.readResources("/", CmsResourceFilter.ALL.addRequireType(propType), true); 221 } catch (Exception e) { 222 logError(e, isStartup); 223 } 224 try { 225 226 synchronized (CmsResourceBundleLoader.class) { 227 CmsResourceBundleLoader.flushBundleCache(); 228 for (String baseName : m_bundleBaseNames) { 229 CmsResourceBundleLoader.flushPermanentCache(baseName); 230 } 231 m_bundleBaseNames.clear(); 232 for (CmsResource xmlBundle : xmlBundles) { 233 addXmlBundle(xmlBundle); 234 } 235 for (CmsResource propertyBundle : propertyBundles) { 236 addPropertyBundle(propertyBundle); 237 } 238 if (OpenCms.getWorkplaceManager() != null) { 239 OpenCms.getWorkplaceManager().flushMessageCache(); 240 } 241 } 242 } catch (Exception e) { 243 logError(e, isStartup); 244 } 245 } 246 } 247 248 /** 249 * Sets the information if a reload thread is currently scheduled. 250 * 251 * @param reloadIsScheduled if <code>true</code> there is a reload currently scheduled 252 */ 253 public void setReloadScheduled(boolean reloadIsScheduled) { 254 255 m_reloadIsScheduled = reloadIsScheduled; 256 } 257 258 /** 259 * Shuts down the VFS bundle manager.<p> 260 * 261 * This will cause the internal reloading Thread not reload in case it is still running.<p> 262 */ 263 public void shutDown() { 264 265 // we don't want to listen to further events 266 OpenCms.getEventManager().removeCmsEventListener(this); 267 setReloadScheduled(false); 268 if (CmsLog.INIT.isInfoEnabled()) { 269 CmsLog.INIT.info( 270 org.opencms.staticexport.Messages.get().getBundle().key( 271 org.opencms.staticexport.Messages.INIT_SHUTDOWN_1, 272 this.getClass().getName())); 273 } 274 } 275 276 /** 277 * Logs an exception that occurred.<p> 278 * 279 * @param e the exception to log 280 * @param logToErrorChannel if true erros should be written to the error channel instead of the info channel 281 */ 282 protected void logError(Exception e, boolean logToErrorChannel) { 283 284 if (logToErrorChannel) { 285 LOG.error(e.getLocalizedMessage(), e); 286 } else { 287 LOG.info(e.getLocalizedMessage(), e); 288 } 289 // if an error was logged make sure that the flag to schedule a reload is reset 290 setReloadScheduled(false); 291 } 292 293 /** 294 * Internal method for adding a resource bundle to the internal cache.<p> 295 * 296 * @param baseName the base name of the resource bundle 297 * @param locale the locale of the resource bundle 298 * @param bundle the resource bundle to add 299 */ 300 private void addBundle(String baseName, Locale locale, I_CmsResourceBundle bundle) { 301 302 CmsResourceBundleLoader.addBundleToCache(baseName, locale, bundle); 303 } 304 305 /** 306 * Adds a resource bundle based on a properties file in the VFS.<p> 307 * 308 * @param bundleResource the properties file 309 */ 310 private void addPropertyBundle(CmsResource bundleResource) { 311 312 NameAndLocale nameAndLocale = getNameAndLocale(bundleResource); 313 Locale locale = nameAndLocale.getLocale(); 314 315 String baseName = nameAndLocale.getName(); 316 m_bundleBaseNames.add(baseName); 317 LOG.info( 318 String.format( 319 "Adding property VFS bundle (path=%s, name=%s, locale=%s)", 320 bundleResource.getRootPath(), 321 baseName, 322 "" + locale)); 323 Locale paramLocale = locale != null ? locale : CmsLocaleManager.getDefaultLocale(); 324 CmsVfsBundleParameters params = new CmsVfsBundleParameters( 325 nameAndLocale.getName(), 326 bundleResource.getRootPath(), 327 paramLocale, 328 locale == null, 329 CmsVfsResourceBundle.TYPE_PROPERTIES); 330 CmsVfsResourceBundle bundle = new CmsVfsResourceBundle(params); 331 addBundle(baseName, locale, bundle); 332 } 333 334 /** 335 * Adds an XML based message bundle.<p> 336 * 337 * @param xmlBundle the XML content containing the message bundle data 338 */ 339 private void addXmlBundle(CmsResource xmlBundle) { 340 341 String name = xmlBundle.getName(); 342 String path = xmlBundle.getRootPath(); 343 m_bundleBaseNames.add(name); 344 345 LOG.info(String.format("Adding property VFS bundle (path=%s, name=%s)", xmlBundle.getRootPath(), name)); 346 for (Locale locale : getAllLocales()) { 347 CmsVfsBundleParameters params = new CmsVfsBundleParameters( 348 name, 349 path, 350 locale, 351 false, 352 CmsVfsResourceBundle.TYPE_XML); 353 CmsVfsResourceBundle bundle = new CmsVfsResourceBundle(params); 354 addBundle(name, locale, bundle); 355 } 356 } 357 358 /** 359 * This actually handles the event.<p> 360 * 361 * @param event the received event 362 */ 363 private void handleEvent(CmsEvent event) { 364 365 switch (event.getType()) { 366 case I_CmsEventListener.EVENT_PUBLISH_PROJECT: 367 //System.out.print(getEventName(event.getType())); 368 String publishIdStr = (String)event.getData().get(I_CmsEventListener.KEY_PUBLISHID); 369 if (publishIdStr != null) { 370 CmsUUID publishId = new CmsUUID(publishIdStr); 371 try { 372 List<CmsPublishedResource> publishedResources = m_cms.readPublishedResources(publishId); 373 if (!publishedResources.isEmpty()) { 374 String[] typesToMatch = new String[] {TYPE_PROPERTIES_BUNDLE, TYPE_XML_BUNDLE}; 375 boolean reload = false; 376 for (CmsPublishedResource res : publishedResources) { 377 for (String typeName : typesToMatch) { 378 if (OpenCms.getResourceManager().matchResourceType(typeName, res.getType())) { 379 reload = true; 380 break; 381 } 382 } 383 } 384 if (reload) { 385 scheduleReload(); 386 } 387 } 388 } catch (CmsException e) { 389 LOG.error(e.getLocalizedMessage(), e); 390 } 391 } 392 break; 393 case I_CmsEventListener.EVENT_CLEAR_CACHES: 394 scheduleReload(); 395 break; 396 default: 397 } 398 } 399 400 /** 401 * Schedules a bundle reload.<p> 402 */ 403 private void scheduleReload() { 404 405 if (!isReloadScheduled() && (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT)) { 406 // only schedule a reload if the system is not going down already 407 m_threadCount++; 408 Thread thread = new Thread("Bundle reload Thread " + m_threadCount) { 409 410 @Override 411 public void run() { 412 413 setReloadScheduled(true); 414 try { 415 Thread.sleep(1000); 416 } catch (Exception e) { 417 // ignore 418 } 419 if (isReloadScheduled()) { 420 reload(false); 421 } 422 setReloadScheduled(false); 423 } 424 }; 425 thread.start(); 426 } 427 } 428}