Package httl.spi.engines

Source Code of httl.spi.engines.DefaultEngine

/*
* Copyright 2011-2013 HTTL Team.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*      http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package httl.spi.engines;

import httl.Engine;
import httl.Node;
import httl.Resource;
import httl.Template;
import httl.spi.Converter;
import httl.spi.Filter;
import httl.spi.Loader;
import httl.spi.Logger;
import httl.spi.Parser;
import httl.spi.Resolver;
import httl.spi.Translator;
import httl.spi.loaders.StringLoader;
import httl.spi.translators.templates.AbstractTemplate;
import httl.util.ConfigUtils;
import httl.util.DelegateMap;
import httl.util.Digest;
import httl.util.StringUtils;
import httl.util.TypeMap;
import httl.util.UrlUtils;
import httl.util.VolatileReference;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

/**
* DefaultEngine. (SPI, Singleton, ThreadSafe)
*
* @see httl.Engine#getEngine()
*
* @author Liang Fei (liangfei0201 AT gmail DOT com)
*/
public class DefaultEngine extends Engine {

  // The storage for string template.
  // @see parseTemplate()
  private final StringLoader stringLoader;

  // httl.properties: loaders=httl.spi.loaders.ClasspathLoader
  private Loader loader;

  // httl.properties: template.parser=httl.spi.parsers.TemplateParser
  private Parser templateParser;

  // httl.properties: translator=httl.spi.translators.DefaultTranslator
  private Translator translator;
 
  // httl.properties: resolver=httl.spi.resolvers.SystemResolver
  private Resolver resolver;

  // httl.properties: loggers=httl.spi.loggers.Log4jLogger
  private Logger logger;

  private Filter templateFilter;

  // httl.properties: template.cache=java.util.concurrent.ConcurrentHashMap
  private Map<Object, Object> cache;

  // httl.properties: template.directory=/META-INF/templates
  private String templateDirectory;

  // httl.properties: template.suffix=.httl
  private String[] templateSuffix;

  // httl.properties: reloadable=true
  private boolean reloadable;
 
  // httl.properties: preload=true
  private boolean preload;
 
  // httl.properties: localized=true
  private boolean localized;

  // httl.properties: use.render.variable.type=false
  private boolean useRenderVariableType;

  // httl.properties: name
  private String name;

  // httl.properties: instantiated content
  private Map<String, Object> properties;
 
  private Converter<Object, Map<String, Object>> mapConverter;

  public DefaultEngine() {
    this.stringLoader = new StringLoader(this);
  }

  /**
   * Get config path.
   */
  public String getName() {
    return name;
  }
 
  /**
   * Get config instantiated value.
   *
   * @see #getEngine()
   * @param key - config key
   * @param cls - config value type
   * @return config value
   */
  @SuppressWarnings("unchecked")
  public <T> T getProperty(String key, Class<T> cls) {
    if (properties != null) {
      if (cls != null && cls != Object.class && cls != String.class
          && ! cls.isInterface() && ! cls.isArray()) {
        // engine.getProperty("loaders", ClasspathLoader.class);
        key = key + "=" + cls.getName();
      }
      Object value = properties.get(key);
      if (value != null) {
        if (cls == String.class && value.getClass() != String.class) {
          return (T) value.getClass().getName();
        }
        return (T) value;
      }
    }
    return null;
  }

  /**
   * Create context map.
   *
   * @return context map
   */
  public Map<String, Object> createContext(final Map<String, Object> parent, Map<String, Object> current) {
    return new DelegateMap<String, Object>(parent, current) {
      private static final long serialVersionUID = 1L;
      @Override
      public Object get(Object key) {
        Object value = super.get(key);
        if (value == null && parent == null
            && resolver != null) {
          return resolver.get((String) key);
        }
        return value;
      }
    };
  }
 
