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

org.apache.lens.cube.metadata.ExprColumn Maven / Gradle / Ivy

The newest version!
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.lens.cube.metadata;

import java.io.UnsupportedEncodingException;
import java.util.*;

import org.apache.lens.cube.parse.HQLParser;
import org.apache.lens.server.api.error.LensException;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.hive.metastore.api.FieldSchema;
import org.apache.hadoop.hive.ql.parse.ASTNode;

import lombok.*;

public class ExprColumn extends CubeColumn {
  public static final char EXPRESSION_DELIMITER = '|';
  public static final char EXPRESSION_SPEC_DELIMITER = ':';
  private static final String EXPRESSION_ENCODED = "true";
  private final Set expressionSet = new LinkedHashSet<>();
  private List astNodeList = new ArrayList<>();
  private final String type;
  private boolean hasHashCode = false;
  private int hashCode;

  // for backward compatibility
  public ExprColumn(FieldSchema column, String displayString, String expression) throws LensException {
    this(column, displayString, new ExprSpec(expression, null, null));
  }

  public ExprColumn(FieldSchema column, String displayString,
                    ExprSpec... expressions) throws LensException {
    this(column, displayString, new HashMap(), expressions);
  }

  public ExprColumn(FieldSchema column, String displayString, Map tags,
                    ExprSpec... expressions) throws LensException {
    super(column.getName(), column.getComment(), displayString, null, null, 0.0, tags);

    if (expressions == null || expressions.length == 0) {
      throw new IllegalArgumentException("No expressions specified for column " + column.getName());
    }

    for (int i = 0; i < expressions.length; i++) {
      ExprSpec e = expressions[i];
      if (StringUtils.isBlank(e.getExpr())) {
        throw new IllegalArgumentException(
          "No expression string specified for column " + column.getName() + " at index:" + i);
      }
      if (e.getStartTime() != null && e.getEndTime() != null) {
        if (e.getStartTime().after(e.getEndTime())) {
          throw new IllegalArgumentException("Start time is after end time for column " + column.getName()
            + " for expression at index:" + i + " for " + e.getExpr());
        }
      }
      expressionSet.add(e);
    }
    this.type = column.getType();
    assert (getAst() != null);
  }

  public ExprColumn(String name, Map props) {
    super(name, props);

    String serializedExpressions = props.get(MetastoreUtil.getExprColumnKey(getName()));
    String[] expressions = StringUtils.split(serializedExpressions, EXPRESSION_DELIMITER);

    if (expressions.length == 0) {
      throw new IllegalArgumentException("No expressions found for column "
        + name + " property val=" + serializedExpressions);
    }

    boolean isExpressionBase64Encoded =
      EXPRESSION_ENCODED.equals(props.get(MetastoreUtil.getExprEncodingPropertyKey(getName())));

    for (String e : expressions) {
      String[] exprSpecStrs = StringUtils.splitPreserveAllTokens(e, EXPRESSION_SPEC_DELIMITER);
      try {
        String decodedExpr =
          isExpressionBase64Encoded ? new String(Base64.decodeBase64(exprSpecStrs[0]), "UTF-8") : exprSpecStrs[0];
        ExprSpec exprSpec = new ExprSpec();
        exprSpec.expr = decodedExpr;
        if (exprSpecStrs.length > 1) {
          // start time and end time serialized
          if (StringUtils.isNotBlank(exprSpecStrs[1])) {
            // start time available
            exprSpec.startTime = getDate(exprSpecStrs[1]);
          }
          if (exprSpecStrs.length > 2) {
            if (StringUtils.isNotBlank(exprSpecStrs[2])) {
              // end time available
              exprSpec.endTime = getDate(exprSpecStrs[2]);
            }
          }
        }
        expressionSet.add(exprSpec);
      } catch (UnsupportedEncodingException e1) {
        throw new IllegalArgumentException("Error decoding expression for expression column "
          + name + " encoded value=" + e);
      }
    }

    this.type = props.get(MetastoreUtil.getExprTypePropertyKey(getName()));
  }

  @NoArgsConstructor
  @ToString(exclude = {"astNode", "hasHashCode", "hashCode"})
  public static class ExprSpec {
    @Getter
    @NonNull
    private String expr;
    @Getter
    private Date startTime;
    @Getter
    private Date endTime;

