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

com.stratio.cassandra.lucene.column.ColumnsMapper.scala Maven / Gradle / Ivy

There is a newer version: 3.11.3.0
Show newest version
/*
 * Copyright (C) 2014 Stratio (http://stratio.com)
 *
 * 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 com.stratio.cassandra.lucene.column

import java.math.{BigDecimal, BigInteger}
import java.net.InetAddress
import java.nio.ByteBuffer
import java.util.{Date, UUID}

import com.stratio.cassandra.lucene.IndexException
import org.apache.cassandra.config.CFMetaData
import org.apache.cassandra.db.marshal._
import org.apache.cassandra.db.rows.{Cell, ComplexColumnData, Row}
import org.apache.cassandra.serializers.CollectionSerializer
import org.apache.cassandra.transport.Server._
import org.apache.cassandra.utils.ByteBufferUtil

import scala.annotation.tailrec
import scala.collection.JavaConversions._

/**
  * Maps Cassandra rows to [[Columns]].
  *
  * @author Andres de la Pena `[email protected]`
  */
object ColumnsMapper {

  /**
    * Returns a [[Columns]] representing the specified row.
    *
    * @param row the Cassandra row to be mapped
    */
  def columns(row: Row): Columns = {
    row.columns().foldLeft(Columns())((cs, columnDefinition) =>
      if (columnDefinition.isComplex)
        cs + columns(row.getComplexColumnData(columnDefinition))
      else
        cs + columns(row.getCell(columnDefinition))
    )
  }

  private[column] def columns(complexColumnData: ComplexColumnData): Columns = {
    complexColumnData.foldLeft(Columns())((cs, cell) => cs + columns(cell))
  }

  private[column] def columns(cell: Cell): Columns = {
    if (cell == null) return Columns()
    val isTombstone = cell.isTombstone
    val name = cell.column.name.toString
    val comparator = cell.column.`type`
    val value = cell.value
    val column = new Column(cellName = name, deletionTime = cell.localDeletionTime)
    comparator match {
      case setType: SetType[_] if !setType.isFrozenCollection =>
        val itemComparator = setType.nameComparator
        val itemValue = cell.path.get(0)
        columns(isTombstone, column, itemComparator, itemValue)
      case listType: ListType[_] if !listType.isFrozenCollection =>
        val itemComparator = listType.valueComparator
        columns(isTombstone, column, itemComparator, value)
      case mapType: MapType[_, _] if !mapType.isFrozenCollection =>
        val itemComparator = mapType.valueComparator
        val keyValue = cell.path.get(0)
        val keyComparator = mapType.nameComparator
        val nameSuffix = keyComparator.compose(keyValue).toString
        columns(isTombstone, column.withMapName(nameSuffix), itemComparator, value)
      case userType:UserType =>
        val cellPath=cell.path()
        if (cellPath == null) {
          columns(isTombstone, column, comparator, value)
        } else {
          val position = ByteBufferUtil.toShort(cellPath.get(0))
          val name = userType.fieldNameAsString(position)
          val typo = userType.`type`(position)
          columns(isTombstone, column.withUDTName(name), typo, value)
        }
      case _ =>
        columns(isTombstone, column, comparator, value)
    }
  }

  private[column] def columns(isTombstone: Boolean,
                              column: Column[_],
                              abstractType: AbstractType[_],
                              value: ByteBuffer): Columns = abstractType match {
    case setType: SetType[_] =>
      columns(isTombstone, column, setType, value)
    case listType: ListType[_] =>
      columns(isTombstone, column, listType, value)
    case mapType: MapType[_, _] =>
      columns(isTombstone, column, mapType, value)
    case userType: UserType =>
      columns(isTombstone, column, userType, value)
    case tupleType: TupleType =>
      columns(isTombstone, column, tupleType, value)
    case _ =>
      Columns(column.withValue(compose(value, abstractType)))
  }

  private[this] def columns(isTombstone: Boolean, column: Column[_], set: SetType[_], value: ByteBuffer): Columns = {
    if (isTombstone) return Columns(column)
    val nameType = set.nameComparator
    val bb = ByteBufferUtil.clone(value) // CollectionSerializer read functions are impure
    (0 until frozenCollectionSize(bb)).foldLeft(Columns())((cs, n) => {
      val itemValue = frozenCollectionValue(bb)
      cs + columns(isTombstone, column, nameType, itemValue)
    })
  }

  private[this] def columns(isTombstone: Boolean, column: Column[_], list: ListType[_], value: ByteBuffer): Columns = {
    if (isTombstone) return Columns(column)
    val valueType = list.valueComparator
    val bb = ByteBufferUtil.clone(value) // CollectionSerializer read functions are impure
    (0 until frozenCollectionSize(bb)).foldLeft(Columns())((cs, n) => {
      val itemValue = frozenCollectionValue(bb)
      cs + columns(isTombstone, column, valueType, itemValue)
    })
  }

  private[this] def columns(isTombstone: Boolean, column: Column[_], map: MapType[_, _], value: ByteBuffer): Columns = {
    if (isTombstone) return Columns(column)
    val itemKeysType = map.nameComparator
    val itemValuesType = map.valueComparator
    val bb = ByteBufferUtil.clone(value) // CollectionSerializer read functions are impure
    (0 until frozenCollectionSize(bb)).foldLeft(Columns())((cs, n) => {
      val itemKey = frozenCollectionValue(bb)
      val itemValue = frozenCollectionValue(bb)
      val itemName = itemKeysType.compose(itemKey).toString
      cs + columns(isTombstone, column.withMapName(itemName), itemValuesType, itemValue)
    })
  }

