ResourceBundleConfigDeployer.java

/*
 * Copyright (C) 2003-2013 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.commons.notification.template;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletContext;

import org.exoplatform.commons.utils.CommonsUtils;
import org.exoplatform.commons.utils.IOUtil;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.container.xml.ValuesParam;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.resources.LocaleConfig;
import org.exoplatform.services.resources.LocaleConfigService;
import org.exoplatform.services.resources.ResourceBundleData;
import org.exoplatform.services.resources.ResourceBundleService;
import org.exoplatform.services.resources.impl.BaseResourceBundlePlugin;
import org.exoplatform.services.resources.impl.SimpleResourceBundleService;
import org.gatein.wci.WebApp;
import org.gatein.wci.WebAppEvent;
import org.gatein.wci.WebAppLifeCycleEvent;
import org.gatein.wci.WebAppListener;

public class ResourceBundleConfigDeployer implements WebAppListener {
  private static final Log LOG = ExoLogger.getLogger(ResourceBundleConfigDeployer.class);
  private final ResourceBundleService bundleService;
  private static final String CONF_LOCATION = "/WEB-INF/classes/";
  private final Map<String, WebApp> contexts;

  public ResourceBundleConfigDeployer() {
    bundleService = CommonsUtils.getService(ResourceBundleService.class);
    this.contexts = new HashMap<String, WebApp>();
  }

  @Override
  public void onEvent(WebAppEvent event) {
    if (event instanceof WebAppLifeCycleEvent) {
      WebAppLifeCycleEvent lifeCycleEvent = (WebAppLifeCycleEvent) event;
      switch (lifeCycleEvent.getType()) {
      case WebAppLifeCycleEvent.ADDED:
        add(event.getWebApp());
        break;
      case WebAppLifeCycleEvent.REMOVED:
        remove(event.getWebApp());
        break;
      }
    }
  }

  private void remove(WebApp webApp) {
    contexts.remove(webApp.getContextPath());
  }

  private void add(WebApp webApp) {
    contexts.put(webApp.getContextPath(), webApp);
  }

  /**
   * Loading the notification resource bundle.
   * 
   * Notice: Don't accept the resource bundle file the same name for both jar and war file.
   * If the case the same happens, we just support to load from webapp context.
   * 
   * When gets the Resource Bundle, 1 priority is resource bundle in jar file. it returns NULL.
   * 
   * 
   * @param list the provided the path of resource bundle file
   */
  public void initBundlePath(Collection<String> list) {
    try {
      //step 1: build resource bundle list base on the supported language from platform.
      LocaleConfigService localeService_ = CommonsUtils.getService(LocaleConfigService.class);
      Collection<LocaleConfig> locales = localeService_.getLocalConfigs();
      Set<String> includedWebApp = new HashSet<String>();
      
      Set<ResourceBundleFile> filePaths = new HashSet<ResourceBundleFile>();
      for (String path : list) {
        if(path == null || path.isEmpty()) {
          continue;
        }
        String baseName = getRealPathFile(path);
        for (LocaleConfig config : locales) {
          filePaths.addAll(buildFilePath(path, baseName, config.getLocale()));
        }
      }
      Map<ResourceBundleFile, StringBuilder> resourceMap = new HashMap<ResourceBundleFile, StringBuilder>();
      //step 2: Loading resource bundle file from webapps
      for (WebApp app : contexts.values()) {
        for(ResourceBundleFile bundleFile : filePaths) {
          String content = this.getResourceBundleContent(bundleFile.filePath, app.getServletContext());
          if (content != null) {
            StringBuilder sb = resourceMap.get(bundleFile);
            if (sb == null) {
              sb = new StringBuilder();
              resourceMap.put(bundleFile, sb);
            }
            sb.append(content);
            LOG.debug("Loading file path = " + bundleFile.filePath);
          }
        }
      }
      
      //step 3 push the map to the resource bundle service
      for (Map.Entry<ResourceBundleFile, StringBuilder> entry : resourceMap.entrySet()) {
        ResourceBundleFile key = entry.getKey();
        // save the content
        ResourceBundleData data = new ResourceBundleData(entry.getValue().toString());
        data.setName(key.resourceName);
        data.setLanguage(key.locale.getLanguage());
        data.setCountry(key.locale.getCountry());
        data.setVariant(key.locale.getVariant());
        this.bundleService.saveResourceBundle(data);
        includedWebApp.add(key.resourceName);
        LOG.debug("Loading file path = " + key.filePath + " ResourceBundleData's ID = " + data.getId());
      }
      //step 4 Loading the resource bundle from jar file what keep in integration notification. 
      for (String path : list) {
        if (!includedWebApp.contains(path)) {
        //add the bundle from jar file
          addResourceBundleByPlugin(path);
        }
      }
      
    } catch (Exception e) {
      LOG.debug("Error when initializing resource bundle of Notification.", e);
    }
  }
  
  private String getResourceBundleContent(String filePath, ServletContext servletContext) throws Exception {
    String fileName = null;
    try {
      URL url = servletContext.getResource(filePath);
      if (url != null) {
        InputStream is = url.openStream();
        try {
          byte[] buf = IOUtil.getStreamContentAsBytes(is);
          return new String(buf, "UTF-8");
        } finally {
          try {
            is.close();
          } catch (IOException e) {
            LOG.error("Failed close InputStream");
          }
        }
      }
    } catch (Exception ex) {
      throw new Exception("Error while reading the file: " + fileName, ex);
    }
    return null;
  }

  private List<ResourceBundleFile> buildFilePath(String resourceName, String baseName, Locale locale) {
    List<ResourceBundleFile> candidateFiles = new ArrayList<ResourceBundleFile>();

    String language = locale.getLanguage();
    String country = locale.getCountry().toUpperCase();
    String variant = locale.getVariant();

    if (variant != null && variant.length() > 0) {
      candidateFiles.add(new ResourceBundleFile(resourceName, baseName + "_" + language + "_" + country + "_" + variant + ".properties", locale));
    }

    if (country != null && country.length() > 0) {
      candidateFiles.add(new ResourceBundleFile(resourceName, baseName + "_" + language + "_" + country + ".properties", locale));
    }

    if (language != null && language.length() > 0) {
      candidateFiles.add(new ResourceBundleFile(resourceName, baseName + "_" + language + ".properties", locale));
    }
    return candidateFiles;
  }

  private void addResourceBundleByPlugin(String path) {
    InitParams params = new InitParams();

    ValuesParam classPathParam = new ValuesParam();
    classPathParam.setName("classpath.resources");
    classPathParam.setValues(new ArrayList<String>(Arrays.asList(path)));
    params.addParameter(classPathParam);

    ValuesParam portalParam = new ValuesParam();
    portalParam.setName("portal.resource.names");
    portalParam.setValues(new ArrayList<String>(Arrays.asList(path)));
    params.addParameter(portalParam);

    BaseResourceBundlePlugin bundlePlugin = new BaseResourceBundlePlugin(params);
    ((SimpleResourceBundleService) bundleService).addResourceBundle(bundlePlugin);
  }
  
  private String getRealPathFile(String path) {
    return new StringBuffer(CONF_LOCATION).append(path.replace(".", "/")).toString();
  }
  
  /**
   * The class what wrapped the filePath and locale
   * @author thanhvc
   *
   */
  private class ResourceBundleFile {
    final String resourceName;
    final String filePath;
    final Locale locale;
    public ResourceBundleFile(String resourceName, String filePath, Locale locale) {
      this.resourceName = resourceName;
      this.filePath = filePath;
      this.locale = locale;
    }
    
    @Override
    public boolean equals(final Object o) {
      if (this == o) {
        return true;
      }
      if (!(o instanceof ResourceBundleFile)) {
        return false;
      }
      
      ResourceBundleFile that = (ResourceBundleFile) o;

      if (filePath != null ? !filePath.equals(that.filePath) : that.filePath != null) {
        return false;
      }

      return true;
    }

    @Override
    public int hashCode() {
      int result = super.hashCode();
      result = 31 * result + (filePath != null ? filePath.hashCode() : 0);
      return result;
    }
    
    @Override
    public String toString() {
      return new StringBuilder().append("filePath: ")
                                .append(this.filePath)
                                .append(", resourceName: ")
                                .append(this.resourceName)
                                .toString();
    }
  }

}