/*
* Copyright (C) 2012-2016 DuyHai DOAN
*
* 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 info.archinnov.achilles.schema;
import static com.datastax.driver.core.ProtocolVersion.NEWEST_SUPPORTED;
import static com.google.common.collect.Sets.newHashSet;
import static info.archinnov.achilles.internals.parser.TypeUtils.ENTITY_META_PACKAGE;
import static info.archinnov.achilles.internals.parser.TypeUtils.GENERATED_PACKAGE;
import static info.archinnov.achilles.internals.parser.TypeUtils.UDT_META_PACKAGE;
import static info.archinnov.achilles.validation.Validator.validateNotBlank;
import static info.archinnov.achilles.validation.Validator.validateTrue;
import static java.lang.String.format;
import static java.util.stream.Collectors.toList;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.datastax.driver.core.CodecRegistry;
import com.google.common.collect.Sets;
import info.archinnov.achilles.exception.AchillesException;
import info.archinnov.achilles.internals.factory.TupleTypeFactory;
import info.archinnov.achilles.internals.factory.UserTypeFactory;
import info.archinnov.achilles.internals.metamodel.AbstractEntityProperty;
import info.archinnov.achilles.internals.metamodel.AbstractUDTClassProperty;
import info.archinnov.achilles.internals.metamodel.AbstractViewProperty;
import info.archinnov.achilles.internals.schema.SchemaContext;
import info.archinnov.achilles.type.tuples.Tuple2;
public class SchemaGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger(SchemaGenerator.class);
private static final CodecRegistry CODEC_REGISTRY = new CodecRegistry();
private static final TupleTypeFactory TUPLE_TYPE_FACTORY = new TupleTypeFactory(NEWEST_SUPPORTED, CODEC_REGISTRY);
private static final UserTypeFactory USER_TYPE_FACTORY = new UserTypeFactory(NEWEST_SUPPORTED, CODEC_REGISTRY);
private static final Comparator<Tuple2<String, Class<AbstractEntityProperty<?>>>> BY_NAME_ENTITY_CLASS_SORTER =
(o1, o2) -> o1._1().compareTo(o2._1());
private static final Comparator<Tuple2<String, Class<AbstractUDTClassProperty<?>>>> BY_NAME_UDT_CLASS_SORTER =
(o1, o2) -> o1._1().compareTo(o2._1());
private Optional<String> keyspace = Optional.empty();
private boolean createIndex = true;
private boolean createUdt = true;
private SchemaGenerator(String keyspaceName) {
this.keyspace = Optional.ofNullable(keyspaceName);
}
public static void main(String... args) throws IOException {
if (args == null || args.length != 4) {
System.out.println(displayUsage());
} else {
final String targetFile = args[1];
final String keyspaceName = args[3];
final Path path = new File(targetFile).toPath();
Files.deleteIfExists(path);
Files.createFile(path);
final SchemaGenerator generator = new SchemaGenerator(keyspaceName);
generator.createIndex = true;
generator.createUdt = true;
generator.generateTo(path);
}
}
private static String displayUsage() {
StringBuilder builder = new StringBuilder();
builder.append("*********************************************************************************************************************************************************************\n");
builder.append("\n");
builder.append("Usage for Schema Generator : \n");
builder.append("\n");
builder.append("java -cp ./your_compiled_entities.jar:./achilles-schema-generator-<version>-shaded.jar info.archinnov.achilles.schema.SchemaGenerator -target <schema_file> -keyspace <keyspace_name> \n");
builder.append("\n");
builder.append("*********************************************************************************************************************************************************************\n");
return builder.toString();
}
public static SchemaGenerator builder() {
return new SchemaGenerator(null);
}
public SchemaGenerator withKeyspace(String keyspace) {
validateNotBlank(keyspace, "Provided keyspace for SchemaGenerator should not be blank");
this.keyspace = Optional.of(keyspace);
return this;
}
public SchemaGenerator generateCustomTypes(boolean generateCustomTypes) {
this.createUdt = generateCustomTypes;
return this;
}
public SchemaGenerator withCustomTypes() {
this.createUdt = true;
return this;
}
public SchemaGenerator withoutCustomTypes() {
this.createUdt = false;
return this;
}
public SchemaGenerator generateIndices(boolean generateIndices) {
this.createIndex = generateIndices;
return this;
}
public SchemaGenerator withIndices() {
this.createIndex = true;
return this;
}
public SchemaGenerator withoutIndices() {
this.createIndex = false;
return this;
}
@SuppressWarnings("")
public String generate() {
LOGGER.info("Start generating schema file ");
validateNotBlank(keyspace.orElse(""), "Keyspace should be provided to generate schema");
final SchemaContext context = new SchemaContext(keyspace.get(), createUdt, createIndex);
ReflectionsHelper.registerUrlTypes(".mar", ".jnilib", ".zip");
Reflections reflections = new Reflections(newHashSet(ENTITY_META_PACKAGE, UDT_META_PACKAGE), this.getClass().getClassLoader());
StringBuilder builder = new StringBuilder();
final List<AbstractEntityProperty<?>> entityMetas = reflections
.getSubTypesOf(AbstractEntityProperty.class)
.stream()
.map(x -> Tuple2.of(x.getCanonicalName(), (Class<AbstractEntityProperty<?>>) x))
.sorted(BY_NAME_ENTITY_CLASS_SORTER)
.map(x -> x._2())
.map(SchemaGenerator::newInstanceForEntityProperty)
.filter(x -> x != null)
.collect(toList());
//Inject keyspace to entity metas
entityMetas.forEach(x -> x.injectKeyspace(keyspace.get()));
LOGGER.info(format("Found %s entity meta classes", entityMetas.size()));
//Generate UDT BEFORE tables
if (context.createUdt) {
LOGGER.info(format("Generating schema for UDT"));
final List<AbstractUDTClassProperty<?>> udtMetas = reflections
.getSubTypesOf(AbstractUDTClassProperty.class)
.stream()
.map(x -> Tuple2.of(x.getCanonicalName(), (Class<AbstractUDTClassProperty<?>>) x))
.sorted(BY_NAME_UDT_CLASS_SORTER)
.map(x -> x._2())
.map(SchemaGenerator::newInstanceForUDTProperty)
.filter(x -> x != null)
.collect(toList());
LOGGER.info(format("Found %s udt classes", udtMetas.size()));
for (AbstractUDTClassProperty<?> instance : udtMetas) {
instance.injectKeyspace(keyspace.get());
instance.inject(USER_TYPE_FACTORY, TUPLE_TYPE_FACTORY);
builder.append(instance.generateSchema(context));
}
}
final long viewCount = entityMetas
.stream()
.filter(AbstractEntityProperty::isView)
.count();
//Inject base table property into view property
if (viewCount > 0) {
final Map<Class<?>, AbstractEntityProperty<?>> entityPropertiesMap = entityMetas
.stream()
.filter(AbstractEntityProperty::isTable)
.collect(Collectors.toMap(x -> x.entityClass, x -> x));
entityMetas
.stream()
.filter(AbstractEntityProperty::isView)
.map(x -> (AbstractViewProperty<?>)x)
.forEach(x -> x.setBaseClassProperty(entityPropertiesMap.get(x.getBaseEntityClass())));
}
for (AbstractEntityProperty<?> instance : entityMetas) {
instance.inject(USER_TYPE_FACTORY, TUPLE_TYPE_FACTORY);
builder.append(instance.generateSchema(context));
}
return builder.toString();
}
public void generateTo(Appendable appendable) throws IOException {
final String schemaString = generate();
appendable.append(schemaString);
}
public void generateTo(Path file) throws IOException {
validateTrue(Files.exists(file), "'%s' should exist", file.toString());
validateTrue(Files.isWritable(file), "'%s' should have write permission", file.toString());
Writer writer = new OutputStreamWriter(Files.newOutputStream(file));
generateTo(writer);
writer.flush();
writer.close();
}
public void generateTo(File file) throws IOException {
generateTo(file.toPath());
}
public static AbstractEntityProperty<?> newInstanceForEntityProperty(Class<? extends AbstractEntityProperty<?>> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public static AbstractUDTClassProperty<?> newInstanceForUDTProperty(Class<? extends AbstractUDTClassProperty<?>> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}