All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.protobufel.crud.el.ProtoMessageQueryProcessor Maven / Gradle / Ivy

There is a newer version: 0.7.1
Show newest version
//
// Copyright © 2014, David Tesler (https://github.com/protobufel)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * 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.
// * Neither the name of the  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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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  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.protobufel.crud.el;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.el.ELContext;
import javax.el.ELManager;
import javax.el.ELProcessor;
import javax.el.ValueExpression;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.protobufel.DynamicMessage;
import com.github.protobufel.IDynamicMessageProvider;
import com.github.protobufel.ProtoInterfaces.IBuilder2;
import com.github.protobufel.crud.el.QueryProcessors.IQueryProcessor;
import com.github.protobufel.crud.el.QueryProcessors.QueryResultListener;
import com.github.protobufel.crud.el.QueryProcessors.ValidationListener;
import com.github.protobufel.el.ProtoELProcessorEx;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;

/**
 * Processes queries, lists of objects, using the EL 3.0 language, including validation and error
 * reporting.
 * 
 * @author [email protected] David Tesler
 */
public final class ProtoMessageQueryProcessor implements IQueryProcessor {
  private static final Logger log = LoggerFactory.getLogger(ProtoMessageQueryProcessor.class);
  private static final DefaultValidationListener DEFAULT_VALIDATION_LISTENER =
      new DefaultValidationListener();
  private static final ProtoMessageQueryProcessor EMPTY_INSTANCE = new ProtoMessageQueryProcessor();
  private static final ProtoMessageQueryProcessor IDENTITY_INSTANCE =
      new ProtoMessageQueryProcessor("record", null, null, null, null, null);
  private final String expression;
  private final String emptyExpression;
  private final Descriptor type;
  private final QueryResultListener resultListener;
  private final ValidationListener validationListener;
  private final IDynamicMessageProvider messageProvider;

  private ProtoMessageQueryProcessor() {
    this(null, null, null, null, null, null);
  }

  protected ProtoMessageQueryProcessor(final String expression, final String emptyExpression,
      final Descriptor type, final QueryResultListener resultListener,
      final ValidationListener validationListener, final IDynamicMessageProvider messageProvider) {
    this.type = type;
    this.resultListener = resultListener;
    this.expression = expression;
    this.emptyExpression = emptyExpression;
    this.validationListener =
        (validationListener == null) ? DEFAULT_VALIDATION_LISTENER : validationListener;
    this.messageProvider = messageProvider;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + (expression == null ? 0 : expression.hashCode());
    result = prime * result + (emptyExpression == null ? 0 : emptyExpression.hashCode());
    result = prime * result + (type == null ? 0 : type.hashCode());
    result = prime * result + (resultListener == null ? 0 : resultListener.hashCode());
    result = prime * result + (validationListener == null ? 0 : validationListener.hashCode());
    return result;
  }

  @Override
  public boolean equals(final Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (!(obj instanceof ProtoMessageQueryProcessor)) {
      return false;
    }
    final ProtoMessageQueryProcessor other = (ProtoMessageQueryProcessor) obj;
    if (expression == null) {
      if (other.expression != null) {
        return false;
      }
    } else if (!expression.equals(other.expression)) {
      return false;
    }
    if (emptyExpression == null) {
      if (other.emptyExpression != null) {
        return false;
      }
    } else if (!emptyExpression.equals(other.emptyExpression)) {
      return false;
    }
    if (type == null) {
      if (other.type != null) {
        return false;
      }
    } else if (!type.equals(other.type)) {
      return false;
    }
    if (resultListener == null) {
      if (other.resultListener != null) {
        return false;
      }
    } else if (!resultListener.equals(other.resultListener)) {
      return false;
    }
    if (validationListener == null) {
      if (other.validationListener != null) {
        return false;
      }
    } else if (!validationListener.equals(other.validationListener)) {
      return false;
    }
    return true;
  }

  public static ProtoMessageQueryProcessor getIdentityProcessor() {
    return IDENTITY_INSTANCE;
  }