  /**
   * Get template.
   *
   * @see #getEngine()
   * @param name - template name
   * @param locale - template locale
   * @param encoding - template encoding
   * @return template instance
   * @throws IOException - If an I/O error occurs
   * @throws ParseException - If the template cannot be parsed
   */
  @SuppressWarnings("unchecked")
  public Template getTemplate(String name, Locale locale, String encoding, Object args) throws IOException, ParseException {
    name = UrlUtils.cleanName(name);
    locale = cleanLocale(locale);
    Map<Object, Object> cache = this.cache; // safe copy reference
    if (cache == null) {
      return parseTemplate(null, name, locale, encoding, args);
    }
    Resource resource = null;
    long lastModified;
    if (reloadable) {
      resource = loadResource(name, locale, encoding);
      lastModified = resource.getLastModified();
    } else {
      lastModified = Long.MIN_VALUE;
    }
    String key = name;
    if (locale != null || encoding != null) {
      StringBuilder buf = new StringBuilder(name.length() + 20);
      buf.append(name);
      if (locale != null) {
        buf.append("_");
        buf.append(locale);
      }
      if (encoding != null) {
        buf.append("_");
        buf.append(encoding);
      }
      key = buf.toString();
    }
    VolatileReference<Template> reference = (VolatileReference<Template>) cache.get(key);
    if (reference == null) {
      if (cache instanceof ConcurrentMap) {
        reference = new VolatileReference<Template>(); // quickly
        VolatileReference<Template> old = (VolatileReference<Template>) ((ConcurrentMap<Object, Object>) cache).putIfAbsent(key, reference);
        if (old != null) { // duplicate
          reference = old;
        }
      } else {
        synchronized (cache) { // cache lock
          reference = (VolatileReference<Template>) cache.get(key);
          if (reference == null) { // double check
            reference = new VolatileReference<Template>(); // quickly
            cache.put(key, reference);
          }
        }
      }
    }
    assert(reference != null);
    Template template = (Template) reference.get();
    if (template == null || template.getLastModified() < lastModified) {
      synchronized (reference) { // reference lock
        template = (Template) reference.get();
        if (template == null || template.getLastModified() < lastModified) { // double check
          template = parseTemplate(resource, name, locale, encoding, args); // slowly
          reference.set(template);
        }
      }
    }
    assert(template != null);
    return template;
  }

  // Parse the template. (No cache)
  private Template parseTemplate(Resource resource, String name, Locale locale, String encoding, Object args) throws IOException, ParseException {
    if (resource == null) {
      resource = loadResource(name, locale, encoding);
    }
    long start = logger != null && logger.isDebugEnabled() ? System.currentTimeMillis() : 0;
    String source = resource.getSource();
    try {
      if (templateFilter != null) {
        source = templateFilter.filter(resource.getName(), source);
      }
      Node root = templateParser.parse(source, 0);
      Map<String, Class<?>> parameterTypes = useRenderVariableType && args != null ? new DelegateMap<String, Class<?>>(new TypeMap(convertMap(args))) : null;
      Template template = translator.translate(resource, root, parameterTypes);
      if (logger != null && logger.isDebugEnabled()) {
        logger.debug("Parsed the template " + name + ", eslapsed: " + (System.currentTimeMillis() - start) + "ms.");
      }
      return template;
    } catch (ParseException e) {
      throw AbstractTemplate.toLocatedParseException(e, resource);
    }
  }

  @SuppressWarnings("unchecked")
  private Map<String, Object> convertMap(Object parameters) throws IOException, ParseException {
    if (mapConverter != null && parameters != null && ! (parameters instanceof Map)) {
      parameters = mapConverter.convert(parameters, null);
    }
    if (parameters == null || parameters instanceof Map) {
      return (Map<String, Object>) parameters;
    } else {
      throw new IllegalArgumentException("No such " + Converter.class.getName() + " to convert the " + parameters.getClass().getName() + " to Map. Please check engine.getTemplate() args or implement a converter and add config in httl.properties: map.converter+=com.your." + parameters.getClass().getSimpleName() + Converter.class.getName() + ".");
    }
  }

  /**
   * Parse string template.
   *
   * @see #getEngine()
   * @param source - template source
   * @return template instance
   * @throws IOException - If an I/O error occurs
   * @throws ParseException - If the template cannot be parsed
   */
  public Template parseTemplate(String source, Object parameterTypes) throws ParseException {
    String name = "/$" + Digest.getMD5(source);
    if (! hasResource(name)) {
      stringLoader.add(name, source);
    }
    try {
      return getTemplate(name, parameterTypes);
    } catch (IOException e) {
      throw new IllegalStateException(e.getMessage(), e);
    }
  }

  /**
   * Get resource.
   *
   * @see #getEngine()
   * @param name - resource name
   * @param locale - resource locale
   * @param encoding - resource encoding
   * @return resource instance
   * @throws IOException - If an I/O error occurs
   */
  public Resource getResource(String name, Locale locale, String encoding) throws IOException {
    name = UrlUtils.cleanName(name);
    locale = cleanLocale(locale);
    return loadResource(name, locale, encoding);
  }

  // Load the resource. (No url clean)
  private Resource loadResource(String name, Locale locale, String encoding) throws IOException {
    Resource resource;
    if (stringLoader.exists(name, locale)) {
      resource = stringLoader.load(name, locale, encoding);
    } else {
      resource = loader.load(name, locale, encoding);
    }
    if (resource == null) {
      throw new FileNotFoundException("Not found resource " + name);
    }
    return resource;
  }

  /**
   * Tests whether the resource denoted by this abstract pathname exists.
   *
   * @see #getEngine()
   * @param name - template name
   * @param locale - resource locale
   * @return exists
   */
  public boolean hasResource(String name, Locale locale) {
    name = UrlUtils.cleanName(name);
    locale = cleanLocale(locale);
    return stringLoader.exists(name, locale) || loader.exists(name, locale);
  }
 
