begin user types implementation

This commit is contained in:
Albert Shift 2015-03-24 21:58:58 -07:00
parent 5242b0ce75
commit 3100cce682
11 changed files with 282 additions and 121 deletions

View file

@ -40,7 +40,7 @@ public interface Timeline {
Session initialization:
```
Timeline timeline = Casser.dsl(Timeline.class);
CasserSession session = Casser.init(getSession()).showCql().createDrop(Timeline.class).get();
CasserSession session = Casser.init(getSession()).showCql().add(Timeline.class).autoCreateDrop().get();
```
Select information:

View file

@ -31,7 +31,7 @@ public class Example {
Cluster cluster = new Cluster.Builder().addContactPoint("localhost").build();
CasserSession session = Casser.connect(cluster).use("test").update(_user).get();
CasserSession session = Casser.connect(cluster).use("test").add(_user).autoUpdate().get();
public static User mapUser(Tuple2<String, Integer> t) {
User user = Casser.pojo(User.class);

View file

@ -0,0 +1,23 @@
/*
* Copyright (C) 2015 Noorq, Inc.
*
* 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 casser.core;
public enum AutoDsl {
VALIDATE,
UPDATE,
CREATE,
CREATE_DROP;
}

View file

@ -17,7 +17,6 @@ package casser.core;
import java.io.Closeable;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import casser.core.dsl.Getter;
@ -47,22 +46,22 @@ public class CasserSession extends AbstractSessionOperations implements Closeabl
private final Session session;
private volatile String usingKeyspace;
private volatile boolean showCql;
private final Set<CasserMappingEntity<?>> dropEntitiesOnClose;
private final CasserMappingRepository mappingRepository;
private final Executor executor;
private final boolean dropSchemaOnClose;
CasserSession(Session session,
String usingKeyspace,
boolean showCql,
Set<CasserMappingEntity<?>> dropEntitiesOnClose,
CasserMappingRepository mappingRepository,
Executor executor) {
Executor executor,
boolean dropSchemaOnClose) {
this.session = session;
this.usingKeyspace = Objects.requireNonNull(usingKeyspace, "keyspace needs to be selected before creating session");
this.showCql = showCql;
this.dropEntitiesOnClose = dropEntitiesOnClose;
this.mappingRepository = mappingRepository;
this.mappingRepository = mappingRepository.setReadOnly();
this.executor = executor;
this.dropSchemaOnClose = dropSchemaOnClose;
}
@Override
@ -243,24 +242,30 @@ public class CasserSession extends AbstractSessionOperations implements Closeabl
}
public void close() {
dropEntitiesIfNeeded();
if (session.isClosed()) {
return;
}
if (dropSchemaOnClose) {
dropSchema();
}
session.close();
}
public CloseFuture closeAsync() {
dropEntitiesIfNeeded();
if (!session.isClosed() && dropSchemaOnClose) {
dropSchema();
}
return session.closeAsync();
}
private void dropEntitiesIfNeeded() {
private void dropSchema() {
if (dropEntitiesOnClose == null || dropEntitiesOnClose.isEmpty()) {
return;
}
for (CasserMappingEntity<?> entity : dropEntitiesOnClose) {
dropEntity(entity);
}
mappingRepository.getKnownEntities().forEach(e -> dropEntity(e));
}

View file

@ -15,21 +15,25 @@
*/
package casser.core;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import casser.core.tuple.Tuple2;
import casser.mapping.CasserMappingEntity;
import casser.mapping.CasserMappingRepository;
import casser.mapping.MappingUtil;
import casser.support.CasserException;
import casser.support.Requires;
import casser.mapping.SimpleDataTypes;
import casser.support.CasserMappingException;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.TableMetadata;
import com.datastax.driver.core.UserType;
import com.google.common.util.concurrent.MoreExecutors;
@ -39,7 +43,6 @@ public class SessionInitializer extends AbstractSessionOperations {
private String usingKeyspace;
private boolean showCql = false;
private Executor executor = MoreExecutors.sameThreadExecutor();
private Set<CasserMappingEntity<?>> dropEntitiesOnClose = null;
private CasserMappingRepository mappingRepository = new CasserMappingRepository();
@ -47,6 +50,9 @@ public class SessionInitializer extends AbstractSessionOperations {
private KeyspaceMetadata keyspaceMetadata;
private final List<Object> initList = new ArrayList<Object>();
private AutoDsl autoDsl = AutoDsl.UPDATE;
SessionInitializer(Session session) {
this.session = Objects.requireNonNull(session, "empty session");
this.usingKeyspace = session.getLoggedKeyspace(); // can be null
@ -98,26 +104,41 @@ public class SessionInitializer extends AbstractSessionOperations {
return showCql;
}
public SessionInitializer validate(Object... dsls) {
initialize(AutoDsl.VALIDATE, dsls);
public SessionInitializer add(Object... dsls) {
Objects.requireNonNull(dsls, "dsls is empty");
int len = dsls.length;
for (int i = 0; i != len; ++i) {
Object obj = Objects.requireNonNull(dsls[i], "element " + i + " is empty");
initList.add(obj);
}
return this;
}
public SessionInitializer autoValidate() {
this.autoDsl = AutoDsl.VALIDATE;
return this;
}
public SessionInitializer update(Object... dsls) {
initialize(AutoDsl.UPDATE, dsls);
public SessionInitializer autoUpdate() {
this.autoDsl = AutoDsl.UPDATE;
return this;
}
public SessionInitializer create(Object... dsls) {
initialize(AutoDsl.CREATE, dsls);
public SessionInitializer autoCreate() {
this.autoDsl = AutoDsl.CREATE;
return this;
}
public SessionInitializer createDrop(Object... dsls) {
initialize(AutoDsl.CREATE_DROP, dsls);
public SessionInitializer autoCreateDrop() {
this.autoDsl = AutoDsl.CREATE_DROP;
return this;
}
public SessionInitializer auto(AutoDsl autoDsl) {
this.autoDsl = autoDsl;
return this;
}
public SessionInitializer use(String keyspace) {
session.execute(SchemaUtil.useCql(keyspace, false));
this.usingKeyspace = keyspace;
@ -131,75 +152,71 @@ public class SessionInitializer extends AbstractSessionOperations {
}
public CasserSession get() {
initialize();
return new CasserSession(session,
usingKeyspace,
showCql,
dropEntitiesOnClose,
mappingRepository,
executor);
executor,
autoDsl == AutoDsl.CREATE_DROP);
}
private enum AutoDsl {
VALIDATE,
UPDATE,
CREATE,
CREATE_DROP;
}
private void initialize(AutoDsl type, Object[] dsls) {
private void initialize() {
Objects.requireNonNull(usingKeyspace, "please define keyspace by 'use' operator");
Requires.nonNullArray(dsls);
KeyspaceMetadata keyspaceMetadata = getKeyspaceMetadata();
mappingRepository.addEntities(dsls);
for (CasserMappingEntity<?> entity : mappingRepository.getKnownEntities()) {
initializeTable(type, entity);
}
initList.forEach(e -> mappingRepository.addEntity(e));
Map<String, Class<?>> map = collectUserDefinedTypes();
TableOperations tableOps = new TableOperations(this, dropRemovedColumns);
UserTypeOperations userTypeOps = new UserTypeOperations(this);
}
private void initializeTable(AutoDsl type, CasserMappingEntity<?> entity) {
switch(autoDsl) {
if (type == AutoDsl.CREATE || type == AutoDsl.CREATE_DROP) {
createNewTable(entity);
}
else {
TableMetadata tmd = getTableMetadata(entity);
case CREATE:
case CREATE_DROP:
mappingRepository.getKnownEntities()
.forEach(e -> tableOps.createTable(e));
break;
if (type == AutoDsl.VALIDATE) {
if (tmd == null) {
throw new CasserException("table not exists " + entity.getTableName() + "for entity " + entity.getMappingInterface());
}
validateTable(tmd, entity);
}
else if (type == AutoDsl.UPDATE) {
if (tmd == null) {
createNewTable(entity);
}
else {
alterTable(tmd, entity);
}
}
case VALIDATE:
mappingRepository.getKnownEntities()
.forEach(e -> tableOps.validateTable(getTableMetadata(e), e));
break;
case UPDATE:
mappingRepository.getKnownEntities()
.forEach(e -> tableOps.updateTable(getTableMetadata(e), e));
break;
}
if (type == AutoDsl.CREATE_DROP) {
getOrCreateDropEntitiesSet().add(entity);
}
}
private Set<CasserMappingEntity<?>> getOrCreateDropEntitiesSet() {
if (dropEntitiesOnClose == null) {
dropEntitiesOnClose = new HashSet<CasserMappingEntity<?>>();
}
return dropEntitiesOnClose;
private Map<String, Class<?>> collectUserDefinedTypes() {
Map<String, Class<?>> map = new HashMap<String, Class<?>>();
mappingRepository.getKnownEntities().stream()
.flatMap(e -> e.getMappingProperties().stream())
.map(p -> p.getJavaType())
.filter(c -> SimpleDataTypes.getDataTypeByJavaClass(c) == null)
.map(c -> new Tuple2<String, Class<?>>(MappingUtil.getUserDefinedTypeName(c), c))
.filter(t -> t.v1 != null)
.forEach(t -> {
Class<?> old = map.putIfAbsent(t.v1, t.v2);
if (old != null) {
throw new CasserMappingException("found UserDefinedType " + t.v1 + " in two classes " + old + " and " + t.v2);
}
});
return map;
}
private KeyspaceMetadata getKeyspaceMetadata() {
@ -214,30 +231,8 @@ public class SessionInitializer extends AbstractSessionOperations {
return getKeyspaceMetadata().getTable(tableName.toLowerCase());
}
private void createNewTable(CasserMappingEntity<?> entity) {
String cql = SchemaUtil.createTableCql(entity);
execute(cql);
}
private void validateTable(TableMetadata tmd, CasserMappingEntity<?> entity) {
String cql = SchemaUtil.alterTableCql(tmd, entity, dropRemovedColumns);
if (cql != null) {
throw new CasserException("schema changed for entity " + entity.getMappingInterface() + ", apply this command: " + cql);
}
}
private void alterTable(TableMetadata tmd, CasserMappingEntity<?> entity) {
String cql = SchemaUtil.alterTableCql(tmd, entity, dropRemovedColumns);
if (cql != null) {
execute(cql);
}
private UserType getUserType(String name) {
return getKeyspaceMetadata().getUserType(name.toLowerCase());
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (C) 2015 Noorq, Inc.
*
* 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 casser.core;
import casser.mapping.CasserMappingEntity;
import casser.support.CasserException;
import com.datastax.driver.core.TableMetadata;
public final class TableOperations {
private final AbstractSessionOperations sessionOps;
private final boolean dropRemovedColumns;
public TableOperations(AbstractSessionOperations sessionOps, boolean dropRemovedColumns) {
this.sessionOps = sessionOps;
this.dropRemovedColumns = dropRemovedColumns;
}
public void createTable(CasserMappingEntity<?> entity) {
String cql = SchemaUtil.createTableCql(entity);
sessionOps.execute(cql);
}
public void validateTable(TableMetadata tmd, CasserMappingEntity<?> entity) {
if (tmd == null) {
throw new CasserException("table not exists " + entity.getTableName() + "for entity " + entity.getMappingInterface());
}
String cql = SchemaUtil.alterTableCql(tmd, entity, dropRemovedColumns);
if (cql != null) {
throw new CasserException("schema changed for entity " + entity.getMappingInterface() + ", apply this command: " + cql);
}
}
public void updateTable(TableMetadata tmd, CasserMappingEntity<?> entity) {
if (tmd == null) {
createTable(entity);
}
String cql = SchemaUtil.alterTableCql(tmd, entity, dropRemovedColumns);
if (cql != null) {
sessionOps.execute(cql);
}
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (C) 2015 Noorq, Inc.
*
* 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 casser.core;
public final class UserTypeOperations {
private final AbstractSessionOperations sessionOps;
public UserTypeOperations(AbstractSessionOperations sessionOps) {
this.sessionOps = sessionOps;
}
}

View file

@ -17,26 +17,53 @@ package casser.mapping;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.HashMap;
import java.util.Map;
import casser.support.CasserException;
import casser.support.CasserMappingException;
public class CasserMappingRepository {
private ConcurrentMap<Class<?>, CasserMappingEntity<?>> entityMap = new ConcurrentHashMap<Class<?>, CasserMappingEntity<?>>();
private final Map<Class<?>, CasserMappingEntity<?>> entityMap = new HashMap<Class<?>, CasserMappingEntity<?>>();
private ConcurrentMap<String, CasserMappingUserType<?>> udtMap = new ConcurrentHashMap<String, CasserMappingUserType<?>>();
private final Map<String, CasserMappingUserType<?>> udtMap = new HashMap<String, CasserMappingUserType<?>>();
private boolean readOnly = false;
public void addEntities(Object[] dsls) {
public CasserMappingRepository setReadOnly() {
this.readOnly = true;
return this;
}
public void addUserType(String name, Class<?> userTypeClass) {
for (Object dsl : dsls) {
Class<?> iface = MappingUtil.getMappingInterface(dsl);
entityMap.putIfAbsent(iface, new CasserMappingEntity(iface));
if (readOnly) {
throw new CasserException("read-only mode");
}
udtMap.putIfAbsent(name, new CasserMappingUserType(userTypeClass));
}
public Collection<CasserMappingUserType<?>> getKnownUserTypes() {
return Collections.unmodifiableCollection(udtMap.values());
}
public CasserMappingUserType<?> findUserType(Class<?> userTypeClass) {
return udtMap.get(userTypeClass);
}
public void addEntity(Object dsl) {
if (readOnly) {
throw new CasserException("read-only mode");
}
Class<?> iface = MappingUtil.getMappingInterface(dsl);
entityMap.putIfAbsent(iface, new CasserMappingEntity(iface));
}
public Collection<CasserMappingEntity<?>> getKnownEntities() {

View file

@ -28,6 +28,24 @@ public final class MappingUtil {
private MappingUtil() {
}
public static String getUserDefinedTypeName(Class<?> clazz) {
UserDefinedType userDefinedType = clazz.getDeclaredAnnotation(UserDefinedType.class);
if (userDefinedType != null) {
String name = userDefinedType.value();
if (name != null && name.isEmpty()) {
name = null;
}
return name;
}
return null;
}
public static Class<?> getMappingInterface(Object entity) {
Class<?> iface = null;

View file

@ -38,7 +38,7 @@ public class CompondKeyTest extends AbstractEmbeddedCassandraTest {
timeline = Casser.dsl(Timeline.class);
session = Casser.init(getSession()).showCql().createDrop(Timeline.class).get();
session = Casser.init(getSession()).showCql().add(Timeline.class).autoCreateDrop().get();
}
@Test

View file

@ -35,7 +35,7 @@ public class SimpleUserTest extends AbstractEmbeddedCassandraTest {
user = Casser.dsl(User.class);
session = Casser.init(getSession()).showCql().createDrop(User.class).get();
session = Casser.init(getSession()).showCql().add(User.class).autoCreateDrop().get();
}
@Test