/** * *************************************************************************** * Copyright (c) 2010 Qcadoo Limited * Project: Qcadoo Framework * Version: 1.2.0 * * This file is part of Qcadoo. * * Qcadoo is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation; either version 3 of the License, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************** */ package com.qcadoo.mes.integration.sfcSimple.parser; import static com.qcadoo.mes.integration.sfcSimple.constants.SfcSimpleConstants.FIELD_TYPE; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.apache.poi.util.IOUtils; import org.springframework.stereotype.Service; import com.qcadoo.mes.integration.cfcSimple.importer.model.ParsedOrder; import com.qcadoo.mes.integration.cfcSimple.importer.model.ParsedOrderItem; import com.qcadoo.mes.integration.cfcSimple.importer.model.ParsedProduct; import com.qcadoo.mes.integration.cfcSimple.importer.parser.IntegrationParseMessages; import com.qcadoo.mes.integration.cfcSimple.importer.parser.IntegrationParser; import com.qcadoo.mes.integration.cfcSimple.importer.parser.IntegrationParserException; import com.qcadoo.mes.integration.cfcSimple.importer.parser.IntegrationParserResult; import com.qcadoo.mes.integration.cfcSimple.importer.parser.IntegrationParserUtils; @Service public class EppParser implements IntegrationParser { private enum ParserState { INIT, AFTER_INFO_HEADER, EXPECT_HEADER_NAGLOWEK, EXPECT_HEADER_NAGLOWEK_CONTENT, EXPECT_HEADER_ZAWARTOSC, EXPECT_HEADER_ZAWARTOSC_CONTENT }; private enum ParserContentType { IGNORE, ORDER, PRODUCT }; private enum LineParserState { BEFORE_ARGUMENT, READING_ARGUMENT, READING_COMA_ARGUMENT, EXPECTING_END_COMA_ARGUMENT, END_ARGUMENT }; public enum ProductType { ARTICLE, SERVICE, PACK, SET }; public IntegrationParserResult parse(final InputStream stream) throws IntegrationParserException { int currentEncoding = 1250; byte[] bytes = getByteArray(stream); BufferedReader reader = createReader(bytes, currentEncoding); String streamLine; ParserState currentState = ParserState.INIT; ParserContentType contentType = ParserContentType.IGNORE; IntegrationParserResult result = new IntegrationParserResult(); ParsedOrder currentOrder = new ParsedOrder(); int lineNumber = 0; try { while ((streamLine = reader.readLine()) != null) { lineNumber++; streamLine = streamLine.trim(); if ("".equals(streamLine)) { continue; } switch (currentState) { case INIT: checkHeader(streamLine, "INFO"); currentState = ParserState.AFTER_INFO_HEADER; break; case AFTER_INFO_HEADER: int encoding = validateInfoLine(streamLine); if (encoding == currentEncoding) { currentState = ParserState.EXPECT_HEADER_NAGLOWEK; } else { currentEncoding = encoding; reader = createReader(bytes, currentEncoding); currentState = ParserState.INIT; } break; case EXPECT_HEADER_NAGLOWEK: checkHeader(streamLine, "NAGLOWEK"); currentState = ParserState.EXPECT_HEADER_NAGLOWEK_CONTENT; break; case EXPECT_HEADER_NAGLOWEK_CONTENT: String[] infoLine = readDataLine(streamLine); if (infoLine.length == 1 && "TOWARY".equals(infoLine[0])) { contentType = ParserContentType.PRODUCT; } else if (infoLine.length > 1 && "ZK".equals(infoLine[0])) { currentOrder = parseOrder(streamLine); result.addParsedOrder(currentOrder); contentType = ParserContentType.ORDER; } else { contentType = ParserContentType.IGNORE; } currentState = ParserState.EXPECT_HEADER_ZAWARTOSC; break; case EXPECT_HEADER_ZAWARTOSC: checkHeader(streamLine, "ZAWARTOSC"); currentState = ParserState.EXPECT_HEADER_ZAWARTOSC_CONTENT; break; case EXPECT_HEADER_ZAWARTOSC_CONTENT: if (isHeader(streamLine)) { checkHeader(streamLine, "NAGLOWEK"); currentState = ParserState.EXPECT_HEADER_NAGLOWEK_CONTENT; } else { if (ParserContentType.PRODUCT.equals(contentType)) { result.addParsedProduct(parseProduct(streamLine)); } else if (ParserContentType.ORDER.equals(contentType)) { currentOrder.addItem(parseOrderItem(streamLine)); } } break; default: throw new IllegalStateException("Incorrect state"); } } if (!ParserState.EXPECT_HEADER_NAGLOWEK.equals(currentState) && !ParserState.EXPECT_HEADER_ZAWARTOSC_CONTENT.equals(currentState)) { throw IntegrationParserException .createParseError(IntegrationParseMessages.UNEXPECTED_END_OF_FILE.getMessageKey()); } stream.close(); performFinalValidation(result); } catch (IntegrationParserException e) { throw IntegrationParserException.createParseErrorOnLine(lineNumber, e, e.getMessageKey(), e.getMessageArgs()); } catch (IOException e) { throw IntegrationParserException.createParseError(e, IntegrationParseMessages.IO_EXCEPTION.getMessageKey(), e.getMessage()); } return result; } private byte[] getByteArray(final InputStream stream) throws IntegrationParserException { try { return IOUtils.toByteArray(stream); } catch (IOException e) { throw IntegrationParserException.createParseError(e, IntegrationParseMessages.IO_EXCEPTION.getMessageKey(), e.getMessage()); } } private BufferedReader createReader(final byte[] bytes, final int encoding) throws IntegrationParserException { String encodingName = null; if (encoding == 1250) { encodingName = "windows-1250"; } else if (encoding == 852) { encodingName = "ISO-8859-2"; } else { throw IntegrationParserException.createParseError(IntegrationParseMessages.ENCODING_NOT_SUPPORTED.getMessageKey(), encoding); } try { return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes), encodingName)); } catch (UnsupportedEncodingException e) { throw new IllegalStateException("Epp file IO exception: UnsupportedEncodingException", e); } } private void checkHeader(final String line, final String expectedheader) throws IntegrationParserException { if (!expectedheader.equals(readHeader(line))) { throw IntegrationParserException.createParseError(IntegrationParseMessages.HEADER_VALUE_EXPECTED.getMessageKey(), expectedheader); } } private boolean isHeader(final String line) { return line.charAt(0) == '[' && line.charAt(line.length() - 1) == ']'; } private String readHeader(final String line) throws IntegrationParserException { if (line.charAt(0) != '[' || line.charAt(line.length() - 1) != ']') { throw IntegrationParserException.createParseError(IntegrationParseMessages.HEADER_EXPECTED.getMessageKey()); } return line.substring(1, line.length() - 1); } private int validateInfoLine(final String line) throws IntegrationParserException { String[] infoLine = readDataLine(line); IntegrationParserUtils.checkParametersNumber(infoLine, "info", 24); if (IntegrationParserUtils.parseBigDecimal(infoLine[0]).compareTo(new BigDecimal("1.05")) < 0) { throw IntegrationParserException .createParseError(IntegrationParseMessages.FILE_VERSION_NOT_SUPPORTED.getMessageKey()); } return IntegrationParserUtils.parseInteger(infoLine[2]); } private void performFinalValidation(final IntegrationParserResult parseResult) throws IntegrationParserException { Set<String> definedProducts = new HashSet<String>(); for (ParsedProduct product : parseResult.getProducts()) { definedProducts.add(product.getIdentificationCode()); } for (ParsedOrder order : parseResult.getOrders()) { for (ParsedOrderItem item : order.getItems()) { if (!definedProducts.contains(item.getProductIdentificationCode())) { throw IntegrationParserException.createParseError( IntegrationParseMessages.PRODUCT_NOT_DEFINED.getMessageKey(), item.getProductIdentificationCode()); } } } } private ParsedProduct parseProduct(final String line) throws IntegrationParserException { String[] infoLine = readDataLine(line); IntegrationParserUtils.checkParametersNumber(infoLine, "product", 42); IntegrationParserUtils.checkParameterLength(infoLine[0], "product type", 1, true); IntegrationParserUtils.checkParameterLength(infoLine[1], "product identification code", 20, true); IntegrationParserUtils.checkParameterLength(infoLine[3], "product ean", 50, false); IntegrationParserUtils.checkParameterLength(infoLine[4], "product name", 100, false); IntegrationParserUtils.checkParameterLength(infoLine[5], "product description", 255, false); IntegrationParserUtils.checkParameterLength(infoLine[9], "product unit", 50, true); ParsedProduct product = new ParsedProduct(); int integerProductType = IntegrationParserUtils.parseInteger(infoLine[0]); switch (integerProductType) { case 1: product.setField(FIELD_TYPE, ProductType.ARTICLE.toString()); break; case 2: product.setField(FIELD_TYPE, ProductType.SERVICE.toString()); break; case 4: product.setField(FIELD_TYPE, ProductType.PACK.toString()); break; case 8: product.setField(FIELD_TYPE, ProductType.SET.toString()); break; default: throw IntegrationParserException.createParseError(IntegrationParseMessages.UNKNOWN_PRODUCT_TYPE.getMessageKey(), integerProductType); } product.setIdentificationCode(infoLine[1]); product.setField("ean", infoLine[3]); product.setField("name", infoLine[4]); product.setField("description", infoLine[5]); product.setField("unit", infoLine[9]); return product; } private ParsedOrder parseOrder(final String line) throws IntegrationParserException { String[] infoLine = readDataLine(line); IntegrationParserUtils.checkParametersNumber(infoLine, "order", 62); IntegrationParserUtils.checkParameterLength(infoLine[6], "order number", 30, true); IntegrationParserUtils.checkParameterLength(infoLine[12], "order client name", 50, false); IntegrationParserUtils.checkParameterLength(infoLine[14], "order client city", 50, false); IntegrationParserUtils.checkParameterLength(infoLine[15], "order client post address", 50, false); IntegrationParserUtils.checkParameterLength(infoLine[16], "order client address", 100, false); IntegrationParserUtils.checkParameterLength(infoLine[17], "order client NIP number", 50, false); ParsedOrder order = new ParsedOrder(); order.setField("number", "ZK " + infoLine[6]); order.setField("clientName", infoLine[12]); order.setField("clientAddress", infoLine[16] + ",\n" + infoLine[15] + " " + infoLine[14] + ",\n" + infoLine[17]); IntegrationParserUtils.parseDate(infoLine[21]); IntegrationParserUtils.parseDate(infoLine[23]); order.setField("drawDate", infoLine[21]); order.setField("realizationDate", infoLine[23]); return order; } private ParsedOrderItem parseOrderItem(final String line) throws IntegrationParserException { String[] infoLine = readDataLine(line); IntegrationParserUtils.checkParametersNumber(infoLine, "order item", 22); IntegrationParserUtils.checkParameterLength(infoLine[2], "order item product identification code", 20, true); ParsedOrderItem orderItem = new ParsedOrderItem(); orderItem.setProductIdentificationCode(infoLine[2]); IntegrationParserUtils.parseInteger(infoLine[0]); IntegrationParserUtils.parseBigDecimal(infoLine[10]); orderItem.setField("number", infoLine[0]); orderItem.setField("quantity", infoLine[10]); return orderItem; } private String[] readDataLine(final String line) throws IntegrationParserException { StringBuilder currentWord = new StringBuilder(); List<String> words = new LinkedList<String>(); LineParserState state = LineParserState.BEFORE_ARGUMENT; int charNumber = 1; for (char c : line.toCharArray()) { switch (state) { case BEFORE_ARGUMENT: if (c == ',') { words.add(currentWord.toString()); currentWord = new StringBuilder(); } else if (c == '"') { state = LineParserState.READING_COMA_ARGUMENT; } else if (!(c == ' ')) { currentWord.append(c); state = LineParserState.READING_ARGUMENT; } break; case READING_ARGUMENT: if (c == ',') { state = LineParserState.BEFORE_ARGUMENT; words.add(currentWord.toString()); currentWord = new StringBuilder(); } else if (c == ' ') { state = LineParserState.END_ARGUMENT; } else { currentWord.append(c); } break; case READING_COMA_ARGUMENT: if (c == '"') { state = LineParserState.EXPECTING_END_COMA_ARGUMENT; } else { currentWord.append(c); } break; case EXPECTING_END_COMA_ARGUMENT: if (c == ',') { words.add(currentWord.toString()); currentWord = new StringBuilder(); state = LineParserState.BEFORE_ARGUMENT; } else if (c == '"') { currentWord.append('"'); state = LineParserState.READING_COMA_ARGUMENT; } else if (c == ' ') { state = LineParserState.EXPECTING_END_COMA_ARGUMENT; } else { throw IntegrationParserException.createParseError( IntegrationParseMessages.CHARACTERS_EXPECTED.getMessageKey(), charNumber, ",", "\""); } break; case END_ARGUMENT: if (c == ',') { words.add(currentWord.toString()); currentWord = new StringBuilder(); state = LineParserState.BEFORE_ARGUMENT; } else if (!(c == ' ')) { throw IntegrationParserException.createParseError( IntegrationParseMessages.CHARACTER_EXPECTED.getMessageKey(), charNumber, ","); } break; default: } charNumber++; } if (state == LineParserState.READING_COMA_ARGUMENT) { throw IntegrationParserException.createParseError(IntegrationParseMessages.UNEXPECTED_END_OF_LINE.getMessageKey()); } words.add(currentWord.toString()); return words.toArray(new String[words.size()]); } }