  private Locale cleanLocale(Locale locale) {
    if (localized) {
      return locale;
    }
    return null;
  }

  /**
   * Init the engine.
   */
  public void init() {
    if (logger != null && StringUtils.isNotEmpty(name)) {
      if (logger.isWarnEnabled() && ! ConfigUtils.isFilePath(name)) {
        try {
          List<String> realPaths = new ArrayList<String>();
          Enumeration<URL> e = Thread.currentThread().getContextClassLoader().getResources(name);
          while (e.hasMoreElements()) {
            URL url = (URL) e.nextElement();
            realPaths.add(url.getFile());
          }
          if (realPaths.size() > 1) {
            logger.warn("Multi httl config in classpath, conflict configs: " + realPaths + ". Please keep only one config.");
          }
        } catch (IOException e) {
          logger.error(e.getMessage(), e);
        }
      }
      if (logger.isInfoEnabled()) {
        String realPath = ConfigUtils.getRealPath(name);
        if (StringUtils.isNotEmpty(realPath)) {
          logger.info("Load httl config from " + realPath + " in " + (name.startsWith("/") ? "filesystem" : "classpath") + ".");
        }
      }
    }
  }

  /**
   * On all inited.
   */
  public void inited() {
    if (preload) {
      try {
        int count = 0;
        if (templateSuffix == null) {
          templateSuffix = new String[] { ".httl" };
        }
        for (String suffix : templateSuffix) {
          List<String> list = loader.list(suffix);
          if (list == null) {
            continue;
          }
          count += list.size();
          for (String name : list) {
            try {
              if (logger != null && logger.isDebugEnabled()) {
                logger.debug("Preload the template: " + name);
              }
              getTemplate(name);
            } catch (Exception e) {
              if (logger != null && logger.isErrorEnabled()) {
                logger.error(e.getMessage(), e);
              }
            }
          }
        }
        if (logger != null && logger.isInfoEnabled()) {
          logger.info("Preload " + count + " templates from directory " + (templateDirectory == null ? "/" : templateDirectory) + " with suffix " + Arrays.toString(templateSuffix));
        }
      } catch (Exception e) {
        if (logger != null && logger.isErrorEnabled()) {
          logger.error(e.getMessage(), e);
        }
      }
    }
  }

  /**
   * httl.properties: name
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * httl.properties: instantiated content
   */
  public void setProperties(Map<String, Object> properties) {
    this.properties = properties;
  }

  /**
   * httl.properties: template.directory=/META-INF/templates
   */
  public void setTemplateDirectory(String templateDirectory) {
    this.templateDirectory = templateDirectory;
  }

  /**
   * httl.properties: template.suffix=.httl
   */
  public void setTemplateSuffix(String[] suffix) {
    this.templateSuffix = suffix;
  }

  /**
   * httl.properties: reloadable=true
   */
  public void setReloadable(boolean reloadable) {
    this.reloadable = reloadable;
  }

  /**
   * httl.properties: preload=true
   */
  public void setPreload(boolean preload) {
    this.preload = preload;
  }

  /**
   * httl.properties: localized=true
   */
  public void setLocalized(boolean localized) {
    this.localized = localized;
  }

  /**
   * httl.properties: use.render.variable.type=false
   */
  public void setUseRenderVariableType(boolean useRenderVariableType) {
    this.useRenderVariableType = useRenderVariableType;
  }

  /**
   * httl.properties: loggers=httl.spi.loggers.Log4jLogger
   */
  public void setLogger(Logger logger) {
    this.logger = logger;
  }

  /**
   * httl.properties: cache=java.util.concurrent.ConcurrentHashMap
   */
  public void setCache(Map<Object, Object> cache) {
    this.cache = cache;
  }
 
  /**
   * httl.properties: loaders=httl.spi.loaders.ClasspathLoader
   */
  public void setLoader(Loader loader) {
    this.loader = loader;
  }

  /**
   * httl.properties: template.parser=httl.spi.parsers.TemplateParser
   */
  public void setTemplateParser(Parser templateParser) {
    this.templateParser = templateParser;
  }

  /**
   * httl.properties: translator=httl.spi.translators.CompileTranslator
   */
  public void setTranslator(Translator translator) {
    this.translator = translator;
  }

  /**
   * httl.properties: resolver=httl.spi.resolvers.SystemResolver
   */
  public void setResolver(Resolver resolver) {
    this.resolver = resolver;
  }

  /**
   * httl.properties: template.filters=httl.spi.filters.CleanBlankLineFilter
   */
  public void setTemplateFilter(Filter templateFilter) {
    this.templateFilter = templateFilter;
  }

  /**
   * httl.properties: map.converter=httl.spi.converters.BeanMapConverter
   */
  public void setMapConverter(Converter<Object, Map<String, Object>> mapConverter) {
    this.mapConverter = mapConverter;
  }

}
TOP

Related Classes of httl.spi.engines.DefaultEngine

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.