    private transient ASTNode astNode;
    private boolean hasHashCode = false;
    private transient int hashCode;

    public ExprSpec(@NonNull String expr, Date startTime, Date endTime) throws LensException {
      this.expr = expr;
      this.startTime = startTime;
      this.endTime = endTime;
      // validation
      initASTNode();
    }

    private synchronized void initASTNode() throws LensException {
      if (astNode == null) {
        if (StringUtils.isNotBlank(expr)) {
          astNode = MetastoreUtil.parseExpr(getExpr());
        }
      }
    }

    private ASTNode getASTNode() throws LensException {
      initASTNode();
      return astNode;
    }

    public ASTNode copyASTNode() throws LensException {
      return MetastoreUtil.copyAST(getASTNode());
    }
    @Override
    public int hashCode() {
      if (!hasHashCode) {
        final int prime = 31;
        int result = 1;
        ASTNode astNode;
        try {
          astNode = getASTNode();
        } catch (LensException e) {
          throw new IllegalArgumentException(e);
        }
        if (astNode != null) {
          String exprNormalized = HQLParser.getString(astNode);
          result = prime * result + exprNormalized.hashCode();
        }
        result = prime * result + ((getStartTime() == null) ? 0 : COLUMN_TIME_FORMAT.get().format(
          getStartTime()).hashCode());
        result = prime * result + ((getEndTime() == null) ? 0 : COLUMN_TIME_FORMAT.get().format(
          getEndTime()).hashCode());
        hashCode = result;
        hasHashCode = true;
      }
      return hashCode;
    }
  }

  /**
   * Returns the first expression.
   *
   * @return the expression
   */
  public String getExpr() {
    return expressionSet.iterator().next().getExpr();
  }

  public String getType() {
    return type;
  }

  @Override
  public void addProperties(Map props) {
    super.addProperties(props);

    String[] encodedExpressions = new String[expressionSet.size()];
    StringBuilder exprSpecBuilder = new StringBuilder();
    int i = 0;
    for (ExprSpec es : expressionSet) {
      String expression = es.getExpr();
      try {
        exprSpecBuilder.append(Base64.encodeBase64String(expression.getBytes("UTF-8")));
        exprSpecBuilder.append(EXPRESSION_SPEC_DELIMITER);
        if (es.getStartTime() != null) {
          exprSpecBuilder.append(COLUMN_TIME_FORMAT.get().format(es.getStartTime()));
        }
        exprSpecBuilder.append(EXPRESSION_SPEC_DELIMITER);
        if (es.getEndTime() != null) {
          exprSpecBuilder.append(COLUMN_TIME_FORMAT.get().format(es.getEndTime()));
        }
        // encoded expression contains the Base64 encoded expression, start time and end time.
        encodedExpressions[i] = exprSpecBuilder.toString();
        exprSpecBuilder.setLength(0);
        i++;
      } catch (UnsupportedEncodingException e) {
        throw new IllegalArgumentException("Failed to encode expression " + expression);
      }
    }

    String serializedExpressions = StringUtils.join(encodedExpressions, EXPRESSION_DELIMITER);
    props.put(MetastoreUtil.getExprColumnKey(getName()) + ".base64", EXPRESSION_ENCODED);
    props.put(MetastoreUtil.getExprColumnKey(getName()), serializedExpressions);
    props.put(MetastoreUtil.getExprTypePropertyKey(getName()), type);
  }

  @Override
  public int hashCode() {
    if (!hasHashCode) {
      final int prime = 31;
      int result = super.hashCode();
      result = prime * result + ((getType() == null) ? 0 : getType().toLowerCase().hashCode());

      for (ExprSpec exprSpec : expressionSet) {
        result = prime * result + exprSpec.hashCode();
      }

      hashCode = result;
      hasHashCode = true;
    }
    return hashCode;
  }

