Package com.github.dandelion.datatables.thymeleaf.processor.config

Source Code of com.github.dandelion.datatables.thymeleaf.processor.config.DivConfTypeAttrProcessor

/*
* [The "BSD licence"]
* Copyright (c) 2013-2014 Dandelion
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Dandelion nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.github.dandelion.datatables.thymeleaf.processor.config;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.thymeleaf.Arguments;
import org.thymeleaf.context.IWebContext;
import org.thymeleaf.dom.Element;
import org.thymeleaf.processor.IAttributeNameProcessorMatcher;
import org.thymeleaf.processor.ProcessorResult;
import org.thymeleaf.processor.attr.AbstractAttrProcessor;

import com.github.dandelion.core.utils.EnumUtils;
import com.github.dandelion.core.utils.StringUtils;
import com.github.dandelion.core.utils.UrlUtils;
import com.github.dandelion.core.web.WebConstants;
import com.github.dandelion.datatables.core.asset.ExtraJs;
import com.github.dandelion.datatables.core.asset.InsertMode;
import com.github.dandelion.datatables.core.callback.Callback;
import com.github.dandelion.datatables.core.callback.CallbackType;
import com.github.dandelion.datatables.core.configuration.ConfigToken;
import com.github.dandelion.datatables.core.configuration.TableConfig;
import com.github.dandelion.datatables.core.exception.ConfigurationProcessingException;
import com.github.dandelion.datatables.core.exception.DandelionDatatablesException;
import com.github.dandelion.datatables.core.export.ExportConf;
import com.github.dandelion.datatables.core.export.ExportConf.Orientation;
import com.github.dandelion.datatables.core.export.ExportUtils;
import com.github.dandelion.datatables.core.export.HttpMethod;
import com.github.dandelion.datatables.core.html.ExtraHtml;
import com.github.dandelion.datatables.core.util.ProcessorUtils;
import com.github.dandelion.datatables.thymeleaf.dialect.DataTablesDialect;
import com.github.dandelion.datatables.thymeleaf.processor.el.DivExtraHtmlFinalizerElProcessor;
import com.github.dandelion.datatables.thymeleaf.util.AttributeUtils;
import com.github.dandelion.datatables.thymeleaf.util.RequestUtils;

/**
* <p>
* Attribute processor applied to the HTML {@code div} tag with the
* {@code dt:confType} attribute.
*
* <p>
* The {@code div} targeted by this processor fill the configuration map
* initialized in the {@link DivConfAttrProcessor} with one (or more) of the
* following configurations:
* <ul>
* <li>A {@link Callback}, using {@code dt:confType="callback"}</li>
* <li>An export configuration ({@link ExportConf}), using
* {@code dt:confType="export"}</li>
* <li>An {@link ExtraJs}, using {@code dt:confType="extrajs"}</li>
* <li>A configuration property, using {@code dt:confType="property"}</li>
* <li>An extra HTML snippet, using {@code dt:confType="extrahtml"}</li>
* </ul>
*
* @author Thibault Duchateau
* @since 0.10.0
*/
public class DivConfTypeAttrProcessor extends AbstractAttrProcessor {