  public static ProtoMessageQueryProcessor getEmptyProcessor() {
    return EMPTY_INSTANCE;
  }

  public static QueryBuilder builder() {
    return new QueryBuilder();
  }

  @Override
  public List process(final List originalList) {
    return process(originalList, type, expression, emptyExpression, null, messageProvider);
  }

  @Override
  public List process(final List originalList,
      final Map beans) {
    return process(originalList, type, expression, emptyExpression, beans, messageProvider);
  }

  public List process(final List originalList, final Descriptor type,
      final String expression, final String emptyExpression, final Map beans,
      final IDynamicMessageProvider messageProvider) {
    if (originalList == null) {
      throw new NullPointerException();
    } else if (originalList.isEmpty() ? emptyExpression == null || emptyExpression.isEmpty()
        : expression == null || expression.isEmpty()) {
      return Collections.emptyList();
    }

    final ELProcessor elp = newELProcessor();

    if (beans != null && !beans.isEmpty()) {
      for (final Entry entry : beans.entrySet()) {
        elp.defineBean(entry.getKey(), entry.getValue());
      }
    }

    final List results = new ArrayList();
    final List readOnlyResults = Collections.unmodifiableList(results);
    int index = -1;

    if (originalList.isEmpty()) {
      final Object result = elp.eval(emptyExpression);

      if (!collectResult(results, type, index, result, originalList)) {
        return Collections.emptyList();
      }

      return readOnlyResults;
    }

    final List records = Collections.unmodifiableList(originalList);
    elp.defineBean("records", records);
    elp.defineBean("results", readOnlyResults);
    final ELContext context = elp.getELManager().getELContext();
    final ValueExpression processExpr = getValueExpression(context, expression, Object.class);

    for (final Message message : records) {
      elp.defineBean("record", getBuilder(message, messageProvider));
      elp.defineBean("index", ++index);
      final Object result = processExpr.getValue(context);

      if (!collectResult(results, type, index, result, originalList)) {
        return Collections.emptyList();
      }
    }

    return readOnlyResults;
  }

  private boolean collectResult(final List results, final Descriptor type,
      final int index, final Object result, final List originalList) {
    if (result == null) {
      // noop - record is not added to the results, i.e. skipped!
    } else if (result instanceof Iterable) {
      // add all non-null items, properly converted, to the results
      for (final Object element : (Iterable) result) {
        if (!collectSingleResult(results, type, index, element, originalList)) {
          return false;
        }
      }
    } else {
      if (!collectSingleResult(results, type, index, result, originalList)) {
        return false;
      }
    }

    return true;
  }

  private boolean collectSingleResult(final List results, final Descriptor type,
      final int index, final Object result, final List originalList) {
    if (result == null) {
      return true;
    } else if (!(result instanceof MessageOrBuilder)) {
      throw new IllegalArgumentException(String.format(
          "result of original record #%s is not a MessageOrBuilder", index));
    } else if (!validationListener.validate(type, originalList.get(index),
        (MessageOrBuilder) result)) {
      throw new IllegalArgumentException(String.format(
          "result of original record #%s is of the wrong message type", index));
    }

    if (result instanceof Message.Builder) {
      results.add(((Message.Builder) result).build());
    } else if (result instanceof Message) {
      results.add((Message) result);
    }

    return notifyResultAdded(index);
  }

  /**
   * Notifies resultListener when the record added.
   *
   * @param index originalList record index
   * @return true - to continue (by default), false - to abort
   */
  protected boolean notifyResultAdded(final int index) {
    return (resultListener == null) ? true : resultListener.resultAdded(index);
  }

  protected ELProcessor newELProcessor() {
    return new ProtoELProcessorEx();
  }

  protected Message.Builder getBuilder(final Message message,
      final IDynamicMessageProvider messageProvider) {
    if (message == null) {
      return null;
    } else if (message instanceof GeneratedMessage || message instanceof IBuilder2) {
      return message.toBuilder();
    } else {
      return messageProvider.newBuilder(message);
    }
  }

