/** * Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org> * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.seedstack.seed.core.internal.data; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.google.common.collect.Lists; import com.google.inject.Injector; import org.seedstack.seed.DataExporter; import org.seedstack.seed.DataImporter; import org.seedstack.seed.DataManager; import org.seedstack.seed.SeedException; import javax.inject.Inject; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * Implementation of the {@link DataManager}. */ class DataManagerImpl implements DataManager { private static final String UTF_8 = "UTF-8"; private static final String DATA_SET = "dataSet"; private static final String IMPORTER_CLASS = "importerClass"; private static final String GROUP = "group"; private static final String NAME = "name"; private static final String ITEMS = "items"; private static final String CLASSES_MAP_KEY = "%s:%s"; @Inject private Map<String, Map<String, DataExporterDefinition<Object>>> allDataExporters; @Inject private Map<String, Map<String, DataImporterDefinition<Object>>> allDataImporters; @Inject private Injector injector; private final JsonFactory jsonFactory; private final ObjectMapper objectMapper; DataManagerImpl() { this.jsonFactory = new JsonFactory(); this.objectMapper = new ObjectMapper(); this.jsonFactory.setCodec(this.objectMapper); } @Override public void exportData(OutputStream outputStream, String group) { Map<String, DataExporterDefinition<Object>> dataExporterDefinitions = allDataExporters.get(group); if (dataExporterDefinitions == null) { throw SeedException.createNew(DataErrorCode.NO_EXPORTER_FOUND).put(DATA_SET, group); } List<DataSetMarker<Object>> allIterators = new ArrayList<>(); for (DataExporterDefinition<Object> dataExporterDefinition : dataExporterDefinitions.values()) { allIterators.add(new DataSetMarker<>( dataExporterDefinition.getGroup(), dataExporterDefinition.getName(), injector.getInstance(dataExporterDefinition.getDataExporterClass()).exportData() )); } dumpAll(allIterators, outputStream); } @Override public void exportData(OutputStream outputStream, String group, String name) { Map<String, DataExporterDefinition<Object>> dataExporterDefinitionMap = allDataExporters.get(group); if (dataExporterDefinitionMap == null) { throw SeedException.createNew(DataErrorCode.NO_EXPORTER_FOUND).put(DATA_SET, String.format(CLASSES_MAP_KEY, group, name)); } DataExporterDefinition<Object> dataExporterDefinition = dataExporterDefinitionMap.get(name); if (dataExporterDefinition == null) { throw SeedException.createNew(DataErrorCode.NO_EXPORTER_FOUND).put(DATA_SET, String.format(CLASSES_MAP_KEY, group, name)); } dumpAll(Lists.newArrayList(new DataSetMarker( dataExporterDefinition.getGroup(), dataExporterDefinition.getName(), injector.getInstance(dataExporterDefinition.getDataExporterClass()).exportData() )), outputStream); } @Override public void exportData(OutputStream outputStream) { List<DataSetMarker<Object>> dataSets = new ArrayList<>(); for (Map<String, DataExporterDefinition<Object>> dataExporterDefinitionMap : allDataExporters.values()) { for (DataExporterDefinition<Object> dataExporterDefinition : dataExporterDefinitionMap.values()) { DataExporter<Object> dataExporter = injector.getInstance(dataExporterDefinition.getDataExporterClass()); dataSets.add(new DataSetMarker<>(dataExporterDefinition.getGroup(), dataExporterDefinition.getName(), dataExporter.exportData())); } } dumpAll(dataSets, outputStream); } @Override public void importData(InputStream inputStream, String acceptGroup, String acceptName, boolean clear) { Set<DataImporter<Object>> usedDataImporters = new HashSet<>(); try { ParsingState state = ParsingState.START; String group = null; String name = null; JsonParser jsonParser = this.jsonFactory.createParser(new InputStreamReader(inputStream, Charset.forName(UTF_8))); JsonToken jsonToken = jsonParser.nextToken(); while (jsonToken != null) { switch (state) { case START: if (jsonToken == JsonToken.START_ARRAY) { state = ParsingState.DEFINITION_START; } else { throwParsingError(jsonParser.getCurrentLocation(), "start array expected"); } break; case DEFINITION_START: if (jsonToken == JsonToken.START_OBJECT) { state = ParsingState.DEFINITION_GROUP; } else { throwParsingError(jsonParser.getCurrentLocation(), "start object expected"); } break; case DEFINITION_GROUP: if (jsonToken == JsonToken.FIELD_NAME && GROUP.equals(jsonParser.getCurrentName())) { group = jsonParser.nextTextValue(); state = ParsingState.DEFINITION_NAME; } else { throwParsingError(jsonParser.getCurrentLocation(), "group field expected"); } break; case DEFINITION_NAME: if (jsonToken == JsonToken.FIELD_NAME && NAME.equals(jsonParser.getCurrentName())) { name = jsonParser.nextTextValue(); state = ParsingState.DEFINITION_ITEMS; } else { throwParsingError(jsonParser.getCurrentLocation(), "name field expected"); } break; case DEFINITION_ITEMS: if (jsonToken == JsonToken.FIELD_NAME && ITEMS.equals(jsonParser.getCurrentName())) { usedDataImporters.add(consumeItems(jsonParser, group, name, acceptGroup, acceptName)); state = ParsingState.DEFINITION_END; } else { throwParsingError(jsonParser.getCurrentLocation(), "items field expected"); } break; case DEFINITION_END: if (jsonToken == JsonToken.END_OBJECT) { group = null; name = null; state = ParsingState.END; } else { throwParsingError(jsonParser.getCurrentLocation(), "end object expected"); } break; case END: if (jsonToken == JsonToken.START_OBJECT) { state = ParsingState.DEFINITION_GROUP; } else if (jsonToken == JsonToken.END_ARRAY) { state = ParsingState.START; } else { throwParsingError(jsonParser.getCurrentLocation(), "start object or end array expected"); } break; default: throwParsingError(jsonParser.getCurrentLocation(), "unexpected parser state"); } jsonToken = jsonParser.nextToken(); } } catch (Exception e1) { for (DataImporter<Object> usedDataImporter : usedDataImporters) { try { usedDataImporter.rollback(); } catch (Exception e2) { e2.initCause(e1); throw SeedException.wrap(e2, DataErrorCode.FAILED_TO_ROLLBACK_IMPORT) .put(IMPORTER_CLASS, usedDataImporter.getClass().getName()); } } throw SeedException.wrap(e1, DataErrorCode.IMPORT_FAILED); } for (DataImporter<Object> usedDataImporter : usedDataImporters) { try { usedDataImporter.commit(clear); } catch (Exception e) { throw SeedException.wrap(e, DataErrorCode.FAILED_TO_COMMIT_IMPORT) .put(IMPORTER_CLASS, usedDataImporter.getClass().getName()); } } } private void throwParsingError(JsonLocation jsonLocation, String message) { throw SeedException.createNew(DataErrorCode.FAILED_TO_PARSE_DATA_STREAM) .put("parsingError", message) .put("line", jsonLocation.getLineNr()) .put("col", jsonLocation.getColumnNr()) .put("offset", jsonLocation.getCharOffset()); } private DataImporter<Object> consumeItems(JsonParser jsonParser, String group, String name, String acceptGroup, String acceptName) throws IOException { Map<String, DataImporterDefinition<Object>> dataImporterDefinitionMap = allDataImporters.get(group); if (dataImporterDefinitionMap == null) { throw SeedException.createNew(DataErrorCode.NO_IMPORTER_FOUND) .put(GROUP, group) .put(NAME, name); } DataImporterDefinition<Object> currentImporterDefinition = dataImporterDefinitionMap.get(name); if (currentImporterDefinition == null) { throw SeedException.createNew(DataErrorCode.NO_IMPORTER_FOUND) .put(GROUP, group) .put(NAME, name); } if (!group.equals(currentImporterDefinition.getGroup())) { throw SeedException.createNew(DataErrorCode.UNEXPECTED_DATA_TYPE) .put(DATA_SET, String.format(CLASSES_MAP_KEY, group, name)) .put(IMPORTER_CLASS, currentImporterDefinition.getDataImporterClass().getName()); } if (!name.equals(currentImporterDefinition.getName())) { throw SeedException.createNew(DataErrorCode.UNEXPECTED_DATA_TYPE) .put(DATA_SET, String.format(CLASSES_MAP_KEY, group, name)) .put(IMPORTER_CLASS, currentImporterDefinition.getDataImporterClass().getName()); } DataImporter<Object> currentDataImporter = null; if ((acceptGroup == null || acceptGroup.equals(group)) && (acceptName == null || acceptName.equals(name))) { currentDataImporter = injector.getInstance(currentImporterDefinition.getDataImporterClass()); // Check if items contains an array if (jsonParser.nextToken() != JsonToken.START_ARRAY) { throw new IllegalArgumentException("Items should be an array"); } jsonParser.nextToken(); // If the array is not empty consume it if (jsonParser.getCurrentToken() != JsonToken.END_ARRAY) { Iterator<Object> objectIterator = jsonParser.readValuesAs(currentImporterDefinition.getImportedClass()); while (objectIterator.hasNext()) { currentDataImporter.importData(objectIterator.next()); } // The array should end correctly if (jsonParser.getCurrentToken() != JsonToken.END_ARRAY) { throw new IllegalArgumentException("end array expected"); } } } // the data importer containing the data return currentDataImporter; } @Override public boolean isInitialized(String group, String name) { Map<String, DataImporterDefinition<Object>> dataImporterDefinitionMap = allDataImporters.get(group); if (dataImporterDefinitionMap == null) { throw SeedException.createNew(DataErrorCode.NO_IMPORTER_FOUND).put(GROUP, group).put(NAME, name); } DataImporterDefinition<Object> dataImporterDefinition = dataImporterDefinitionMap.get(name); if (dataImporterDefinition == null) { throw SeedException.createNew(DataErrorCode.NO_IMPORTER_FOUND).put(GROUP, group).put(NAME, name); } DataImporter<Object> dataImporter = injector.getInstance(dataImporterDefinition.getDataImporterClass()); return dataImporter.isInitialized(); } private void dumpAll(List<DataSetMarker<Object>> dataSetMarker, OutputStream outputStream) { try { JsonGenerator jsonGenerator = this.jsonFactory.createGenerator(new OutputStreamWriter(outputStream, Charset.forName(UTF_8))); ObjectWriter objectWriter = objectMapper.writer(); jsonGenerator.writeStartArray(); for (DataSetMarker<Object> objectDataSetMarker : dataSetMarker) { // start jsonGenerator.writeStartObject(); // metadata jsonGenerator.writeStringField(GROUP, objectDataSetMarker.getGroup()); jsonGenerator.writeStringField(NAME, objectDataSetMarker.getName()); // items jsonGenerator.writeArrayFieldStart(ITEMS); while (objectDataSetMarker.getItems().hasNext()) { objectWriter.writeValue(jsonGenerator, objectDataSetMarker.getItems().next()); } jsonGenerator.writeEndArray(); // end jsonGenerator.writeEndObject(); } jsonGenerator.writeEndArray(); jsonGenerator.flush(); } catch (Exception e) { throw SeedException.wrap(e, DataErrorCode.EXPORT_FAILED); } } private enum ParsingState { START, DEFINITION_START, DEFINITION_GROUP, DEFINITION_NAME, DEFINITION_ITEMS, DEFINITION_END, END } }