  public DivConfTypeAttrProcessor(IAttributeNameProcessorMatcher matcher) {
    super(matcher);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getPrecedence() {
    return DataTablesDialect.DT_HIGHEST_PRECEDENCE;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  @SuppressWarnings("unchecked")
  protected ProcessorResult processAttribute(Arguments arguments, Element element, String attributeName) {

    checkMarkupUsage(element);

    HttpServletRequest request = ((IWebContext) arguments.getContext()).getHttpServletRequest();
    HttpServletResponse response = ((IWebContext) arguments.getContext()).getHttpServletResponse();
   
    // A Map<ConfType, Object> is associated with each table id
    Map<String, Map<ConfType, Object>> configs = (Map<String, Map<ConfType, Object>>) RequestUtils.getFromRequest(
        DataTablesDialect.INTERNAL_BEAN_CONFIGS, request);

    String tableId = ((Element) element.getParent()).getAttributeValue(DataTablesDialect.DIALECT_PREFIX + ":conf");

    String confTypeStr = AttributeUtils.parseStringAttribute(arguments, element, attributeName);
    ConfType confType = null;
    try {
      confType = ConfType.valueOf(confTypeStr.trim().toUpperCase());
    } catch (IllegalArgumentException e) {
      StringBuilder sb = new StringBuilder();
      sb.append("'");
      sb.append(confTypeStr.trim());
      sb.append("' is not a valid configuration type. Possible values are: ");
      sb.append(EnumUtils.printPossibleValuesOf(ConfType.class));
      throw new ConfigurationProcessingException(sb.toString());
    }

    switch (confType) {
    case CALLBACK:
      processCallbackAttributes(element, configs, request, tableId);
      break;
    case EXPORT:
      processExportAttributes(arguments, element, configs, request, response, tableId);
      break;
    case EXTRAJS:
      processExtraJsAttributes(arguments, element, configs, tableId);
      break;
    case PROPERTY:
      processPropertyAttributes(element, configs, tableId);
      break;
    case EXTRAHTML:
      processExtraHtmlAttributes(element, configs, tableId);
      break;
    }
   
    return ProcessorResult.ok();
  }

  private void checkMarkupUsage(Element element) {
    Element parent = (Element) element.getParent();

    if (parent == null || !"div".equals(parent.getNormalizedName())
        || !parent.hasAttribute(DataTablesDialect.DIALECT_PREFIX + ":conf")
        || StringUtils.isBlank(parent.getAttributeValue(DataTablesDialect.DIALECT_PREFIX + ":conf"))) {
      throw new DandelionDatatablesException(
          "The element 'div dt:confType=\"...\"' must be inside an element 'div dt:conf=\"tableId\"'.");
    }
  }

  /**
   * <p>
   * Processes ExtraHtml attributes in order to build an instance of
   * {@link ExtraHtml}.
   *
   * <p>
   * As Thymeleaf runs a processor before the evaluation of its children, the
   * content of the HTML snippet is not set here but in a dedicated processor:
   * {@link DivExtraHtmlFinalizerElProcessor}. As such, a fake {@code div} tag
   * is added inside the main configuration {@code div} with the same
   * {@code uid} attribute.
   *
   * @param element
   *            The {@code div} element which holds the attribute.
   * @see DivExtraHtmlFinalizerElProcessor
   */
  @SuppressWarnings("unchecked")
  private void processExtraHtmlAttributes(Element element, Map<String, Map<ConfType, Object>> configs, String tableId) {
   
    if (hasAttribute(element, "uid")) {

      ExtraHtml extraHtml = new ExtraHtml();
      extraHtml.setUid(getStringValue(element, "uid"));

      if (hasAttribute(element, "container")) {
        extraHtml.setContainer(getStringValue(element, "container"));
      } else {
        extraHtml.setContainer("div");
      }

      if (hasAttribute(element, "cssStyle")) {
        extraHtml.setCssStyle(getStringValue(element, "cssStyle"));
      }
      if (hasAttribute(element, "cssClass")) {
        extraHtml.setCssClass(getStringValue(element, "cssClass"));
      }

      if (configs.get(tableId).containsKey(ConfType.EXTRAHTML)) {
        List<ExtraHtml> extraHtmls = (List<ExtraHtml>) configs.get(tableId).get(ConfType.EXTRAHTML);

        extraHtmls.add(extraHtml);
      }
      else {
        List<ExtraHtml> extraHtmls = new ArrayList<ExtraHtml>();
        extraHtmls.add(extraHtml);

        configs.get(tableId).put(ConfType.EXTRAHTML, extraHtmls);
      }

      // We add a fake div here, in order to be able to get the content
      // processed
      Element div = new Element("div");
      div.setAttribute(DataTablesDialect.DIALECT_PREFIX + ":tmp", "internalUseExtraHtml");
      div.setAttribute(DataTablesDialect.DIALECT_PREFIX + ":uid", extraHtml.getUid());
      element.getParent().addChild(div);
     
    }
    else {
      throw new ConfigurationProcessingException(
          "The attribute 'dt:uid' is required when defining an extra HTML snippet.");
    }
  }

  /**
   * Processes export attributes in order to build an instance of
   * {@link ExportConf}.
   *
   * @param element
   *            The {@code div} element which holds the attribute.
   */
  @SuppressWarnings("unchecked")
  private void processExportAttributes(Arguments arguments, Element element, Map<String, Map<ConfType, Object>> configs, HttpServletRequest request, HttpServletResponse response,
      String tableId) {

    ExportConf conf = null;
    String exportFormat = null;

    if (hasAttribute(element, "type")) {
      exportFormat = element.getAttributeValue(DataTablesDialect.DIALECT_PREFIX + ":type").trim().toLowerCase();
      conf = new ExportConf(exportFormat);
    } else {
      throw new ConfigurationProcessingException(
          "The attribute 'dt:type' is required when defining an export configuration.");
    }

    StringBuilder exportUrl = null;
    // Custom mode (export using controller)
    if (element.hasAttribute(DataTablesDialect.DIALECT_PREFIX + ":url")) {
      exportUrl = new StringBuilder(AttributeUtils.parseStringAttribute(arguments, element,
          DataTablesDialect.DIALECT_PREFIX + ":url").trim());
      UrlUtils.addParameter(exportUrl, ExportUtils.DDL_DT_REQUESTPARAM_EXPORT_TYPE, "c");
      conf.setHasCustomUrl(true);
    }
    // Default mode (export using filter)
    else{
      exportUrl = UrlUtils.getCurrentUri(request);
      UrlUtils.addParameter(exportUrl, ExportUtils.DDL_DT_REQUESTPARAM_EXPORT_TYPE, "f");
      conf.setHasCustomUrl(false);
    }
   
    if (hasAttribute(element, "fileName")) {
      String fileName = getStringValue(element, "fileName");
      conf.setFileName(fileName);
      UrlUtils.addParameter(exportUrl, ExportUtils.DDL_DT_REQUESTPARAM_EXPORT_NAME, fileName);
    }

    if (hasAttribute(element, "mimeType")) {
      String mimeType = getStringValue(element, "mimeType");
      conf.setMimeType(mimeType);
      UrlUtils.addParameter(exportUrl, ExportUtils.DDL_DT_REQUESTPARAM_EXPORT_MIME_TYPE, mimeType);
    }

    if (hasAttribute(element, "label")) {
      conf.setLabel(getStringValue(element, "label"));
    }

    if (hasAttribute(element, "cssStyle")) {
      conf.setCssStyle(new StringBuilder(getStringValue(element, "cssStyle")));
    }

    if (hasAttribute(element, "cssClass")) {
      conf.setCssClass(new StringBuilder(getStringValue(element, "cssClass")));
    }

    if (hasAttribute(element, "includeHeader")) {
      String includeHeader = getStringValue(element, "includeHeader");
      conf.setIncludeHeader(Boolean.parseBoolean(includeHeader));
      UrlUtils.addParameter(exportUrl, ExportUtils.DDL_DT_REQUESTPARAM_EXPORT_HEADER, includeHeader);
    }
   
    if (hasAttribute(element, "method")) {
      String methodStr = element.getAttributeValue(DataTablesDialect.DIALECT_PREFIX + ":method");

      HttpMethod methodEnum = null;
      try {
        methodEnum = HttpMethod.valueOf(methodStr.toUpperCase().trim());
      } catch (IllegalArgumentException e) {
        StringBuilder sb = new StringBuilder();
        sb.append("'");
        sb.append(methodStr);
        sb.append("' is not a valid HTTP method. Possible values are: ");
        sb.append(EnumUtils.printPossibleValuesOf(HttpMethod.class));
        throw new ConfigurationProcessingException(sb.toString());
      }

      conf.setMethod(methodEnum);
    }

    if (hasAttribute(element, "autoSize")) {
      String autosize = getStringValue(element, "autoSize");
      conf.setAutoSize(Boolean.parseBoolean(autosize));
      UrlUtils.addParameter(exportUrl, ExportUtils.DDL_DT_REQUESTPARAM_EXPORT_AUTOSIZE, autosize);
    }

    if (hasAttribute(element, "exportClass")) {
      conf.setExportClass(getStringValue(element, "exportClass"));
    }

    if (hasAttribute(element, "fileExtension")) {
      String fileExtension = getStringValue(element, "fileExtension");
      conf.setFileExtension(fileExtension);
      UrlUtils.addParameter(exportUrl, ExportUtils.DDL_DT_REQUESTPARAM_EXPORT_EXTENSION, fileExtension);
    }

    if (hasAttribute(element, "orientation")) {
      String orientationStr = element.getAttributeValue(DataTablesDialect.DIALECT_PREFIX + ":orientation");

      Orientation orientationEnum = null;
      try {
        orientationEnum = Orientation.valueOf(orientationStr.toUpperCase().trim());
      } catch (IllegalArgumentException e) {
        StringBuilder sb = new StringBuilder();
        sb.append("'");
        sb.append(orientationStr);
        sb.append("' is not a valid orientation. Possible values are: ");
        sb.append(EnumUtils.printPossibleValuesOf(Orientation.class));
        throw new ConfigurationProcessingException(sb.toString());
      }

      conf.setOrientation(orientationEnum);
      UrlUtils.addParameter(exportUrl, ExportUtils.DDL_DT_REQUESTPARAM_EXPORT_ORIENTATION, orientationStr);
    }

    // Finalizes export URL
    UrlUtils.addParameter(exportUrl, ExportUtils.DDL_DT_REQUESTPARAM_EXPORT_ID, tableId);
    UrlUtils.addParameter(exportUrl, ExportUtils.DDL_DT_REQUESTPARAM_EXPORT_FORMAT, exportFormat);
    UrlUtils.addParameter(exportUrl, ExportUtils.DDL_DT_REQUESTPARAM_EXPORT_IN_PROGRESS, "y");
    UrlUtils.addParameter(exportUrl, WebConstants.DANDELION_ASSET_FILTER_STATE, false);
    conf.setUrl(UrlUtils.getProcessedUrl(exportUrl, request, response));
   
    if (conf != null) {

      if (configs.get(tableId).containsKey(ConfType.EXPORT)) {
        ((Map<String, ExportConf>) configs.get(tableId).get(ConfType.EXPORT)).put(exportFormat, conf);
      } else {
        Map<String, ExportConf> exportConfiguration = new HashMap<String, ExportConf>();
        exportConfiguration.put(exportFormat, conf);
        configs.get(tableId).put(ConfType.EXPORT, exportConfiguration);
      }
    }
  }

  /**
   * Processes attributes in order to build an instance of {@link Callback}.
   *
   * @param element
   *            The {@code div} element which holds the attribute.
   */
  @SuppressWarnings("unchecked")
  private void processCallbackAttributes(Element element, Map<String, Map<ConfType, Object>> configs, HttpServletRequest request, String tableId) {

    if (hasAttribute(element, "type")) {

      String typeStr = getStringValue(element, "type");

      if (hasAttribute(element, "function")) {

        String functionStr = getStringValue(element, "function");
        functionStr = ProcessorUtils.getValueAfterProcessingBundles(functionStr, request);
        CallbackType callbackType = null;
        try {
          callbackType = CallbackType.valueOf(typeStr.toUpperCase().trim());
        } catch (IllegalArgumentException e) {
          StringBuilder sb = new StringBuilder();
          sb.append("'");
          sb.append(typeStr);
          sb.append("' is not a valid callback type. Possible values are: ");
          sb.append(EnumUtils.printPossibleValuesOf(CallbackType.class));
          throw new ConfigurationProcessingException(sb.toString());
        }

        if (configs.get(tableId).containsKey(ConfType.CALLBACK)) {
          List<Callback> callbacks = (List<Callback>) configs.get(tableId).get(ConfType.CALLBACK);

          if (Callback.hasCallback(callbackType, callbacks)) {
            Callback.findByType(callbackType, callbacks).appendCode(
                (callbackType.hasReturn() ? "return " : "") + functionStr + "("
                    + StringUtils.join(callbackType.getArgs(), ",") + ");");
          } else {
            callbacks.add(new Callback(callbackType, (callbackType.hasReturn() ? "return " : "")
                + functionStr + "(" + StringUtils.join(callbackType.getArgs(), ",") + ");"));
          }
        } else {
          List<Callback> callbacks = new ArrayList<Callback>();
          callbacks.add(new Callback(callbackType, (callbackType.hasReturn() ? "return " : "") + functionStr
              + "(" + StringUtils.join(callbackType.getArgs(), ",") + ");"));

          configs.get(tableId).put(ConfType.CALLBACK, callbacks);
        }
      } else {
        throw new ConfigurationProcessingException("The attribute '" + DataTablesDialect.DIALECT_PREFIX
            + ":function' is required when defining a callback.");
      }
    } else {
      throw new ConfigurationProcessingException("The attribute '" + DataTablesDialect.DIALECT_PREFIX
          + ":type' is required when defining a callback.");
    }
  }

  /**
   * Processes attributes in order to build an instance of {@link ExtraJs}.
   *
   * @param element
   *            The {@code div} element which holds the attribute.
   */
  @SuppressWarnings("unchecked")
  private void processExtraJsAttributes(Arguments arguments, Element element, Map<String, Map<ConfType, Object>> configs, String tableId) {

    if (hasAttribute(element, "bundles")) {

      String bundles = AttributeUtils.parseStringAttribute(arguments, element, DataTablesDialect.DIALECT_PREFIX
          + ":bundles");
      InsertMode insertMode = null;

      if (hasAttribute(element, "insert")) {
        String insert = getStringValue(element, "insert");
        try {
          insertMode = InsertMode.valueOf(insert.toUpperCase().trim());
        } catch (IllegalArgumentException e) {
          StringBuilder sb = new StringBuilder();
          sb.append("'");
          sb.append(insert);
          sb.append("' is not a valid insert mode. Possible values are: ");
          sb.append(EnumUtils.printPossibleValuesOf(InsertMode.class));
          throw new ConfigurationProcessingException(sb.toString());
        }
      } else {
        insertMode = InsertMode.BEFOREALL;
      }

      Set<String> bundleSet = new HashSet<String>(Arrays.asList(bundles.split(",")));
      if (configs.get(tableId).containsKey(ConfType.EXTRAJS)) {
        ((Set<ExtraJs>) configs.get(tableId).get(ConfType.EXTRAJS)).add(new ExtraJs(bundleSet,
            insertMode));
      }
      else {
        Set<ExtraJs> extraFiles = new HashSet<ExtraJs>();
        extraFiles.add(new ExtraJs(bundleSet, insertMode));
        configs.get(tableId).put(ConfType.EXTRAJS, extraFiles);

      }
    } else {
      throw new ConfigurationProcessingException(
          "The attribute 'dt:bundles' is required when defining an extra JavaScript.");
    }
  }

  /**
   * Processes attributes in order to overload locally the properties
   * configured globally.build an instance of {@link Callback}.
   *
   * @param element
   *            The {@code div} element which holds the attribute.
   */
  @SuppressWarnings("unchecked")
  private void processPropertyAttributes(Element element, Map<String, Map<ConfType, Object>> configs, String tableId) {

    if (hasAttribute(element, "name")) {

      String name = getStringValue(element, "name");
      ConfigToken<?> configToken = TableConfig.findByPropertyName(name);
      String value = getStringValue(element, "value");

      if (configToken == null) {
        throw new ConfigurationProcessingException("'" + name
            + "' is not a valid property. Please read the documentation.");
      } else {

        if (configs.get(tableId).containsKey(ConfType.PROPERTY)) {
          ((Map<ConfigToken<?>, Object>) configs.get(tableId).get(ConfType.PROPERTY)).put(configToken,
              value);
        } else {
          Map<ConfigToken<?>, Object> stagingConf = new HashMap<ConfigToken<?>, Object>();
          stagingConf.put(configToken, value);
          configs.get(tableId).put(ConfType.PROPERTY, stagingConf);
        }
      }
    } else {
      throw new ConfigurationProcessingException(
          "The attribute 'dt:name' is required when overloading a configuration property.");
    }
  }

  public boolean hasAttribute(Element element, String attribute) {
    return element.hasAttribute(DataTablesDialect.DIALECT_PREFIX + ":" + attribute)
        && StringUtils
            .isNotBlank(element.getAttributeValue(DataTablesDialect.DIALECT_PREFIX + ":" + attribute));
  }

  public String getStringValue(Element element, String attribute) {
    return String.valueOf(element.getAttributeValue(DataTablesDialect.DIALECT_PREFIX + ":" + attribute));
  }
}
TOP

Related Classes of com.github.dandelion.datatables.thymeleaf.processor.config.DivConfTypeAttrProcessor

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.