  private ValueExpression getValueExpression(final ELContext context, final String expression,
      final Class expectedType) {
    return ELManager.getExpressionFactory().createValueExpression(context, bracket(expression),
        expectedType);
  }

  private String bracket(final String expression) {
    return "${" + expression + '}';
  }

  /**
   * A query builder and EL 3.0 Language processor with ProtoBuf.
   * 
   * @author [email protected] David Tesler
   */
  public static final class QueryBuilder implements IQueryProcessor {
    private Descriptor type = null;
    private String expression = null;
    private String emptyExpression = null;
    private QueryResultListener resultListener = null;
    private ValidationListener validationListener = null;
    private Map beans = null;
    private IDynamicMessageProvider messageProvider = null;

    /**
     * Constructs a default QueryBuilder
     */
    public QueryBuilder() {
      messageProvider = DynamicMessage.getProvider();
    }

    /**
     * Constructs a QueryBuilder based on another QueryBuilder.
     * 

* All the fields of the parameter builder are copied as-is but {@code beans}, which are not * set. This is done to prevent the inadvertent original builder's {@code beans} mutation. If * needed, this can be accomplished explicitly via {@link #setBeans(Map)} or * {@link #addBean(String, Object)} methods. * * @param builder a template QueryBuilder to copy from */ public QueryBuilder(final QueryBuilder builder) { type = builder.type; expression = builder.expression; emptyExpression = builder.emptyExpression; resultListener = builder.resultListener; validationListener = builder.validationListener; messageProvider = builder.messageProvider; // beans = (builder.beans == null) ? null : new HashMap(builder.beans); } public QueryBuilder(final ProtoMessageQueryProcessor processor) { type = processor.type; expression = processor.expression; emptyExpression = processor.emptyExpression; resultListener = processor.resultListener; validationListener = processor.validationListener; messageProvider = processor.messageProvider; } public IDynamicMessageProvider getMessageProvider() { return messageProvider; } public QueryBuilder setMessageProvider(final IDynamicMessageProvider messageProvider) { this.messageProvider = messageProvider; return this; } public Map getBeans() { return beans; } /** * Sets this QueryBuilder's beans to the shallow copy of the parameter. * * @param beans a map of a bean name and its value to be copied over */ public QueryBuilder setBeans(final Map beans) { this.beans = beans == null ? null : new HashMap(beans); return this; } public QueryBuilder addBean(final String name, final Object bean) { if (beans == null) { beans = new HashMap(beans); } beans.put(name, bean); return this; } public Descriptor getType() { return type; } public QueryBuilder setType(final Descriptor type) { this.type = type; return this; } public QueryResultListener getResultListener() { return resultListener; } public QueryBuilder setResultListener(final QueryResultListener resultListener) { this.resultListener = resultListener; return this; } public ValidationListener getValidationListener() { return validationListener; } public QueryBuilder setValidationListener(final ValidationListener validationListener) { this.validationListener = validationListener; return this; } public String getExpression() { return expression; } public QueryBuilder setExpression(final String expression) { this.expression = expression; return this; } public String getEmptyExpression() { return emptyExpression; } public QueryBuilder setEmptyExpression(final String emptyExpression) { this.emptyExpression = emptyExpression; return this; } public ProtoMessageQueryProcessor build() { if (messageProvider == null) { throw new NullPointerException(); } final ProtoMessageQueryProcessor processor = new ProtoMessageQueryProcessor(expression, emptyExpression, type, resultListener, validationListener, messageProvider); return processor; } @Override public List process(final List originalList) { return process(originalList, beans); } @Override public List process(final List originalList, final Map beans) { return getEmptyProcessor().process(originalList, type, expression, emptyExpression, beans, messageProvider); } } public static class DefaultValidationListener implements ValidationListener { @Override public boolean validate(final Descriptor type, final Message original, final MessageOrBuilder result) { if (type == null) { return original == null ? true : original.getDescriptorForType().equals( result.getDescriptorForType()); } else { return type.equals(result.getDescriptorForType()); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy