diff --git a/README.md b/README.md index 8d290af..6fd7dd3 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/src/main/java/casser/Example.java b/src/main/java/casser/Example.java index 511a0b8..030da38 100644 --- a/src/main/java/casser/Example.java +++ b/src/main/java/casser/Example.java @@ -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 t) { User user = Casser.pojo(User.class); diff --git a/src/main/java/casser/core/AutoDsl.java b/src/main/java/casser/core/AutoDsl.java new file mode 100644 index 0000000..04aa1e5 --- /dev/null +++ b/src/main/java/casser/core/AutoDsl.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/casser/core/CasserSession.java b/src/main/java/casser/core/CasserSession.java index c5e53a8..fff08d5 100644 --- a/src/main/java/casser/core/CasserSession.java +++ b/src/main/java/casser/core/CasserSession.java @@ -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> dropEntitiesOnClose; private final CasserMappingRepository mappingRepository; private final Executor executor; + private final boolean dropSchemaOnClose; CasserSession(Session session, String usingKeyspace, boolean showCql, - Set> 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)); } diff --git a/src/main/java/casser/core/SessionInitializer.java b/src/main/java/casser/core/SessionInitializer.java index 4d233ac..d0871ac 100644 --- a/src/main/java/casser/core/SessionInitializer.java +++ b/src/main/java/casser/core/SessionInitializer.java @@ -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> dropEntitiesOnClose = null; private CasserMappingRepository mappingRepository = new CasserMappingRepository(); @@ -47,6 +50,9 @@ public class SessionInitializer extends AbstractSessionOperations { private KeyspaceMetadata keyspaceMetadata; + private final List initList = new ArrayList(); + 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> 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> getOrCreateDropEntitiesSet() { - if (dropEntitiesOnClose == null) { - dropEntitiesOnClose = new HashSet>(); - } - return dropEntitiesOnClose; + + + private Map> collectUserDefinedTypes() { + + Map> map = new HashMap>(); + + mappingRepository.getKnownEntities().stream() + .flatMap(e -> e.getMappingProperties().stream()) + .map(p -> p.getJavaType()) + .filter(c -> SimpleDataTypes.getDataTypeByJavaClass(c) == null) + .map(c -> new Tuple2>(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()); } } diff --git a/src/main/java/casser/core/TableOperations.java b/src/main/java/casser/core/TableOperations.java new file mode 100644 index 0000000..a88a41f --- /dev/null +++ b/src/main/java/casser/core/TableOperations.java @@ -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); + } + } + +} diff --git a/src/main/java/casser/core/UserTypeOperations.java b/src/main/java/casser/core/UserTypeOperations.java new file mode 100644 index 0000000..18731af --- /dev/null +++ b/src/main/java/casser/core/UserTypeOperations.java @@ -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; + } + +} diff --git a/src/main/java/casser/mapping/CasserMappingRepository.java b/src/main/java/casser/mapping/CasserMappingRepository.java index be95710..1e09a38 100644 --- a/src/main/java/casser/mapping/CasserMappingRepository.java +++ b/src/main/java/casser/mapping/CasserMappingRepository.java @@ -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, CasserMappingEntity> entityMap = new ConcurrentHashMap, CasserMappingEntity>(); + private final Map, CasserMappingEntity> entityMap = new HashMap, CasserMappingEntity>(); - private ConcurrentMap> udtMap = new ConcurrentHashMap>(); + private final Map> udtMap = new HashMap>(); + + 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> 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> getKnownEntities() { diff --git a/src/main/java/casser/mapping/MappingUtil.java b/src/main/java/casser/mapping/MappingUtil.java index 866762f..44adc86 100644 --- a/src/main/java/casser/mapping/MappingUtil.java +++ b/src/main/java/casser/mapping/MappingUtil.java @@ -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; diff --git a/src/test/java/casser/test/integration/core/compound/CompondKeyTest.java b/src/test/java/casser/test/integration/core/compound/CompondKeyTest.java index 1ca1cde..32ea8cc 100644 --- a/src/test/java/casser/test/integration/core/compound/CompondKeyTest.java +++ b/src/test/java/casser/test/integration/core/compound/CompondKeyTest.java @@ -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 diff --git a/src/test/java/casser/test/integration/core/simple/SimpleUserTest.java b/src/test/java/casser/test/integration/core/simple/SimpleUserTest.java index 82c8800..2a5203c 100644 --- a/src/test/java/casser/test/integration/core/simple/SimpleUserTest.java +++ b/src/test/java/casser/test/integration/core/simple/SimpleUserTest.java @@ -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