/*
* Copyright 2013 Gordon Burgett and individual contributors
*
* 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.xflatdb.xflat.db;
import java.io.File;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.xflatdb.xflat.DatabaseConfig;
import org.xflatdb.xflat.TableConfig;
import org.xflatdb.xflat.XFlatConstants;
import org.xflatdb.xflat.XFlatException;
import org.xflatdb.xflat.convert.ConversionException;
import org.xflatdb.xflat.util.DocumentFileWrapper;
/**
* A Factory which creates {@link TableMetadata} instances.
* @author Gordon
*/
public class TableMetadataFactory {
private XFlatDatabase db;
private DatabaseConfig dbConfig;
private DocumentFileWrapper wrapper;
/**
* Creates a new TableMetadataFactory with the given dependencies.
* @param db The XFlatDatabase from which dependencies are retrieved when engines are provided.
* @param metadataDirectory The metadata directory to which the factory should read and save table metadata.
*/
public TableMetadataFactory(XFlatDatabase db, File metadataDirectory){
this(db, db.getConfig(), new DocumentFileWrapper(metadataDirectory));
}
/**
* Creates a new TableMetadataFactory with the given dependencies.
* @param db The XFlatDatabase from which dependencies are retrieved when engines are provided.
* @param config The XFlat Database configuration.
* @param wrapper A Wrapper around the metadata directory allowing the factory to read and save table metadata.
*/
TableMetadataFactory(XFlatDatabase db, DatabaseConfig config, DocumentFileWrapper wrapper){
this.db = db;
this.dbConfig= config;
this.wrapper = wrapper;
}
/**
* Creates a new TableMetadataFactory with no dependencies.
*/
TableMetadataFactory(){
}
/**
* Gets the metadata document for the given table name.
* @param name The table name for which to get the metadata document.
* @return A Document representing the metadata associated to the table.
*/
public Document getMetadataDoc(String name){
try {
return this.wrapper.readFile(name + ".config.xml");
} catch (IOException | JDOMException ex) {
Log log = LogFactory.getLog(getClass());
if(log.isInfoEnabled())
log.info(String.format("corrupt metadata file: %s.config.xml in directory %s", name, this.wrapper.toString()), ex);
return null;
}
}
/**
* Creates a TableMetadata for the given engine. This TableMetadata object
* does not have the ID generator or table config, so it can function only as
* an EngineProvider. Do not call {@link TableMetadata#getTable(java.lang.Class) } on the
* resulting object.
* @param name The name of the table.
* @param engineFile The file where the engine data should be stored.
* @return A new TableMetadata object.
*/
public TableMetadata makeTableMetadata(String name, File engineFile){
Document doc;
try {
doc = this.wrapper.readFile(name + ".config.xml");
} catch (IOException | JDOMException ex) {
Log log = LogFactory.getLog(getClass());
if(log.isInfoEnabled())
log.info(String.format("corrupt metadata file: %s.config.xml in directory %s", name, this.wrapper.toString()), ex);
doc = null;
}
TableMetadata ret = new TableMetadata(name, db, engineFile);
if(doc == null){
//no need for config or ID generator
ret.engineMetadata = new Element("engine", XFlatConstants.xFlatNs);
}
else{
//load engine
ret.engineMetadata = doc.getRootElement().getChild("engine", XFlatConstants.xFlatNs);
if(ret.engineMetadata == null){
ret.engineMetadata = new Element("engine", XFlatConstants.xFlatNs);
}
}
return ret;
}
/**
* Creates a TableMetadata for the given table information.
* @param name The name of the table
* @param engineFile The file locating the engine.
* @param config The configuration of the table, null to use {@link TableConfig#DEFAULT}
* @param idType The type of the ID property for the table.
* @return A table metadata for the given table.
*/
public TableMetadata makeTableMetadata(String name, File engineFile, TableConfig config, Class<?> idType){
Document doc;
TableMetadata ret;
try {
doc = this.wrapper.readFile(name + ".config.xml");
} catch (IOException | JDOMException ex) {
Log log = LogFactory.getLog(getClass());
if(log.isInfoEnabled())
log.info(String.format("regenerating corrupt metadata file: %s.config.xml in directory %s", name, this.wrapper.toString()), ex);
doc = null;
}
if(doc == null){
ret = makeNewTableMetadata(name, engineFile, config, idType);
}
else{
ret = makeTableMetadataFromDocument(name, engineFile, doc, config, idType);
}
return ret;
}
private TableMetadata makeNewTableMetadata(String name, File engineFile, TableConfig config, Class<?> idType){
TableMetadata ret = new TableMetadata(name, db, engineFile);
config = config == null ? new TableConfig() : config;
ret.config = config;
//make ID Generator
Class<? extends IdGenerator> generatorClass = config.getIdGenerator();
if(generatorClass != null){
ret.idGenerator = makeIdGenerator(generatorClass);
if(idType != null && !ret.idGenerator.supports(idType)){
throw new XFlatException("Id Generator " + generatorClass.getName() +
" does not support type " + idType);
}
}
else {
//pick using our strategy
if(idType != null)
for(Class<? extends IdGenerator> g : dbConfig.getIdGeneratorStrategy()){
IdGenerator gen = makeIdGenerator(g);
if(gen.supports(idType)){
ret.idGenerator = gen;
break;
}
}
if(ret.idGenerator == null){
throw new XFlatException("Could not pick id generator for type " + idType);
}
}
ret.engineMetadata = new Element("engine", XFlatConstants.xFlatNs);
return ret;
}
private TableMetadata makeTableMetadataFromDocument(String name, File engineFile, Document metadata, TableConfig config, Class<?> idType){
TableMetadata ret = new TableMetadata(name, db, engineFile);
if(config == null){
Element c = metadata.getRootElement().getChild("config", XFlatConstants.xFlatNs);
try {
config = TableConfig.FromElementConverter.convert(c);
} catch (ConversionException ex) {
throw new XFlatException("Cannot deserialize metadata for table " + name, ex);
}
}
//else we already verified that config was equal to that stored in metadata
ret.config = config;
//load ID generator
Class<? extends IdGenerator> generatorClass = null;
Element g = metadata.getRootElement().getChild("generator", XFlatConstants.xFlatNs);
if(g != null){
String gClassStr = g.getAttributeValue("class", XFlatConstants.xFlatNs);
if(gClassStr != null){
try {
generatorClass = (Class<? extends IdGenerator>) TableMetadata.class.getClassLoader().loadClass(gClassStr);
} catch (ClassNotFoundException ex) {
throw new XFlatException("Cannot load metadata: generator class could not be loaded", ex);
}
}
}
ret.idGenerator = makeIdGenerator(generatorClass);
if(idType != null && !ret.idGenerator.supports(idType)){
throw new XFlatException("Id Generator " + generatorClass + " does not support " +
" ID type " + idType);
}
ret.idGenerator.loadState(g);
//load engine
ret.engineMetadata = metadata.getRootElement().getChild("engine", XFlatConstants.xFlatNs);
if(ret.engineMetadata == null){
ret.engineMetadata = new Element("engine", XFlatConstants.xFlatNs);
}
return ret;
}
/**
* Saves the given metadata to disk.
* @param metadata The metadata to save.
* @throws IOException
*/
public void saveTableMetadata(TableMetadata metadata) throws IOException {
Document doc = new Document();
doc.setRootElement(new Element("metadata", XFlatConstants.xFlatNs));
//save config
if(metadata.config != null){
Element cfg;
try {
cfg = TableConfig.ToElementConverter.convert(metadata.config);
} catch (ConversionException ex) {
throw new XFlatException("Cannot serialize table metadata", ex);
}
doc.getRootElement().addContent(cfg);
}
//save generator
if(metadata.idGenerator != null){
Element g= new Element("generator", XFlatConstants.xFlatNs);
g.setAttribute("class", metadata.idGenerator.getClass().getName(), XFlatConstants.xFlatNs);
metadata.idGenerator.saveState(g);
doc.getRootElement().addContent(g);
}
//save engine
Element e = metadata.engineMetadata.clone();
doc.getRootElement().addContent(e);
this.wrapper.writeFile(metadata.name + ".config.xml", doc);
}
private IdGenerator makeIdGenerator(Class<? extends IdGenerator> generatorClass){
if(generatorClass == null){
throw new XFlatException("generator class could not be loaded");
}
try {
return generatorClass.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
throw new XFlatException("Cannot load metadata: generator class could not be instantiated", ex);
}
}
}