/*
* 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.ignite.cache.store.cassandra.persistence;
import java.beans.PropertyDescriptor;
import java.util.LinkedList;
import java.util.List;
import org.apache.ignite.IgniteException;
import org.apache.ignite.cache.store.cassandra.common.PropertyMappingHelper;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* Stores persistence settings for Ignite cache key
*/
public class KeyPersistenceSettings extends PersistenceSettings {
/** Partition key XML tag. */
private static final String PARTITION_KEY_ELEMENT = "partitionKey";
/** Cluster key XML tag. */
private static final String CLUSTER_KEY_ELEMENT = "clusterKey";
/** POJO field XML tag. */
private static final String FIELD_ELEMENT = "field";
/** POJO fields. */
private List<PojoField> fields = new LinkedList<>();
/** Partition key fields. */
private List<PojoField> partKeyFields = new LinkedList<>();
/** Cluster key fields. */
private List<PojoField> clusterKeyFields = new LinkedList<>();
/**
* Creates key persistence settings object based on it's XML configuration.
*
* @param el XML element storing key persistence settings
*/
public KeyPersistenceSettings(Element el) {
super(el);
if (PersistenceStrategy.POJO != getStrategy()) {
init();
return;
}
NodeList keyElem = el.getElementsByTagName(PARTITION_KEY_ELEMENT);
Element partKeysNode = keyElem != null ? (Element) keyElem.item(0) : null;
Element clusterKeysNode = el.getElementsByTagName(CLUSTER_KEY_ELEMENT) != null ?
(Element)el.getElementsByTagName(CLUSTER_KEY_ELEMENT).item(0) : null;
if (partKeysNode == null && clusterKeysNode != null) {
throw new IllegalArgumentException("It's not allowed to specify cluster key fields mapping, but " +
"doesn't specify partition key mappings");
}
partKeyFields = detectFields(partKeysNode, getPartitionKeyDescriptors());
if (partKeyFields == null || partKeyFields.isEmpty()) {
throw new IllegalStateException("Failed to initialize partition key fields for class '" +
getJavaClass().getName() + "'");
}
clusterKeyFields = detectFields(clusterKeysNode, getClusterKeyDescriptors(partKeyFields));
fields = new LinkedList<>();
fields.addAll(partKeyFields);
fields.addAll(clusterKeyFields);
checkDuplicates(fields);
init();
}
/** {@inheritDoc} */
@Override public List<PojoField> getFields() {
return fields;
}
/**
* Returns Cassandra DDL for primary key.
*
* @return DDL statement.
*/
public String getPrimaryKeyDDL() {
StringBuilder partKey = new StringBuilder();
List<String> cols = getPartitionKeyColumns();
for (String column : cols) {
if (partKey.length() != 0)
partKey.append(", ");
partKey.append("\"").append(column).append("\"");
}
StringBuilder clusterKey = new StringBuilder();
cols = getClusterKeyColumns();
if (cols != null) {
for (String column : cols) {
if (clusterKey.length() != 0)
clusterKey.append(", ");
clusterKey.append("\"").append(column).append("\"");
}
}
return clusterKey.length() == 0 ?
" primary key ((" + partKey + "))" :
" primary key ((" + partKey + "), " + clusterKey + ")";
}
/**
* Returns Cassandra DDL for cluster key.
*
* @return Cluster key DDL.
*/
public String getClusteringDDL() {
StringBuilder builder = new StringBuilder();
for (PojoField field : clusterKeyFields) {
PojoKeyField.SortOrder sortOrder = ((PojoKeyField)field).getSortOrder();
if (sortOrder == null)
continue;
if (builder.length() != 0)
builder.append(", ");
boolean asc = PojoKeyField.SortOrder.ASC == sortOrder;
builder.append("\"").append(field.getColumn()).append("\" ").append(asc ? "asc" : "desc");
}
return builder.length() == 0 ? null : "clustering order by (" + builder + ")";
}
/** {@inheritDoc} */
@Override protected String defaultColumnName() {
return "key";
}
/**
* Returns partition key columns of Cassandra table.
*
* @return List of column names.
*/
private List<String> getPartitionKeyColumns() {
List<String> cols = new LinkedList<>();
if (PersistenceStrategy.BLOB == getStrategy() || PersistenceStrategy.PRIMITIVE == getStrategy()) {
cols.add(getColumn());
return cols;
}
if (partKeyFields != null) {
for (PojoField field : partKeyFields)
cols.add(field.getColumn());
}
return cols;
}
/**
* Returns cluster key columns of Cassandra table.
*
* @return List of column names.
*/
private List<String> getClusterKeyColumns() {
List<String> cols = new LinkedList<>();
if (clusterKeyFields != null) {
for (PojoField field : clusterKeyFields)
cols.add(field.getColumn());
}
return cols;
}
/**
* Extracts POJO fields specified in XML element.
*
* @param el XML element describing fields.
* @param descriptors POJO fields descriptors.
* @return List of {@code This} fields.
*/
private List<PojoField> detectFields(Element el, List<PropertyDescriptor> descriptors) {
List<PojoField> list = new LinkedList<>();
if (el == null && (descriptors == null || descriptors.isEmpty()))
return list;
if (el == null) {
for (PropertyDescriptor desc : descriptors) {
// Skip POJO field if it's read-only
if (desc.getWriteMethod() != null)
list.add(new PojoKeyField(desc));
}
return list;
}
NodeList nodes = el.getElementsByTagName(FIELD_ELEMENT);
int cnt = nodes == null ? 0 : nodes.getLength();
if (cnt == 0) {
throw new IllegalArgumentException("Incorrect configuration of Cassandra key persistence settings, " +
"no key fields specified inside '" + PARTITION_KEY_ELEMENT + "/" +
CLUSTER_KEY_ELEMENT + "' element");
}
for (int i = 0; i < cnt; i++) {
PojoKeyField field = new PojoKeyField((Element)nodes.item(i), getJavaClass());
PropertyDescriptor desc = findPropertyDescriptor(descriptors, field.getName());
if (desc == null) {
throw new IllegalArgumentException("Specified POJO field '" + field.getName() +
"' doesn't exist in '" + getJavaClass().getName() + "' class");
}
list.add(field);
}
return list;
}
/**
* @return POJO field descriptors for partition key.
*/
private List<PropertyDescriptor> getPartitionKeyDescriptors() {
List<PropertyDescriptor> primitivePropDescriptors = PropertyMappingHelper.getPojoPropertyDescriptors(
getJavaClass(), true);
boolean valid = false;
for (PropertyDescriptor desc : primitivePropDescriptors) {
if (desc.getWriteMethod() != null) {
valid = true;
break;
}
}
if (!valid) {
throw new IgniteException("Partition key can't have only calculated read-only fields, there should be " +
"some fields with setter method");
}
return primitivePropDescriptors;
}
/**
* @param partKeyFields List of fields.
* @return POJO field descriptors for cluster key.
*/
private List<PropertyDescriptor> getClusterKeyDescriptors(List<PojoField> partKeyFields) {
List<PropertyDescriptor> primitivePropDescriptors =
PropertyMappingHelper.getPojoPropertyDescriptors(getJavaClass(), true);
if (primitivePropDescriptors == null || primitivePropDescriptors.isEmpty() ||
partKeyFields.size() == primitivePropDescriptors.size())
return null;
for (PojoField field : partKeyFields) {
for (int i = 0; i < primitivePropDescriptors.size(); i++) {
if (primitivePropDescriptors.get(i).getName().equals(field.getName())) {
primitivePropDescriptors.remove(i);
break;
}
}
}
return primitivePropDescriptors;
}
}