package dgm.configuration.javascript;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import dgm.configuration.FixtureConfiguration;
import dgm.configuration.FixtureIndexConfiguration;
import dgm.configuration.FixtureTypeConfiguration;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A Fixture config contains a number of mappings of index names to index configurations.
* This class also knows how to a config directory into this object tree.
* <p/>
* This is how your file/directory structure should look, for this code to work.
* The root dir is the 'fixtures' dir in the config directory.
* <pre>
* /[index name]/
* [type name]/
* mapping.json (type mapping)
* [id].json (document)
* </pre>
* <p/>
* All json files are parsed and errors are reported with the names of the problematic files, and the cause of the problem.
*
* @author Ernst Bunders
*/
public class JavascriptFixtureConfiguration implements FixtureConfiguration
{
public static final String MAPPING_FILE_NAME = "mapping.json";
public static final String DATA_DIR = "data";
public static final String RESULTS_DIR = "results";
public static final String EXPECTED_DIR = "expected";
private Map<String, JavascriptFixtureIndexConfiguration> indexConfigs = new LinkedHashMap<String, JavascriptFixtureIndexConfiguration>();
private Map<String, JavascriptFixtureIndexConfiguration> expectedConfigs = new LinkedHashMap<String, JavascriptFixtureIndexConfiguration>();
private File resultsDir = null;
private static final Logger log = LoggerFactory.getLogger(JavascriptFixtureConfiguration.class);
public JavascriptFixtureConfiguration(File configDir) throws IOException
{
try
{
resultsDir = new File(configDir, RESULTS_DIR);
for (File directory : configDir.listFiles(FixtureUtil.onlyDirsFilter()))
{
if (DATA_DIR.equals(directory.getName()))
{
for (File indexDir : directory.listFiles(FixtureUtil.onlyDirsFilter()))
{
indexConfigs.put(indexDir.getName(), new JavascriptFixtureIndexConfiguration(indexDir));
}
}
if (EXPECTED_DIR.equals(directory.getName()))
{
for (File expectedDir : directory.listFiles(FixtureUtil.onlyDirsFilter()))
{
expectedConfigs.put(expectedDir.getName(), new JavascriptFixtureIndexConfiguration(expectedDir));
}
}
}
} catch (FixtureConfigurationException e)
{
log.error("Could not parse fixture data. Illegal json: " + e.getMessage());
throw e.getJpe();
}
}
public Iterable<String> getIndexNames()
{
return indexConfigs.keySet();
}
public FixtureIndexConfiguration getIndexConfig(String name)
{
return indexConfigs.get(name);
}
public Iterable<String> getExpectedIndexNames()
{
return expectedConfigs.keySet();
}
public FixtureIndexConfiguration getExpectedIndexConfig(String name)
{
return expectedConfigs.get(name);
}
public File getResultsDirectory() {
return resultsDir;
}
@Override
public String toString()
{
return "Fixture configuration. Indexes: " + indexConfigs.toString();
}
}
/**
* An index config contains a number of mappings of type names to type configurations.
*/
class JavascriptFixtureIndexConfiguration implements FixtureIndexConfiguration
{
private Map<String, FixtureTypeConfiguration> typeConfigs = new LinkedHashMap<String, FixtureTypeConfiguration>();
JavascriptFixtureIndexConfiguration(File configDir) throws IOException
{
for (File typeDir : configDir.listFiles(FixtureUtil.onlyDirsFilter()))
typeConfigs.put(typeDir.getName(), new JavascriptFixtureTypeConfiguration(typeDir));
}
public Iterable<String> getTypeNames()
{
return typeConfigs.keySet();
}
public FixtureTypeConfiguration getTypeConfig(String name)
{
return typeConfigs.get(name);
}
@Override
public Iterable<FixtureTypeConfiguration> getTypeConfigurations()
{
return (Iterable<FixtureTypeConfiguration>) typeConfigs.values();
}
@Override
public String toString()
{
return " index with types:" + typeConfigs.toString();
}
}
/**
* The type config contains a number of documents, and
* optionally an ElasticSearch index mapping for this type.
*/
class JavascriptFixtureTypeConfiguration implements FixtureTypeConfiguration
{
private final JsonNode mapping;
private final Map<String, JsonNode> documentsById = new LinkedHashMap<String, JsonNode>();
JavascriptFixtureTypeConfiguration(File configDir) throws IOException
{
mapping = resolveMapping(configDir);
readDocuments(configDir);
}
public JsonNode getMapping()
{
return mapping;
}
public Iterable<JsonNode> getDocuments()
{
return documentsById.values();
}
public Iterable<String> getDocumentIds()
{
return documentsById.keySet();
}
public JsonNode getDocumentById(String id)
{
return documentsById.get(id);
}
@Override
public boolean hasDocuments()
{
return documentsById.size() > 0;
}
/**
* Read all the documents, create JsonNode instances for them, and map them to their id's
*
* @param configDir the Type config dir
* @throws IOException When all else fails.
*/
private void readDocuments(File configDir) throws IOException
{
for (File file : configDir.listFiles(new FilenameFilter()
{
@Override
public boolean accept(File dir, String name)
{
return (!JavascriptFixtureConfiguration.MAPPING_FILE_NAME.equals(name)) && name.endsWith(".json");
}
}))
{
try
{
documentsById.put(
file.getName().replaceFirst(".json", ""),
FixtureUtil.mapper.readTree(FileUtils.readFileToString(file))
);
} catch (JsonProcessingException e)
{
throw new FixtureConfigurationException(e, file);
}
}
}
/**
* Reads the mapping for this type, if there is one.
*
* @param configDir the Type config dir
* @return The node created for this document.
* @throws IOException
*/
private JsonNode resolveMapping(File configDir) throws IOException
{
File mf = new File(configDir, JavascriptFixtureConfiguration.MAPPING_FILE_NAME);
if (mf.exists() && mf.canRead())
try
{
return FixtureUtil.mapper.readTree(FileUtils.readFileToString(mf));
} catch (JsonProcessingException e)
{
throw new FixtureConfigurationException(e, mf);
}
return null;
}
@Override
public String toString()
{
return " type that has: " + documentsById.keySet().size() + " documents" + (mapping != null ? " and index mapping" : "");
}
}
class FixtureConfigurationException extends RuntimeException
{
private JsonProcessingException jpe;
FixtureConfigurationException(JsonProcessingException jpe, File cause1)
{
super("Could not parse json file [" + cause1.getAbsolutePath() + "] because of:" + jpe.getMessage(), jpe);
this.jpe = jpe;
}
public JsonProcessingException getJpe()
{
return jpe;
}
}
class FixtureUtil
{
private FixtureUtil()
{
}
public static FileFilter onlyDirsFilter()
{
return new FileFilter()
{
@Override
public boolean accept(File pathname)
{
return pathname.isDirectory();
}
};
}
public static final ObjectMapper mapper = new ObjectMapper();
}