  @Override
  public boolean equals(Object obj) {
    if (!super.equals(obj)) {
      return false;
    }
    ExprColumn other = (ExprColumn) obj;
    if (this.getType() == null) {
      if (other.getType() != null) {
        return false;
      }
    } else if (!this.getType().equalsIgnoreCase(other.getType())) {
      return false;
    }
    if (this.getAllExpressions() == null) {
      if (other.getAllExpressions() != null) {
        return false;
      }
    }

    if (expressionSet.size() != other.expressionSet.size()) {
      return false;
    }
    // Compare expressions for both - compare ASTs
    List myExpressions, otherExpressions;
    try {
      myExpressions = getExpressionASTList();
    } catch (LensException e) {
      throw new IllegalArgumentException(e);
    }
    try {
      otherExpressions = other.getExpressionASTList();
    } catch (LensException e) {
      throw new IllegalArgumentException(e);
    }
    for (int i = 0; i < myExpressions.size(); i++) {
      if (!HQLParser.equalsAST(myExpressions.get(i), otherExpressions.get(i))) {
        return false;
      }
    }
    // compare start and end times for expressions
    Iterator thisIter = this.expressionSet.iterator();
    Iterator otherIter = other.expressionSet.iterator();
    while (thisIter.hasNext() && otherIter.hasNext()) {
      ExprSpec thisES = thisIter.next();
      ExprSpec otherES = otherIter.next();
      if (!equalDates(thisES.getStartTime(), otherES.getStartTime())) {
        return false;
      }
      if (!equalDates(thisES.getEndTime(), otherES.getEndTime())) {
        return false;
      }
    }
    if (thisIter.hasNext() != otherIter.hasNext()) {
      return false;
    }
    return true;
  }

  private boolean equalDates(Date d1, Date d2) {
    if (d1 == null) {
      if (d2 != null) {
        return false;
      }
    } else if (d2 == null) {
      return false;
    } else if (!COLUMN_TIME_FORMAT.get().format(d1).equals(COLUMN_TIME_FORMAT.get().format(
      d2))) {
      return false;
    }
    return true;
  }

  @Override
  public String toString() {
    String str = super.toString();
    str += "#type:" + type;
    str += "#expr:" + expressionSet.toString();
    return str;
  }

  /**
   * Get the AST corresponding to the expression
   *
   * @return the ast
   */
  public ASTNode getAst() throws LensException {
    return getExpressionASTList().get(0);
  }

  public List getExpressionASTList() throws LensException {
    synchronized (expressionSet) {
      if (astNodeList.isEmpty()) {
        for (ExprSpec expr : expressionSet) {
          astNodeList.add(expr.copyASTNode());
        }
      }
    }
    return astNodeList;
  }

  private Set getAllExpressions() {
    return expressionSet;
  }

  private final Set cachedExpressionStrings = new LinkedHashSet();

  /**
   * Get immutable view of this column's expression strings
   *
   * @return
   */
  public Collection getExpressions() {
    if (cachedExpressionStrings.isEmpty()) {
      synchronized (expressionSet) {
        for (ExprSpec es : expressionSet) {
          cachedExpressionStrings.add(es.getExpr());
        }
      }
    }
    return Collections.unmodifiableSet(cachedExpressionStrings);
  }

  /**
   * Get immutable view of this column's expression full spec
   *
   * @return
   */
  public Collection getExpressionSpecs() {
    return Collections.unmodifiableSet(expressionSet);
  }

  /**
   * Add an expression to existing set of expressions for this column
   *
   * @param expression
   * @throws LensException
   */
  public void addExpression(ExprSpec expression) throws LensException {
    if (expression == null || expression.getExpr().isEmpty()) {
      throw new IllegalArgumentException("Empty expression not allowed");
    }

    // Validate if expression can be correctly parsed
    MetastoreUtil.parseExpr(expression.getExpr());
    synchronized (expressionSet) {
      expressionSet.add(expression);
    }
    astNodeList = null;
    hasHashCode = false;
  }

  /**
   * Remove an expression from the set of expressions of this column
   *
   * @param expression
   * @return
   */
  public boolean removeExpression(String expression) {
    if (expression == null || expression.isEmpty()) {
      throw new IllegalArgumentException("Empty expression not allowed");
    }
    boolean removed = false;
    synchronized (expressionSet) {
      Iterator it = expressionSet.iterator();
      while (it.hasNext()) {
        if (it.next().getExpr().equals(expression)) {
          it.remove();
          removed = true;
          break;
        }
      }
    }
    if (removed) {
      astNodeList = null;
      hasHashCode = false;
    }
    return removed;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy