/* * Copyright 2013-2017 the original author or authors * * 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 org.springframework.data.cassandra.mapping; import static org.springframework.cassandra.core.cql.CqlIdentifier.cqlId; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Optional; import org.springframework.beans.BeansException; import org.springframework.cassandra.core.cql.CqlIdentifier; import org.springframework.cassandra.support.exception.UnsupportedCassandraOperationException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.expression.BeanFactoryAccessor; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.data.cassandra.util.SpelUtils; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.AssociationHandler; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.util.TypeInformation; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import com.datastax.driver.core.UserType; /** * Cassandra specific {@link BasicPersistentEntity} implementation that adds Cassandra specific metadata. * * @author Alex Shvid * @author Matthew T. Adams * @author John Blum * @author Mark Paluch */ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T, CassandraPersistentProperty> implements CassandraPersistentEntity<T>, ApplicationContextAware { private static final CassandraPersistentEntityMetadataVerifier DEFAULT_VERIFIER = new CompositeCassandraPersistentEntityMetadataVerifier(); private static final Optional<Comparator<CassandraPersistentProperty>> PROPERTY_COMPARATOR = Optional .of(CassandraPersistentPropertyComparator.INSTANCE); private CassandraPersistentEntityMetadataVerifier verifier = DEFAULT_VERIFIER; private CassandraMappingContext mappingContext; private ApplicationContext context; private StandardEvaluationContext spelContext; private Optional<Boolean> forceQuote = Optional.empty(); private Optional<CqlIdentifier> tableName = Optional.empty(); /** * Create a new {@link BasicCassandraPersistentEntity} given {@link TypeInformation}. * * @param typeInformation must not be {@literal null}. */ public BasicCassandraPersistentEntity(TypeInformation<T> typeInformation) { this(typeInformation, null, DEFAULT_VERIFIER); } /** * Create a new {@link BasicCassandraPersistentEntity} with the given {@link TypeInformation}. Will default the table * name to the entity's simple type name. * * @param typeInformation */ public BasicCassandraPersistentEntity(TypeInformation<T> typeInformation, CassandraMappingContext mappingContext) { this(typeInformation, mappingContext, DEFAULT_VERIFIER); } /** * Create a new {@link BasicCassandraPersistentEntity} with the given {@link TypeInformation}. Will default the table * name to the entity's simple type name. * * @param typeInformation */ public BasicCassandraPersistentEntity(TypeInformation<T> typeInformation, CassandraMappingContext mappingContext, CassandraPersistentEntityMetadataVerifier verifier) { // FIXME: Constructor with comparator, no optionality here super(typeInformation, PROPERTY_COMPARATOR); this.mappingContext = mappingContext; setVerifier(verifier); } protected CqlIdentifier determineTableName() { Optional<Table> tableAnnotation = findAnnotation(Table.class); return tableAnnotation // .map(annotation -> determineName(annotation.value(), annotation.forceQuote())) // .orElseGet(this::determineDefaultName); } /* (non-Javadoc) * @see org.springframework.data.mapping.model.BasicPersistentEntity#addAssociation(org.springframework.data.mapping.Association) */ @Override public void addAssociation(Association<CassandraPersistentProperty> association) { throw new UnsupportedCassandraOperationException("Cassandra does not support associations"); } /* (non-Javadoc) * @see org.springframework.data.mapping.model.BasicPersistentEntity#doWithAssociations(org.springframework.data.mapping.AssociationHandler) */ @Override public void doWithAssociations(AssociationHandler<CassandraPersistentProperty> handler) { throw new UnsupportedCassandraOperationException("Cassandra does not support associations"); } /* (non-Javadoc) * @see org.springframework.data.cassandra.mapping.CassandraPersistentEntity#isCompositePrimaryKey() */ @Override public boolean isCompositePrimaryKey() { return findAnnotation(PrimaryKeyClass.class).isPresent(); } /* (non-Javadoc) * @see org.springframework.data.cassandra.mapping.CassandraPersistentEntity#getCompositePrimaryKeyProperties() */ @Override public List<CassandraPersistentProperty> getCompositePrimaryKeyProperties() { List<CassandraPersistentProperty> properties = new ArrayList<>(); Assert.state(isCompositePrimaryKey(), String.format("[%s] does not represent a composite primary key class", this.getType().getName())); addCompositePrimaryKeyProperties(this, properties); return properties; } protected void addCompositePrimaryKeyProperties(CassandraPersistentEntity<?> compositePrimaryKeyEntity, final List<CassandraPersistentProperty> properties) { compositePrimaryKeyEntity.getPersistentProperties().forEach(property -> { if (property.isCompositePrimaryKey()) { addCompositePrimaryKeyProperties(property.getCompositePrimaryKeyEntity(), properties); } else { properties.add(property); } }); } /* (non-Javadoc) * @see org.springframework.data.mapping.model.BasicPersistentEntity#verify() */ @Override public void verify() throws MappingException { super.verify(); if (verifier != null) { verifier.verify(this); } if (!tableName.isPresent()) { setTableName(determineTableName()); } } /* (non-Javadoc) * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) */ @Override public void setApplicationContext(ApplicationContext context) throws BeansException { Assert.notNull(context, "ApplicationContext must not be null"); this.context = context; spelContext = new StandardEvaluationContext(); spelContext.addPropertyAccessor(new BeanFactoryAccessor()); spelContext.setBeanResolver(new BeanFactoryResolver(context)); spelContext.setRootObject(context); } /* (non-Javadoc) * @see org.springframework.data.cassandra.mapping.CassandraPersistentEntity#getApplicationContext() */ @Override public ApplicationContext getApplicationContext() { return context; } /* (non-Javadoc) * @see org.springframework.data.cassandra.mapping.CassandraPersistentEntity#setForceQuote(boolean) */ @Override public void setForceQuote(boolean forceQuote) { boolean changed = !this.forceQuote.isPresent() || this.forceQuote.filter(v -> v != forceQuote).isPresent(); this.forceQuote = Optional.of(forceQuote); if (changed) { setTableName(cqlId(getTableName().getUnquoted(), forceQuote)); } } /* (non-Javadoc) * @see org.springframework.data.cassandra.mapping.CassandraPersistentEntity#getMappingContext() */ @Override public CassandraMappingContext getMappingContext() { return mappingContext; } /* (non-Javadoc) * @see org.springframework.data.cassandra.mapping.CassandraPersistentEntity#setTableName(org.springframework.cassandra.core.cql.CqlIdentifier) */ @Override public void setTableName(CqlIdentifier tableName) { Assert.notNull(tableName, "CqlIdentifier must not be null"); this.tableName = Optional.of(tableName); } /* (non-Javadoc) * @see org.springframework.data.cassandra.mapping.CassandraPersistentEntity#getTableName() */ @Override public CqlIdentifier getTableName() { return tableName.orElseGet(this::determineTableName); } /** * @param verifier The verifier to set. */ public void setVerifier(CassandraPersistentEntityMetadataVerifier verifier) { this.verifier = verifier; } /** * @return the verifier. */ public CassandraPersistentEntityMetadataVerifier getVerifier() { return verifier; } /* (non-Javadoc) * @see org.springframework.data.cassandra.mapping.CassandraPersistentEntity#isUserDefinedType() */ @Override public boolean isUserDefinedType() { return false; } /* (non-Javadoc) * @see org.springframework.data.cassandra.mapping.CassandraPersistentEntity#getUserType() */ @Override public UserType getUserType() { return null; } protected CqlIdentifier determineDefaultName() { return cqlId(getType().getSimpleName(), false); } protected CqlIdentifier determineName(String value, boolean forceQuote) { if (!StringUtils.hasText(value)) { return cqlId(getType().getSimpleName(), forceQuote); } return cqlId(spelContext == null ? value : SpelUtils.evaluate(value, spelContext), forceQuote); } }