  private[this] def columns(isTombstone: Boolean, column: Column[_], udt: UserType, value: ByteBuffer): Columns = {
    if (isTombstone) return Columns(column)
    val itemValues = udt.split(value)
    (0 until udt.fieldNames.size).foldLeft(Columns())((cs, i) => {
      val itemName = udt.fieldNameAsString(i)
      val itemType = udt.fieldType(i)
      val itemValue = itemValues(i)
      if (isTombstone || itemValue == null)
        cs + column.withUDTName(itemName)
      else
        cs + columns(isTombstone, column.withUDTName(itemName), itemType, itemValue)
    })
  }

  private[this] def columns(isTombstone: Boolean, column: Column[_], tuple: TupleType, value: ByteBuffer): Columns = {
    if (isTombstone) return Columns(column)
    val itemValues = tuple.split(value)
    (0 until tuple.size).foldLeft(Columns())((cs, i) => {
      val itemName = i.toString
      val itemType = tuple.`type`(i)
      val itemValue = itemValues(i)
      if (isTombstone || itemValue == null)
        cs + column.withUDTName(itemName)
      else
        cs + columns(isTombstone, column.withUDTName(itemName), itemType, itemValue)
    })
  }

  private[this] def frozenCollectionSize(bb: ByteBuffer): Int =
    CollectionSerializer.readCollectionSize(bb, CURRENT_VERSION)

  private[this] def frozenCollectionValue(bb: ByteBuffer): ByteBuffer =
    CollectionSerializer.readValue(bb, CURRENT_VERSION)

  def compose(bb: ByteBuffer, t: AbstractType[_]): Any = t match {
    case sdt: SimpleDateType => new Date(sdt.toTimeInMillis(bb))
    case _ => t.compose(bb)
  }

  ///////////////////////////////////////////////////////////////////////////
  // Validation
  ///////////////////////////////////////////////////////////////////////////

  def validate(metadata: CFMetaData, column: String, field: String, supportedTypes: java.util.List[Class[_]]) {

    val cellName = Column.parse(column).cellName
    val cellDefinition = metadata.getColumnDefinition(UTF8Type.instance.decompose(cellName))

    if (cellDefinition == null) {
      throw new IndexException("No column definition '{}' for mapper '{}'", cellName, field)
    }
    if (cellDefinition.isStatic) {
      throw new IndexException("Lucene indexes are not allowed on static columns as '{}'", column)
    }

    def checkSupported(t: AbstractType[_], mapper: String) {
      if (!supports(t, supportedTypes)) {
        throw new IndexException("Type '{}' in column '{}' is not supported by mapper '{}'", t, mapper, field)
      }
    }

    val cellType = cellDefinition.`type`
    val udtNames = Column.parse(column).udtNames
    if (udtNames.isEmpty) {
      checkSupported(cellType, cellName)
    } else {
      var col = Column.apply(cellName)
      var currentType = cellType
      for (i <- udtNames.indices) {
        col = col.withUDTName(udtNames(i))
        ColumnsMapper.childType(currentType, udtNames(i)) match {
          case None => throw new IndexException("No column definition '{}' for mapper '{}'", col.mapperName, field)
          case Some(n) if i == udtNames.indices.last => checkSupported(n, col.mapperName)
          case Some(n) => currentType = n
        }
      }
    }
  }

  @tailrec
  def childType(parent: AbstractType[_], child: String): Option[AbstractType[_]] = parent match {
    case t: ReversedType[_] => childType(t.baseType, child)
    case t: SetType[_] => childType(t.nameComparator, child)
    case t: ListType[_] => childType(t.valueComparator, child)
    case t: MapType[_, _] => childType(t.valueComparator, child)
    case t: UserType => (0 until t.fieldNames.size).find(t.fieldNameAsString(_) == child).map(t.fieldType)
    case t: TupleType => (0 until t.size).find(_.toString == child).map(t.`type`)
    case _ => None
  }

  @tailrec
  def supports(candidateType: AbstractType[_], supportedTypes: Seq[Class[_]]): Boolean = candidateType match {
    case t: ReversedType[_] => supports(t.baseType, supportedTypes)
    case t: SetType[_] => supports(t.getElementsType, supportedTypes)
    case t: ListType[_] => supports(t.getElementsType, supportedTypes)
    case t: MapType[_, _] => supports(t.getValuesType, supportedTypes)
    case _ =>
      val native = nativeType(candidateType)
      supportedTypes.exists(_ isAssignableFrom native)
  }

  def nativeType(validator: AbstractType[_]): Class[_] = validator match {
    case _: UTF8Type | _: AsciiType => classOf[String]
    case _: SimpleDateType | _: TimestampType => classOf[Date]
    case _: UUIDType | _: LexicalUUIDType | _: TimeUUIDType => classOf[UUID]
    case _: ShortType => classOf[java.lang.Short]
    case _: ByteType => classOf[java.lang.Byte]
    case _: Int32Type => classOf[Integer]
    case _: LongType => classOf[java.lang.Long]
    case _: IntegerType => classOf[BigInteger]
    case _: FloatType => classOf[java.lang.Float]
    case _: DoubleType => classOf[java.lang.Double]
    case _: DecimalType => classOf[BigDecimal]
    case _: BooleanType => classOf[java.lang.Boolean]
    case _: BytesType => classOf[ByteBuffer]
    case _: InetAddressType => classOf[InetAddress]
    case _ => throw new IndexException(s"Unsupported Cassandra data type: ${validator.getClass}")
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy