Browse Source

Converted to a lambda function.

Matt Clark 3 years ago
commit
d5734742e6
34 changed files with 2306 additions and 0 deletions
  1. 5 0
      .gitignore
  2. 96 0
      pom.xml
  3. 113 0
      src/main/java/com/amazon/barcodes/lambda/RequestHandler.java
  4. 98 0
      src/main/java/org/barcodeapi/core/utils/CodeUtils.java
  5. 32 0
      src/main/java/org/barcodeapi/server/api/BarcodeAPIHandler.java
  6. 64 0
      src/main/java/org/barcodeapi/server/core/Barcode.java
  7. 27 0
      src/main/java/org/barcodeapi/server/core/Blacklist.java
  8. 89 0
      src/main/java/org/barcodeapi/server/core/CodeGenerators.java
  9. 27 0
      src/main/java/org/barcodeapi/server/core/GenerationException.java
  10. 74 0
      src/main/java/org/barcodeapi/server/core/TypeSelector.java
  11. 94 0
      src/main/java/org/barcodeapi/server/gen/BarcodeFactory.java
  12. 97 0
      src/main/java/org/barcodeapi/server/gen/BarcodeGenerator.java
  13. 139 0
      src/main/java/org/barcodeapi/server/gen/BarcodeType.java
  14. 74 0
      src/main/java/org/barcodeapi/server/gen/types/CodabarGenerator.java
  15. 70 0
      src/main/java/org/barcodeapi/server/gen/types/Code128Generator.java
  16. 61 0
      src/main/java/org/barcodeapi/server/gen/types/Code39Generator.java
  17. 58 0
      src/main/java/org/barcodeapi/server/gen/types/DataMatrixGenerator.java
  18. 69 0
      src/main/java/org/barcodeapi/server/gen/types/Ean13Generator.java
  19. 69 0
      src/main/java/org/barcodeapi/server/gen/types/Ean8Generator.java
  20. 55 0
      src/main/java/org/barcodeapi/server/gen/types/PDF417Generator.java
  21. 56 0
      src/main/java/org/barcodeapi/server/gen/types/QRCodeGenerator.java
  22. 60 0
      src/main/java/org/barcodeapi/server/gen/types/UPCAGenerator.java
  23. 60 0
      src/main/java/org/barcodeapi/server/gen/types/UPCEGenerator.java
  24. 2 0
      src/main/resources/blacklist.conf
  25. BIN
      src/main/resources/favicon.png
  26. 1 0
      src/main/resources/help.html
  27. 136 0
      src/main/resources/index.css
  28. 84 0
      src/main/resources/index.html
  29. 84 0
      src/main/resources/index.js
  30. BIN
      src/main/resources/logo.png
  31. 9 0
      src/main/resources/robots.txt
  32. 34 0
      src/test/java/com/amazon/barcodes/lambda/RequestHandlerTest.java
  33. 136 0
      src/test/java/com/amazon/barcodes/lambda/TestContext.java
  34. 233 0
      src/test/java/com/amazon/barcodes/lambda/TestUtils.java

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+.classpath
+.project
+.settings/
+target/
+

+ 96 - 0
pom.xml

@@ -0,0 +1,96 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+
+	<groupId>com.amazon.barcodes</groupId>
+	<artifactId>lambda</artifactId>
+	<version>1.0.0</version>
+	<packaging>jar</packaging>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>3.6.0</version>
+				<configuration>
+					<source>1.8</source>
+					<target>1.8</target>
+					<encoding>UTF-8</encoding>
+					<forceJavacCompilerUse>true</forceJavacCompilerUse>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-shade-plugin</artifactId>
+				<version>3.0.0</version>
+				<executions>
+					<execution>
+						<phase>package</phase>
+						<goals>
+							<goal>shade</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+
+	<dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>com.amazonaws</groupId>
+				<artifactId>aws-java-sdk-bom</artifactId>
+				<version>1.11.537</version>
+				<type>pom</type>
+				<scope>import</scope>
+			</dependency>
+		</dependencies>
+	</dependencyManagement>
+
+	<dependencies>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.12</version>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>com.amazonaws</groupId>
+			<artifactId>aws-lambda-java-events</artifactId>
+			<version>1.3.0</version>
+		</dependency>
+		<dependency>
+			<groupId>com.amazonaws</groupId>
+			<artifactId>aws-lambda-java-core</artifactId>
+			<version>1.1.0</version>
+		</dependency>
+
+		<dependency>
+			<groupId>net.sf.barcode4j</groupId>
+			<artifactId>barcode4j</artifactId>
+			<version>2.1</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.google.zxing</groupId>
+			<artifactId>core</artifactId>
+			<version>3.3.0</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.google.zxing</groupId>
+			<artifactId>javase</artifactId>
+			<version>3.3.0</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.json</groupId>
+			<artifactId>json</artifactId>
+			<version>20160212</version>
+			<scope>compile</scope>
+		</dependency>
+	</dependencies>
+</project>

+ 113 - 0
src/main/java/com/amazon/barcodes/lambda/RequestHandler.java

@@ -0,0 +1,113 @@
+package com.amazon.barcodes.lambda;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import org.barcodeapi.server.core.Barcode;
+import org.barcodeapi.server.core.GenerationException;
+import org.barcodeapi.server.gen.BarcodeFactory;
+import org.json.JSONObject;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
+
+public class RequestHandler implements RequestStreamHandler {
+
+	@Override
+	public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
+
+		BufferedReader reader = new BufferedReader(new InputStreamReader(input));
+		PrintWriter writer = new PrintWriter(output, true);
+
+		String req = "";
+		String line;
+		while ((line = reader.readLine()) != null) {
+			req += line;
+		}
+
+		JSONObject request = new JSONObject(req);
+
+		try {
+
+			writer.println(onRequest(request));
+		} catch (GenerationException e) {
+
+			writer.println(onError(e));
+		}
+	}
+
+	private JSONObject onRequest(JSONObject request) throws GenerationException {
+
+		Barcode barcode = BarcodeFactory.requestBarcode(request.getString("path"));
+
+		String b64 = barcode.getImage();
+
+		System.out.println(System.currentTimeMillis() + " : " + //
+				"Served [ " + barcode.getType() + " ] " + //
+				"with [ " + barcode.getData() + " ] " + //
+				"size [ " + barcode.getImageSize() + " ]" + //
+				"using [ " + getSource(request) + " ] " + //
+				"for [ " + getCaller(request) + " ]");
+
+		JSONObject headers = new JSONObject()//
+				.put("Server", "BarcodeAPI.org")//
+				.put("Accept-Charset", "utf-8")//
+				.put("Cache-Control", "max-age=86400, public")//
+				.put("Content-Type", "image/png")//
+				.put("Content-Disposition", "filename=" + barcode.getDataNice() + ".png")//
+				.put("Content-Length", barcode.getImageSize())//
+				.put("X-Barcode-Type", barcode.getType())//
+				.put("X-Barcode-Content", barcode.getDataEncoded());
+
+		return new JSONObject()//
+				.put("statusCode", 200)//
+				.put("headers", headers)//
+				.put("body", b64)//
+				.put("isBase64Encoded", true);
+	}
+
+	private JSONObject onError(GenerationException e) {
+
+		JSONObject body = new JSONObject()//
+				.put("error", e.getMessage())//
+				.put("details", e.getExceptionType());
+
+		return new JSONObject()//
+				.put("statusCode", 400)//
+				.put("headers", new JSONObject())//
+				.put("body", body)//
+				.put("isBase64Encoded", false);
+	}
+
+	public String getSource(JSONObject request) {
+
+		// get source of the request
+		String source;
+		String ref = request.getJSONObject("headers").optString("Referer");
+		if (!ref.equals("")) {
+			source = ref;
+		} else {
+			source = "API";
+		}
+
+		return source;
+	}
+
+	public String getCaller(JSONObject request) {
+
+		// get users IP
+		String caller;
+		String ip = request.getJSONObject("headers").optString("X-Forwarded-For");
+		if (!ip.equals("")) {
+			caller = ip;
+		} else {
+			caller = "Unknown";
+		}
+
+		return caller;
+	}
+}

+ 98 - 0
src/main/java/org/barcodeapi/core/utils/CodeUtils.java

@@ -0,0 +1,98 @@
+package org.barcodeapi.core.utils;
+
+import java.security.MessageDigest;
+
+public class CodeUtils {
+
+	/**
+	 * Calculate the MD5 hash of some bytes.
+	 * 
+	 * @param bytes
+	 * @return
+	 */
+	public static String getMD5Sum(byte[] bytes) {
+
+		try {
+
+			byte[] hash = MessageDigest.getInstance("MD5").digest(bytes);
+
+			StringBuilder hexString = new StringBuilder();
+
+			for (int i = 0; i < hash.length; i++) {
+				String hex = Integer.toHexString(0xFF & hash[i]);
+				if (hex.length() == 1) {
+					hexString.append('0');
+				}
+				hexString.append(hex);
+			}
+
+			return hexString.toString();
+		} catch (Exception e) {
+
+			return null;
+		}
+	}
+
+	/**
+	 * Converts a data string into a string containing control characters; Any
+	 * instance of [$$?] will be converted into it's control character equivalent,
+	 * offset by 64 from the given character.
+	 * 
+	 * A$$@A --> A(NUL)A
+	 * 
+	 * A$$_A --> A(US)A
+	 * 
+	 * @param offset
+	 * @param data
+	 * @return
+	 */
+	public static String parseControlChars(String data) {
+
+		String newData = "";
+
+		for (int x = 0; x < data.length(); x++) {
+
+			if (data.length() > x + 2 && data.charAt(x) == '$' && data.charAt(x + 1) == '$') {
+
+				newData += (char) (((int) data.charAt(x += 2)) - 64);
+			} else {
+
+				newData += data.charAt(x);
+			}
+		}
+
+		return newData;
+	}
+
+	// FIXME does not work correctly
+	public static int calculateEanChecksum(String data, int count) {
+
+		int sum0 = 0;
+		int sum1 = 0;
+
+		for (int x = count; x > 1; x--) {
+
+			int digit = Character.getNumericValue(data.charAt(count - x));
+
+			if (x % 2 == 0) {
+
+				sum1 += (3 * digit);
+			} else {
+
+				sum0 += digit;
+			}
+		}
+
+		int sum = sum0 + sum1;
+
+		int check = 10 - (sum % 10);
+
+		if (check == 10) {
+
+			return 0;
+		} else {
+
+			return check;
+		}
+	}
+}

+ 32 - 0
src/main/java/org/barcodeapi/server/api/BarcodeAPIHandler.java

@@ -0,0 +1,32 @@
+package org.barcodeapi.server.api;
+
+public class BarcodeAPIHandler {
+
+	// public void handle(String request) throws IOException {
+	//
+	// ERR = BarcodeFactory.requestBarcode("/128/$$@E$$@R$$@R$$@O$$@R$$@");
+	// BLK =
+	// BarcodeFactory.requestBarcode("/128/$$@B$$@L$$@A$$@C$$@K$$@L$$@I$$@S$$@T$$@");
+	//
+	// String message = "Failed [ " + target + " ]" + //
+	// " reason [ " + e.toString() + " ]";
+	//
+	// switch (e.getExceptionType()) {
+	// case BLACKLIST:
+	// // serve blacklist code
+	//
+	// barcode = BLK;
+	// break;
+	//
+	// case EMPTY:
+	// case FAILED:
+	// default: // serve error code
+	// barcode = ERR;
+	// break;
+	// }
+	//
+	// // set HTTP response code and add message to headers
+	// response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+	// response.setHeader("X-Error-Message", message);
+	// }
+}

+ 64 - 0
src/main/java/org/barcodeapi/server/core/Barcode.java

@@ -0,0 +1,64 @@
+package org.barcodeapi.server.core;
+
+import java.net.URLEncoder;
+import java.util.Base64;
+
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+
+public class Barcode {
+
+	private final BarcodeGenerator generator;
+	private final String data;
+	private final int size;
+	private final String img;
+
+	public Barcode(BarcodeGenerator generator, String data) throws GenerationException {
+
+		this.generator = generator;
+		this.data = data;
+
+		byte[] render = generator.render(getData());
+		String b64 = Base64.getEncoder().encodeToString(render);
+
+		this.size = render.length;
+		this.img = b64;
+	}
+
+	public BarcodeType getType() {
+
+		return generator.getType();
+	}
+
+	public String getData() {
+
+		return data;
+	}
+
+	public String getImage() {
+
+		return img;
+	}
+
+	public int getImageSize() {
+
+		return size;
+	}
+
+	public String getDataNice() {
+
+		return data.replaceAll("[!@#$%^&*\\(\\)\\[\\]\\{\\};:\\',\\<\\>\\\"]", "");
+	}
+
+	public String getDataEncoded() {
+
+		try {
+
+			return URLEncoder.encode(data, "UTF-8");
+		} catch (Exception e) {
+
+			return null;
+		}
+	}
+
+}

+ 27 - 0
src/main/java/org/barcodeapi/server/core/Blacklist.java

@@ -0,0 +1,27 @@
+package org.barcodeapi.server.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Blacklist {
+
+	private static final List<String> blacklist;
+
+	static {
+
+		try {
+
+			// URL file = Blacklist.class.getResource("blacklist.conf");
+			// blacklist = Files.readAllLines(Paths.get(file.toURI()));
+			blacklist = new ArrayList<String>();
+		} catch (Exception e) {
+
+			throw new RuntimeException("Failed to initialize blacklist.");
+		}
+	}
+
+	public static List<String> getBlacklist() {
+
+		return blacklist;
+	}
+}

+ 89 - 0
src/main/java/org/barcodeapi/server/core/CodeGenerators.java

@@ -0,0 +1,89 @@
+package org.barcodeapi.server.core;
+
+import java.util.HashMap;
+
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+import org.barcodeapi.server.gen.types.CodabarGenerator;
+import org.barcodeapi.server.gen.types.Code128Generator;
+import org.barcodeapi.server.gen.types.Code39Generator;
+import org.barcodeapi.server.gen.types.DataMatrixGenerator;
+import org.barcodeapi.server.gen.types.Ean13Generator;
+import org.barcodeapi.server.gen.types.Ean8Generator;
+import org.barcodeapi.server.gen.types.PDF417Generator;
+import org.barcodeapi.server.gen.types.QRCodeGenerator;
+import org.barcodeapi.server.gen.types.UPCAGenerator;
+import org.barcodeapi.server.gen.types.UPCEGenerator;
+
+public class CodeGenerators {
+
+	private static CodeGenerators codeGenerators;
+
+	private HashMap<BarcodeType, BarcodeGenerator> generators;
+
+	public CodeGenerators() {
+
+		generators = new HashMap<BarcodeType, BarcodeGenerator>();
+
+		generators.put(BarcodeType.EAN8, new Ean8Generator());
+		generators.put(BarcodeType.EAN13, new Ean13Generator());
+
+		generators.put(BarcodeType.UPC_A, new UPCAGenerator());
+		generators.put(BarcodeType.UPC_E, new UPCEGenerator());
+
+		generators.put(BarcodeType.CODABAR, new CodabarGenerator());
+
+		generators.put(BarcodeType.Code39, new Code39Generator());
+		generators.put(BarcodeType.Code128, new Code128Generator());
+
+		generators.put(BarcodeType.QRCode, new QRCodeGenerator());
+		generators.put(BarcodeType.DataMatrix, new DataMatrixGenerator());
+
+		generators.put(BarcodeType.PDF417, new PDF417Generator());
+	}
+
+	/**
+	 * Get a CodeType object by any of its associated string IDs.
+	 * 
+	 * Will return null if none are found.
+	 * 
+	 * @param codeType
+	 * @return
+	 */
+	public BarcodeGenerator getGenerator(String codeType) {
+
+		// Convert to lower case
+		codeType = codeType.toLowerCase();
+
+		// Loop all known types
+		for (BarcodeType type : generators.keySet()) {
+
+			// Loop each defined type string
+			for (String typeString : type.getTypeStrings()) {
+
+				// Return on match
+				if (codeType.equals(typeString)) {
+
+					return getGenerator(type);
+				}
+			}
+		}
+
+		// Return no matches
+		return null;
+	}
+
+	public BarcodeGenerator getGenerator(BarcodeType codeType) {
+
+		return generators.get(codeType);
+	}
+
+	public static synchronized CodeGenerators getInstance() {
+
+		if (codeGenerators == null) {
+
+			codeGenerators = new CodeGenerators();
+		}
+		return codeGenerators;
+	}
+}

+ 27 - 0
src/main/java/org/barcodeapi/server/core/GenerationException.java

@@ -0,0 +1,27 @@
+package org.barcodeapi.server.core;
+
+public class GenerationException extends Exception {
+
+	private static final long serialVersionUID = 1L;
+
+	public enum ExceptionType {
+		EMPTY, BLACKLIST, FAILED, INVALID;
+	}
+
+	private final ExceptionType type;
+
+	public GenerationException(ExceptionType type) {
+		this(type, new Throwable(type.toString()));
+	}
+
+	public GenerationException(ExceptionType type, Throwable throwable) {
+		super(throwable);
+
+		this.type = type;
+	}
+
+	public ExceptionType getExceptionType() {
+
+		return type;
+	}
+}

+ 74 - 0
src/main/java/org/barcodeapi/server/core/TypeSelector.java

@@ -0,0 +1,74 @@
+package org.barcodeapi.server.core;
+
+import org.barcodeapi.server.gen.BarcodeType;
+
+public class TypeSelector {
+
+	/**
+	 * Returns a CodeType object best suited for the given data string.
+	 * 
+	 * @param data
+	 * @return
+	 */
+	public static BarcodeType getType(String data) {
+
+		// Match UPC-E format
+		if (data.matches(BarcodeType.UPC_E.getAutomatchPattern())) {
+
+			return BarcodeType.UPC_E;
+		}
+
+		// Match UPC-A format
+		if (data.matches(BarcodeType.UPC_A.getAutomatchPattern())) {
+
+			return BarcodeType.UPC_A;
+		}
+
+		// Match EAN-8 format
+		if (data.matches(BarcodeType.EAN8.getAutomatchPattern())) {
+
+			// TODO validate checksum
+			return BarcodeType.EAN8;
+		}
+
+		// Match EAN-13 format
+		if (data.matches(BarcodeType.EAN13.getAutomatchPattern())) {
+
+			// TODO validate checksum
+			return BarcodeType.EAN13;
+		}
+
+		// Match Codabar format
+		if (data.matches(BarcodeType.CODABAR.getAutomatchPattern())) {
+
+			return BarcodeType.CODABAR;
+		}
+
+		// Match Code39 format
+		if (data.matches(BarcodeType.Code39.getAutomatchPattern())) {
+
+			return BarcodeType.Code39;
+		}
+
+		// Match Code128 format
+		if (data.matches(BarcodeType.Code128.getAutomatchPattern())) {
+
+			return BarcodeType.Code128;
+		}
+
+		// Match QR format
+		if (data.matches(BarcodeType.QRCode.getAutomatchPattern())) {
+
+			return BarcodeType.QRCode;
+		}
+
+		// Match DataMatrix format
+		if (data.matches(BarcodeType.DataMatrix.getAutomatchPattern())) {
+
+			return BarcodeType.DataMatrix;
+		}
+
+		// Return null on no matches
+		return null;
+	}
+}

+ 94 - 0
src/main/java/org/barcodeapi/server/gen/BarcodeFactory.java

@@ -0,0 +1,94 @@
+package org.barcodeapi.server.gen;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+
+import org.barcodeapi.server.core.Barcode;
+import org.barcodeapi.server.core.Blacklist;
+import org.barcodeapi.server.core.CodeGenerators;
+import org.barcodeapi.server.core.GenerationException;
+import org.barcodeapi.server.core.GenerationException.ExceptionType;
+import org.barcodeapi.server.core.TypeSelector;
+
+public class BarcodeFactory {
+
+	private BarcodeFactory() {
+
+	}
+
+	public static Barcode requestBarcode(String target) throws GenerationException {
+
+		// get the request string
+		String data = target.substring(1, target.length());
+
+		try {
+
+			// decode the data string
+			data = URLDecoder.decode(data, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+
+			throw new IllegalArgumentException("Failed to decode target");
+		}
+
+		CodeGenerators generators = CodeGenerators.getInstance();
+
+		// process selected type
+		BarcodeGenerator generator;
+		BarcodeType type;
+
+		// parse code type / data string
+		int typeIndex = data.indexOf("/");
+		if (typeIndex > 0) {
+
+			// get the type string
+			String typeString = target.substring(1, typeIndex + 1);
+
+			// type is auto
+			if (typeString.equals("auto")) {
+
+				// no type specified
+				data = data.substring(5);
+				type = TypeSelector.getType(data);
+				generator = generators.getGenerator(type);
+			} else {
+
+				// check if generator found for given type
+				generator = generators.getGenerator(typeString);
+				if (generator == null) {
+
+					// no type specified
+					type = TypeSelector.getType(data);
+					generator = generators.getGenerator(type);
+				} else {
+
+					// get generator type and data string
+					type = generator.getType();
+					data = data.substring(typeIndex + 1);
+				}
+			}
+		} else {
+
+			// no type specified
+			type = TypeSelector.getType(data);
+			generator = generators.getGenerator(type);
+		}
+
+		// check for valid render data
+		if (data == null || data.equals("")) {
+
+			throw new GenerationException(ExceptionType.EMPTY);
+		}
+
+		// match against blacklist
+		for (String invalid : Blacklist.getBlacklist()) {
+
+			if (data.matches(invalid)) {
+
+				throw new GenerationException(ExceptionType.BLACKLIST);
+			}
+		}
+
+		// render image and create new object with image
+		return generator.getCode(data);
+	}
+}

+ 97 - 0
src/main/java/org/barcodeapi/server/gen/BarcodeGenerator.java

@@ -0,0 +1,97 @@
+package org.barcodeapi.server.gen;
+
+import org.barcodeapi.server.core.Barcode;
+import org.barcodeapi.server.core.GenerationException;
+import org.barcodeapi.server.core.GenerationException.ExceptionType;
+
+public abstract class BarcodeGenerator {
+
+	private final BarcodeType codeType;
+
+	/**
+	 * Initialize a new generator of the defined type.
+	 * 
+	 * @param type
+	 */
+	public BarcodeGenerator(BarcodeType type) {
+
+		this.codeType = type;
+	}
+
+	/**
+	 * Get the defined code type.
+	 * 
+	 * @return
+	 */
+	public BarcodeType getType() {
+
+		return codeType;
+	}
+
+	/**
+	 * Get the raw bytes of a PNG for the given data string.
+	 * 
+	 * @param data
+	 * @return
+	 */
+	public Barcode getCode(String data) throws GenerationException {
+
+		// validate code format
+		if (!data.matches(getType().getFormatPattern())) {
+
+			throw new GenerationException(ExceptionType.INVALID, //
+					new Throwable("Invalid data for selected code type"));
+		}
+
+		try {
+
+			// validate and render barcode object
+			return new Barcode(this, onValidateRequest(data));
+
+		} catch (Exception e) {
+
+			throw new GenerationException(ExceptionType.FAILED, e);
+		}
+	}
+
+	/**
+	 * Call this to render the barcode image.
+	 * 
+	 * @param data
+	 * @return
+	 * @throws GenerationException
+	 */
+	public byte[] render(String data) throws GenerationException {
+
+		try {
+
+			return onRender(data);
+		} catch (GenerationException e) {
+
+			throw e;
+		} catch (Exception | Error e) {
+
+			throw new GenerationException(ExceptionType.FAILED, e);
+		}
+	}
+
+	/**
+	 * Default validation does not modifications.
+	 * 
+	 * @param data
+	 * @return
+	 * @throws GenerationException
+	 */
+	protected String onValidateRequest(String data) throws GenerationException {
+
+		return data;
+	}
+
+	/**
+	 * Implemented by the specific generator.
+	 * 
+	 * @param data
+	 * @return
+	 */
+	protected abstract byte[] onRender(String data) throws Exception;
+}

+ 139 - 0
src/main/java/org/barcodeapi/server/gen/BarcodeType.java

@@ -0,0 +1,139 @@
+package org.barcodeapi.server.gen;
+
+public enum BarcodeType {
+
+	/**
+	 * UPC-E type UPC code;
+	 */
+	UPC_E(new String[] { "e", "upc-e", "upce" }, //
+			"^(?=.*0)[0-9]{8}$", //
+			"^(?=.*0)[0-9]{7,8}$"),
+
+	/**
+	 * UPC-A type UPC code;
+	 */
+	UPC_A(new String[] { "a", "upc-a", "upca", "upc" }, //
+			"^(?=.*0)[0-9]{12}$", //
+			"^(?=.*0)[0-9]{11,12}$"),
+
+	/**
+	 * EAN-8 type UPC code;
+	 * 
+	 * 7 numerical digits followed by a single checksum digit.
+	 */
+	EAN8(new String[] { "8", "ean-8", "ean8" }, //
+			"^[0-9]{8}$", //
+			"^[0-9]{7,8}$"),
+
+	/**
+	 * EAN-13 type UPC code;
+	 * 
+	 * 12 numerical digits followed by a single checksum digit.
+	 */
+	EAN13(new String[] { "13", "ean-13", "ean13" }, //
+			"^[0-9]{13}$", //
+			"^[0-9]{12,13}$"),
+
+	/**
+	 * Codabar type code;
+	 */
+	CODABAR(new String[] { "codabar" }, //
+			"^[0-9:$]{4,12}$", //
+			"^[0-9-:$\\/.+]+$"), //
+
+	/**
+	 * Code39 type code;
+	 * 
+	 * Variable length consisting of only numbers and upper-case characters.
+	 */
+	Code39(new String[] { "39", "code-39", "code39" }, //
+			"^[A-Z0-9 $.\\/]{1,20}$", //
+			"^[A-Z*0-9 -$%.\\/+]+$"),
+
+	/**
+	 * Code128 type code;
+	 * 
+	 * Variable length consisting of numbers, letters, and symbols.
+	 */
+	Code128(new String[] { "128", "code-128", "code128" }, //
+			"^[ !#$()*.\\/0-9=?A-Z_a-z~]{1,24}$", //
+			"^[ !\"#$%&'()*+,-.\\/0-9:;<=>?@A-Z\\[\\\\\\]^_`a-z{|}~]+$"),
+
+	/**
+	 * QR type code;
+	 * 
+	 * A high density data code with error correction.
+	 */
+	QRCode(new String[] { "qr", "qr-code", "qrcode" }, //
+			"^.{1,64}$", //
+			"^.{1,65535}$"),
+
+	/**
+	 * Data Matrix type code;
+	 * 
+	 * A high density data code with error correction.
+	 */
+	DataMatrix(new String[] { "dm", "data-matrix", "datamatrix", "matrix", "data" }, //
+			"^[ !\"#$%&'()*+,-.\\/0-9:;<=>?@A-Z\\[\\\\\\]^_`a-z{|}~]{1,2335}$", //
+			"^[ !\"#$%&'()*+,-.\\/0-9:;<=>?@A-Z\\[\\\\\\]^_`a-z{|}~]{1,2335}$"),
+
+	/**
+	 * PDF417
+	 * 
+	 * 
+	 */
+	PDF417(new String[] { "417", "pdf417", "pdf" }, //
+			"^[ !\"#$%&'()*+,-.\\/0-9:;<=>?@A-Z\\[\\\\\\]^_`a-z{|}~]{1,2335}$", //
+			"^[ !\"#$%&'()*+,-.\\/0-9:;<=>?@A-Z\\[\\\\\\]^_`a-z{|}~]{1,2335}$");
+
+	/**
+	 * Local Variables
+	 */
+	private final String[] types;
+
+	private final String autoPattern;
+	private final String formatPattern;
+
+	/**
+	 * Create a new CodeType with its pattern and list of associated IDs.
+	 * 
+	 * @param typeStrings
+	 */
+	BarcodeType(String[] typeStrings, String automatchPattern, String extendedPattern) {
+
+		this.types = typeStrings;
+
+		this.autoPattern = automatchPattern;
+		this.formatPattern = extendedPattern;
+	}
+
+	/**
+	 * Get a list of all IDs associated with a CodeType.
+	 * 
+	 * @return
+	 */
+	public String[] getTypeStrings() {
+
+		return types;
+	}
+
+	/**
+	 * Get the regular expression that matches on auto-typing.
+	 * 
+	 * @return
+	 */
+	public String getAutomatchPattern() {
+
+		return autoPattern;
+	}
+
+	/**
+	 * Get the regular expression that validates the data for the code type.
+	 * 
+	 * @return
+	 */
+	public String getFormatPattern() {
+
+		return formatPattern;
+	}
+}

+ 74 - 0
src/main/java/org/barcodeapi/server/gen/types/CodabarGenerator.java

@@ -0,0 +1,74 @@
+package org.barcodeapi.server.gen.types;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+import org.krysalis.barcode4j.HumanReadablePlacement;
+import org.krysalis.barcode4j.impl.codabar.CodabarBean;
+import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;
+import org.krysalis.barcode4j.tools.UnitConv;
+
+public class CodabarGenerator extends BarcodeGenerator {
+
+	private CodabarBean generator;
+
+	private final int dpi = 150;
+
+	/**
+	 * Constructor for the Codabar generator.
+	 */
+	public CodabarGenerator() {
+		super(BarcodeType.CODABAR);
+
+		// Setup Codabar generator
+		generator = new CodabarBean();
+
+		// barcode128Bean.setBarHeight(height);
+		double moduleWidth = UnitConv.in2mm(2.5f / dpi);
+		generator.setModuleWidth(moduleWidth);
+
+		/**
+		 * The minimum width of the Quiet Zone to the left and right of the 128 Bar Code
+		 * is 10x, where x is the minimum width of a module. It is mandatory at the left
+		 * and right side of the barcode.
+		 * 
+		 * https://en.wikipedia.org/wiki/Code_128#Quiet_zone
+		 * 
+		 */
+		generator.doQuietZone(true);
+		generator.setQuietZone(10 * moduleWidth);
+		// generator.setVerticalQuietZone(2 * moduleWidth);
+
+		generator.setMsgPosition(HumanReadablePlacement.HRP_BOTTOM);
+
+		generator.setHeight(UnitConv.in2mm(1));
+	}
+
+	/**
+	 * Called when an image was not found in cache and must be rendered;
+	 * 
+	 * Return a PNG image as bytes.
+	 * 
+	 * @throws IOException
+	 */
+	@Override
+	public byte[] onRender(String data) throws IOException {
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		BitmapCanvasProvider canvasProvider = new BitmapCanvasProvider(//
+				out, "image/x-png", dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0);
+
+		generator.generateBarcode(canvasProvider, data);
+
+		canvasProvider.getBufferedImage();
+		canvasProvider.finish();
+
+		out.close();
+
+		return out.toByteArray();
+	}
+}

+ 70 - 0
src/main/java/org/barcodeapi/server/gen/types/Code128Generator.java

@@ -0,0 +1,70 @@
+package org.barcodeapi.server.gen.types;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.barcodeapi.core.utils.CodeUtils;
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+import org.krysalis.barcode4j.HumanReadablePlacement;
+import org.krysalis.barcode4j.impl.code128.Code128Bean;
+import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;
+import org.krysalis.barcode4j.tools.UnitConv;
+
+public class Code128Generator extends BarcodeGenerator {
+
+	private Code128Bean generator;
+
+	private final int dpi = 150;
+
+	public Code128Generator() {
+		super(BarcodeType.Code128);
+
+		// Setup Code128 generator
+		generator = new Code128Bean();
+
+		// barcode128Bean.setBarHeight(height);
+		double moduleWidth = UnitConv.in2mm(2.5f / dpi);
+		generator.setModuleWidth(moduleWidth);
+
+		/**
+		 * The minimum width of the Quiet Zone to the left and right of the 128 Bar Code
+		 * is 10x, where x is the minimum width of a module. It is mandatory at the left
+		 * and right side of the barcode.
+		 * 
+		 * https://en.wikipedia.org/wiki/Code_128#Quiet_zone
+		 * 
+		 */
+		generator.doQuietZone(true);
+		generator.setQuietZone(10 * moduleWidth);
+
+		generator.setMsgPosition(HumanReadablePlacement.HRP_BOTTOM);
+
+		generator.setHeight(UnitConv.in2mm(1));
+	}
+
+	@Override
+	public String onValidateRequest(String data) {
+
+		return CodeUtils.parseControlChars(data);
+	}
+
+	@Override
+	public byte[] onRender(String data) throws IOException {
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		BitmapCanvasProvider canvasProvider = new BitmapCanvasProvider(//
+				out, "image/x-png", dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0);
+
+		generator.generateBarcode(canvasProvider, data);
+
+		canvasProvider.getBufferedImage();
+		canvasProvider.finish();
+
+		out.close();
+
+		return out.toByteArray();
+	}
+}

+ 61 - 0
src/main/java/org/barcodeapi/server/gen/types/Code39Generator.java

@@ -0,0 +1,61 @@
+package org.barcodeapi.server.gen.types;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+import org.krysalis.barcode4j.HumanReadablePlacement;
+import org.krysalis.barcode4j.impl.code39.Code39Bean;
+import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;
+import org.krysalis.barcode4j.tools.UnitConv;
+
+public class Code39Generator extends BarcodeGenerator {
+
+	private Code39Bean generator;
+
+	private final int dpi = 150;
+
+	public Code39Generator() {
+		super(BarcodeType.Code39);
+
+		// Setup Code39 generator
+		generator = new Code39Bean();
+
+		double moduleWidth = UnitConv.in2mm(2.5f / dpi);
+		generator.setModuleWidth(moduleWidth);
+
+		/**
+		 * Set quiet zone
+		 */
+		generator.doQuietZone(true);
+		generator.setQuietZone(10 * moduleWidth);
+		generator.setVerticalQuietZone(2 * moduleWidth);
+
+		generator.setMsgPosition(HumanReadablePlacement.HRP_BOTTOM);
+
+		generator.setHeight(UnitConv.in2mm(1));
+		// barcode39Bean.setBarHeight(UnitConv.in2mm(.5));
+
+		// barcode39Bean.setFontName(name);
+		// barcode39Bean.setFontSize(size);
+	}
+
+	@Override
+	public byte[] onRender(String data) throws IOException {
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		BitmapCanvasProvider canvasProvider = new BitmapCanvasProvider(//
+				out, "image/x-png", dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0);
+
+		generator.generateBarcode(canvasProvider, data);
+
+		canvasProvider.getBufferedImage();
+		canvasProvider.finish();
+		out.close();
+
+		return out.toByteArray();
+	}
+}

+ 58 - 0
src/main/java/org/barcodeapi/server/gen/types/DataMatrixGenerator.java

@@ -0,0 +1,58 @@
+package org.barcodeapi.server.gen.types;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.barcodeapi.core.utils.CodeUtils;
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+import org.krysalis.barcode4j.impl.datamatrix.DataMatrixBean;
+import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;
+import org.krysalis.barcode4j.tools.UnitConv;
+
+public class DataMatrixGenerator extends BarcodeGenerator {
+
+	private DataMatrixBean generator;
+
+	private final int dpi = 200;
+
+	/**
+	 * https://en.wikipedia.org/wiki/Data_Matrix
+	 */
+	public DataMatrixGenerator() {
+		super(BarcodeType.DataMatrix);
+
+		generator = new DataMatrixBean();
+
+		// configure barcode generator
+		generator.setQuietZone(2);
+		generator.doQuietZone(true);
+		generator.setModuleWidth(UnitConv.in2mm(5.0f / dpi));
+	}
+
+	@Override
+	public String onValidateRequest(String data) {
+
+		return CodeUtils.parseControlChars(data);
+	}
+
+	@Override
+	public byte[] onRender(String data) throws IOException {
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		BitmapCanvasProvider canvasProvider = new BitmapCanvasProvider(//
+				out, "image/x-png", dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0);
+
+		generator.generateBarcode(canvasProvider, data);
+
+		canvasProvider.getBufferedImage();
+
+		canvasProvider.finish();
+
+		out.close();
+
+		return out.toByteArray();
+	}
+}

+ 69 - 0
src/main/java/org/barcodeapi/server/gen/types/Ean13Generator.java

@@ -0,0 +1,69 @@
+package org.barcodeapi.server.gen.types;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.barcodeapi.core.utils.CodeUtils;
+import org.barcodeapi.server.core.GenerationException;
+import org.barcodeapi.server.core.GenerationException.ExceptionType;
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+import org.krysalis.barcode4j.impl.upcean.EAN13Bean;
+import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;
+import org.krysalis.barcode4j.tools.UnitConv;
+
+public class Ean13Generator extends BarcodeGenerator {
+
+	private EAN13Bean generator;
+
+	private final int dpi = 150;
+
+	public Ean13Generator() {
+		super(BarcodeType.EAN13);
+
+		generator = new EAN13Bean();
+
+		// configure barcode generator
+		generator.setModuleWidth(UnitConv.in2mm(2.5f / dpi));
+		generator.doQuietZone(true);
+		generator.setQuietZone(4);
+	}
+
+	@Override
+	public String onValidateRequest(String data) throws GenerationException {
+
+		if (data.length() == 12) {
+
+			return data;
+		}
+
+		int checksum = CodeUtils.calculateEanChecksum(data, 13);
+		String provided = data.substring(data.length() - 1);
+
+		if (!Integer.toString(checksum).equals(provided)) {
+
+			throw new GenerationException(ExceptionType.INVALID, //
+					new Throwable("Expected checksum : " + checksum));
+		}
+
+		return data;
+	}
+
+	@Override
+	public byte[] onRender(String data) throws IOException {
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		BitmapCanvasProvider canvasProvider = new BitmapCanvasProvider(//
+				out, "image/x-png", dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0);
+
+		generator.generateBarcode(canvasProvider, data);
+
+		canvasProvider.getBufferedImage();
+		canvasProvider.finish();
+		out.close();
+
+		return out.toByteArray();
+	}
+}

+ 69 - 0
src/main/java/org/barcodeapi/server/gen/types/Ean8Generator.java

@@ -0,0 +1,69 @@
+package org.barcodeapi.server.gen.types;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.barcodeapi.core.utils.CodeUtils;
+import org.barcodeapi.server.core.GenerationException;
+import org.barcodeapi.server.core.GenerationException.ExceptionType;
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+import org.krysalis.barcode4j.impl.upcean.EAN8Bean;
+import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;
+import org.krysalis.barcode4j.tools.UnitConv;
+
+public class Ean8Generator extends BarcodeGenerator {
+
+	private EAN8Bean generator;
+
+	private final int dpi = 150;
+
+	public Ean8Generator() {
+		super(BarcodeType.EAN8);
+
+		generator = new EAN8Bean();
+
+		// configure barcode generator
+		generator.setModuleWidth(UnitConv.in2mm(2.5f / dpi));
+		generator.doQuietZone(true);
+		generator.setQuietZone(4);
+	}
+
+	@Override
+	public String onValidateRequest(String data) throws GenerationException {
+
+		if (data.length() == 7) {
+
+			return data;
+		}
+
+		int checksum = CodeUtils.calculateEanChecksum(data, 8);
+		String provided = data.substring(data.length() - 1);
+
+		if (!Integer.toString(checksum).equals(provided)) {
+
+			throw new GenerationException(ExceptionType.INVALID, //
+					new Throwable("Expected checksum : " + checksum));
+		}
+
+		return data;
+	}
+
+	@Override
+	public byte[] onRender(String data) throws IOException {
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		BitmapCanvasProvider canvasProvider = new BitmapCanvasProvider(//
+				out, "image/x-png", dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0);
+
+		generator.generateBarcode(canvasProvider, data);
+
+		canvasProvider.getBufferedImage();
+		canvasProvider.finish();
+		out.close();
+
+		return out.toByteArray();
+	}
+}

+ 55 - 0
src/main/java/org/barcodeapi/server/gen/types/PDF417Generator.java

@@ -0,0 +1,55 @@
+package org.barcodeapi.server.gen.types;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+import org.krysalis.barcode4j.impl.pdf417.PDF417Bean;
+import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;
+import org.krysalis.barcode4j.tools.UnitConv;
+
+public class PDF417Generator extends BarcodeGenerator {
+
+	private PDF417Bean generator;
+
+	private final int dpi = 200;
+
+	/**
+	 * https://en.wikipedia.org/wiki/Data_Matrix
+	 */
+	public PDF417Generator() {
+		super(BarcodeType.PDF417);
+
+		generator = new PDF417Bean();
+
+		// configure barcode generator
+		generator.setQuietZone(2);
+		generator.doQuietZone(true);
+		generator.setModuleWidth(UnitConv.in2mm(5.0f / dpi));
+	}
+
+	@Override
+	public String onValidateRequest(String data) {
+
+		return data;
+	}
+
+	@Override
+	public byte[] onRender(String data) throws IOException {
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		BitmapCanvasProvider canvasProvider = new BitmapCanvasProvider(//
+				out, "image/x-png", dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0);
+
+		generator.generateBarcode(canvasProvider, data);
+
+		canvasProvider.getBufferedImage();
+		canvasProvider.finish();
+		out.close();
+
+		return out.toByteArray();
+	}
+}

+ 56 - 0
src/main/java/org/barcodeapi/server/gen/types/QRCodeGenerator.java

@@ -0,0 +1,56 @@
+package org.barcodeapi.server.gen.types;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.barcodeapi.core.utils.CodeUtils;
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+public class QRCodeGenerator extends BarcodeGenerator {
+
+	private QRCodeWriter codeWriter;
+
+	public QRCodeGenerator() {
+		super(BarcodeType.QRCode);
+
+		codeWriter = new QRCodeWriter();
+	}
+
+	@Override
+	public String onValidateRequest(String data) {
+
+		return CodeUtils.parseControlChars(data);
+	}
+
+	@Override
+	public byte[] onRender(String data) throws WriterException, IOException {
+
+		int mWidth = 300;
+		int mHeight = 300;
+
+		Map<EncodeHintType, Object> hintsMap = new HashMap<>();
+		hintsMap.put(EncodeHintType.CHARACTER_SET, "utf-8");
+		hintsMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
+		hintsMap.put(EncodeHintType.MARGIN, 2);
+
+		BitMatrix bitMatrix = codeWriter.encode(//
+				data, BarcodeFormat.QR_CODE, mWidth, mHeight, hintsMap);
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		MatrixToImageWriter.writeToStream(bitMatrix, "png", out);
+
+		return out.toByteArray();
+	}
+}

+ 60 - 0
src/main/java/org/barcodeapi/server/gen/types/UPCAGenerator.java

@@ -0,0 +1,60 @@
+package org.barcodeapi.server.gen.types;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+import org.krysalis.barcode4j.HumanReadablePlacement;
+import org.krysalis.barcode4j.impl.upcean.UPCABean;
+import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;
+import org.krysalis.barcode4j.tools.UnitConv;
+
+public class UPCAGenerator extends BarcodeGenerator {
+
+	private UPCABean generator;
+
+	private final int dpi = 150;
+
+	/**
+	 * 
+	 */
+	public UPCAGenerator() {
+		super(BarcodeType.UPC_A);
+
+		// Setup Code39 generator
+		generator = new UPCABean();
+
+		double moduleWidth = UnitConv.in2mm(2.5f / dpi);
+		generator.setModuleWidth(moduleWidth);
+
+		/**
+		 * Set quiet zone
+		 */
+		generator.doQuietZone(true);
+		generator.setQuietZone(10 * moduleWidth);
+		generator.setVerticalQuietZone(2 * moduleWidth);
+
+		generator.setMsgPosition(HumanReadablePlacement.HRP_BOTTOM);
+
+		generator.setHeight(UnitConv.in2mm(1));
+	}
+
+	@Override
+	public byte[] onRender(String data) throws IOException {
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		BitmapCanvasProvider canvasProvider = new BitmapCanvasProvider(//
+				out, "image/x-png", dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0);
+
+		generator.generateBarcode(canvasProvider, data);
+
+		canvasProvider.getBufferedImage();
+		canvasProvider.finish();
+		out.close();
+
+		return out.toByteArray();
+	}
+}

+ 60 - 0
src/main/java/org/barcodeapi/server/gen/types/UPCEGenerator.java

@@ -0,0 +1,60 @@
+package org.barcodeapi.server.gen.types;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.barcodeapi.server.gen.BarcodeGenerator;
+import org.barcodeapi.server.gen.BarcodeType;
+import org.krysalis.barcode4j.HumanReadablePlacement;
+import org.krysalis.barcode4j.impl.upcean.UPCEBean;
+import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider;
+import org.krysalis.barcode4j.tools.UnitConv;
+
+public class UPCEGenerator extends BarcodeGenerator {
+
+	private UPCEBean generator;
+
+	private final int dpi = 150;
+
+	/**
+	 * 
+	 */
+	public UPCEGenerator() {
+		super(BarcodeType.UPC_E);
+
+		// Setup Code39 generator
+		generator = new UPCEBean();
+
+		double moduleWidth = UnitConv.in2mm(2.5f / dpi);
+		generator.setModuleWidth(moduleWidth);
+
+		/**
+		 * Set quiet zone
+		 */
+		generator.doQuietZone(true);
+		generator.setQuietZone(10 * moduleWidth);
+		generator.setVerticalQuietZone(2 * moduleWidth);
+
+		generator.setMsgPosition(HumanReadablePlacement.HRP_BOTTOM);
+
+		generator.setHeight(UnitConv.in2mm(1));
+	}
+
+	@Override
+	public byte[] onRender(String data) throws IOException {
+
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+		BitmapCanvasProvider canvasProvider = new BitmapCanvasProvider(//
+				out, "image/x-png", dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0);
+
+		generator.generateBarcode(canvasProvider, data);
+
+		canvasProvider.getBufferedImage();
+		canvasProvider.finish();
+		out.close();
+
+		return out.toByteArray();
+	}
+}

+ 2 - 0
src/main/resources/blacklist.conf

@@ -0,0 +1,2 @@
+^__tstblk$
+^_tstblk_$

BIN
src/main/resources/favicon.png


+ 1 - 0
src/main/resources/help.html

@@ -0,0 +1 @@
+https://barcodeapi.org/api/Code_Generation

+ 136 - 0
src/main/resources/index.css

@@ -0,0 +1,136 @@
+#global_wrapper {
+	position: absolute;
+	width: 100%;
+	height: 100%;
+	top: 0px;
+	left: 0px;
+}
+
+#content_wrapper {
+	width: auto;
+	max-width: 1024px;
+	margin-left: auto;
+	margin-right: auto;
+}
+
+#header {
+	background-image: url("logo.png");
+	background-size: cover;
+	background-position: 50% 50%;
+	max-width: 1024px;
+	height: 75px;
+}
+
+#input_wrapper {
+	width: 100%;
+}
+
+#text {
+	width: 100%;
+	height: 45px;
+	font-size: 18px;
+	border-radius: 5px;
+	text-align: center;
+}
+
+#frame {
+	border: solid 1px;
+}
+
+#barcode_output {
+	max-width: 100%;
+	margin-left: auto;
+	margin-right: auto;
+	display: block;
+	padding-top: 8px;
+	padding-bottom: 8px;
+}
+
+#barcode_outout[src=""] {
+	display: none;
+}
+
+#footer_wrapper {
+	position: absolute;
+	bottom: 0px;
+	width: 100%;
+}
+
+#footer_help {
+	font-size: 18px;
+	max-width: 650px;
+	position: absolute;
+	bottom: 0px;
+	left: 0px;
+	max-width: 650px;
+	max-height: 350px;
+	max-width: 650px;
+	padding: 16px;
+}
+
+#footer_link {
+	padding: 8px;
+	font-size: 14px;
+	position: absolute;
+	bottom: 0px;
+	right: 0px;
+}
+
+a:link, a:visited {
+	text-decoration: none;
+	color: blue;
+	cursor: pointer;
+}
+
+.topnav {
+	background-color: #333;
+	overflow: hidden;
+}
+
+.topnav a {
+	float: left;
+	color: #f2f2f2;
+	text-align: center;
+	padding: 14px 16px;
+	text-decoration: none;
+	font-size: 17px;
+}
+
+/* Change the color of links on hover */
+.topnav a:hover {
+	background-color: #ddd;
+	color: black;
+}
+
+/* Add a color to the active/current link */
+.topnav a.active {
+	background-color: #4CAF50;
+	color: white;
+}
+
+#barcode_options {
+	height: 24px;
+	width: 100%;
+}
+
+#barcode_print_button {
+	width: 50%;
+	text-align: center;
+	float: left;
+}
+
+#barcode_print_content {
+	display: block;
+	height: 24px;
+}
+
+#barcode_download_button {
+	width: 50%;
+	text-align: center;
+	float: right;
+}
+
+#barcode_print_content {
+	display: block;
+	height: 24px;
+}

+ 84 - 0
src/main/resources/index.html

@@ -0,0 +1,84 @@
+<html>
+<head>
+<title>BarcodeAPI.org</title>
+
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+<meta name="keywords"
+	content="barcode, barcode app, barcode generator, barcode api, qr code, qr code generator, code 128, code128, ean8, eac-8, ean13, ean-13, codabar, download barcode, barcodes, pdf, pdf 417">
+<meta name="description"
+	content="Generate barcodes of nearly any type, scan it in the web browser, or download the generated images for free.">
+
+<link rel="icon" type="image/png" href="/favicon.png" />
+
+<script src="/index.js"></script>
+
+<link rel="stylesheet" type="text/css" href="/index.css" />
+</head>
+<body onload="loadHash()">
+	<div id="global_wrapper">
+
+		<div id="content_wrapper">
+
+			<div id="header"></div>
+
+			<div id="input_wrapper">
+				<div id="topnav" class="topnav">
+					<a id="type-auto" onclick="location.hash='auto'">Auto</a>
+					<a id="type-e" onclick="location.hash='e'">UPC-E</a>
+					<a id="type-a" onclick="location.hash='a'">UPC-A</a>
+					<a id="type-8" onclick="location.hash='8'">EAN-8</a>
+					<a id="type-13" onclick="location.hash='13'">EAN-13</a>
+					<a id="type-codabar" onclick="location.hash='codabar'">Codabar</a>
+					<a id="type-39" onclick="location.hash='39'">Code39</a>
+					<a id="type-128" onclick="location.hash='128'">Code128</a>
+					<a id="type-qr" onclick="location.hash='qr'">QR Code</a>
+					<a id="type-dm" onclick="location.hash='dm'">Data Matrix</a>
+					<a id="type-417" onclick="location.hash='417'">PDF 417</a>
+				</div>
+
+				<div id="frame">
+					<form onsubmit="return false;">
+
+						<input id="text" onkeyup="genCode()" type="text"
+							placeholder="a code for anything..." autocomplete="off"
+							maxlength="200" />
+					</form>
+					<div id="barcode_wrapper">
+						<img id="barcode_output" />
+					</div>
+
+					<div id="barcode_options">
+
+						<div id="barcode_print">
+							<a id="barcode_print_button" onclick="printCode()">
+								<span id="barcode_print_content">Print!</span>
+							</a>
+						</div>
+
+						<div id="barcode_download">
+							<a id="barcode_download_button" download>
+								<span id="barcode_download_content">Download!</span>
+							</a>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<div id="footer_wrapper">
+			<div id="footer_help">
+				<p>Generate barcodes of nearly any type, scan it in the web
+					browser, or download the generated images for free. BarcodeAPI.org
+					is an open source barcode server capable of generating a wide range
+					of barcodes over a RESTful HTTP server.</p>
+			</div>
+			<div id="footer_link">
+				<a href="https://git.mclarkdev.com/BarcodeAPI.org/server">Free,
+					Open Source</a> <br /> BarcodeAPI.org, 2018
+			</div>
+		</div>
+	</div>
+</body>
+</html>
+

+ 84 - 0
src/main/resources/index.js

@@ -0,0 +1,84 @@
+/**
+ * Call our method when the URL hash changes.
+ */
+window.onhashchange = loadHash;
+
+/**
+ * Called each time we should read the hash of the URL.
+ * 
+ * @returns
+ */
+function loadHash() {
+
+	// Get current hash ( minus # )
+	var hash = location.hash.substring(1);
+
+	// Remove the 'active' class from all topnav objects
+	var topnav = document.getElementById("topnav");
+	for ( var x in topnav.childNodes) {
+		var classList = topnav.childNodes[x].classList;
+		if (classList != null) {
+			classList.remove("active");
+		}
+	}
+
+	// Get element for selected hash
+	var selected = document.getElementById("type-" + hash);
+
+	// If no element for type
+	if (selected == null) {
+
+		// Default to AUTO
+		location.hash = "auto";
+		return;
+	}
+
+	// Mark selected item as active
+	selected.setAttribute("class", "active");
+
+	// Regenerate the code
+	genCode();
+}
+
+/**
+ * Called each time we should generate a new barcode.
+ * 
+ * @returns
+ */
+function genCode() {
+
+	// The API target
+	var url = location.origin + "/api";
+
+	// Get the requested type
+	var type = location.hash.substring(1);
+
+	// Get the requested text
+	var text = document.getElementById("text").value;
+	if (text == "") {
+
+		text = "Try Me!";
+	}
+
+	// Build URL with type
+	url = url + "/" + type;
+
+	// Build URL with encoded request
+	url += "/" + encodeURIComponent(text);
+
+	// Update download button
+	document.getElementById("barcode_download_button").setAttribute("href", url);
+
+	// Update IMG element source
+	document.getElementById("barcode_output").src = url;
+}
+
+function printCode() {
+
+	var content = document.getElementById("barcode_wrapper").innerHTML;
+
+	w = window.open();
+	w.document.write(content);
+	w.print();
+	w.close();
+}

BIN
src/main/resources/logo.png


+ 9 - 0
src/main/resources/robots.txt

@@ -0,0 +1,9 @@
+User-agent: *
+Allow: /index.html
+Allow: /help.html
+Allow: /index.css
+Allow: /index.js
+Allow: /logo.png
+Allow: /ping
+Allow: /robots.txt
+Allow: /api/

+ 34 - 0
src/test/java/com/amazon/barcodes/lambda/RequestHandlerTest.java

@@ -0,0 +1,34 @@
+package com.amazon.barcodes.lambda;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * A simple test harness for locally invoking your Lambda function handler.
+ */
+public class RequestHandlerTest {
+
+    private static final String SAMPLE_INPUT_STRING = "{\"foo\": \"bar\"}";
+    private static final String EXPECTED_OUTPUT_STRING = "{\"FOO\": \"BAR\"}";
+
+    @Test
+    public void testRequestHandler() throws IOException {
+        RequestHandler handler = new RequestHandler();
+
+        InputStream input = new ByteArrayInputStream(SAMPLE_INPUT_STRING.getBytes());;
+        OutputStream output = new ByteArrayOutputStream();
+
+        handler.handleRequest(input, output, null);
+
+        // TODO: validate output here if needed.
+        String sampleOutputString = output.toString();
+        System.out.println(sampleOutputString);
+        Assert.assertEquals(EXPECTED_OUTPUT_STRING, sampleOutputString);
+    }
+}

+ 136 - 0
src/test/java/com/amazon/barcodes/lambda/TestContext.java

@@ -0,0 +1,136 @@
+package com.amazon.barcodes.lambda;
+
+import com.amazonaws.services.lambda.runtime.ClientContext;
+import com.amazonaws.services.lambda.runtime.CognitoIdentity;
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.LambdaLogger;
+
+/**
+ * A simple mock implementation of the {@code Context} interface. Default
+ * values are stubbed out, and setters are provided so you can customize
+ * the context before passing it to your function.
+ */
+public class TestContext implements Context {
+
+    private String awsRequestId = "EXAMPLE";
+    private ClientContext clientContext;
+    private String functionName = "EXAMPLE";
+    private CognitoIdentity identity;
+    private String logGroupName = "EXAMPLE";
+    private String logStreamName = "EXAMPLE";
+    private LambdaLogger logger = new TestLogger();
+    private int memoryLimitInMB = 128;
+    private int remainingTimeInMillis = 15000;
+    private String functionVersion = "EXAMPLE";
+    private String invokedFunctionArn = "EXAMPLE";
+
+    @Override
+    public String getAwsRequestId() {
+        return awsRequestId;
+    }
+
+    public void setAwsRequestId(String value) {
+        awsRequestId = value;
+    }
+
+    @Override
+    public ClientContext getClientContext() {
+        return clientContext;
+    }
+
+    public void setClientContext(ClientContext value) {
+        clientContext = value;
+    }
+
+    @Override
+    public String getFunctionName() {
+        return functionName;
+    }
+
+    public void setFunctionName(String value) {
+        functionName = value;
+    }
+
+    @Override
+    public CognitoIdentity getIdentity() {
+        return identity;
+    }
+
+    public void setIdentity(CognitoIdentity value) {
+        identity = value;
+    }
+
+    @Override
+    public String getLogGroupName() {
+        return logGroupName;
+    }
+
+    public void setLogGroupName(String value) {
+        logGroupName = value;
+    }
+
+    @Override
+    public String getLogStreamName() {
+        return logStreamName;
+    }
+
+    public void setLogStreamName(String value) {
+        logStreamName = value;
+    }
+
+    @Override
+    public LambdaLogger getLogger() {
+        return logger;
+    }
+
+    public void setLogger(LambdaLogger value) {
+        logger = value;
+    }
+
+    @Override
+    public int getMemoryLimitInMB() {
+        return memoryLimitInMB;
+    }
+
+    public void setMemoryLimitInMB(int value) {
+        memoryLimitInMB = value;
+    }
+
+    @Override
+    public int getRemainingTimeInMillis() {
+        return remainingTimeInMillis;
+    }
+
+    public void setRemainingTimeInMillis(int value) {
+        remainingTimeInMillis = value;
+    }
+
+    @Override
+    public String getFunctionVersion() {
+        return functionVersion;
+    }
+
+    public void setFunctionVersion(String value) {
+        functionVersion = value;
+    }
+
+    @Override
+    public String getInvokedFunctionArn() {
+        return invokedFunctionArn;
+    }
+
+    public void setInvokedFunctionArn(String value) {
+        invokedFunctionArn = value;
+    }
+
+    /**
+     * A simple {@code LambdaLogger} that prints everything to stderr.
+     */
+    private static class TestLogger implements LambdaLogger {
+
+        @Override
+        public void log(String message) {
+            System.err.println(message);
+        }
+    }
+}

+ 233 - 0
src/test/java/com/amazon/barcodes/lambda/TestUtils.java

@@ -0,0 +1,233 @@
+package com.amazon.barcodes.lambda;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.joda.time.tz.FixedDateTimeZone;
+
+import com.amazonaws.services.dynamodbv2.model.AttributeValue;
+import com.amazonaws.services.dynamodbv2.model.Record;
+import com.amazonaws.services.dynamodbv2.model.StreamRecord;
+import com.amazonaws.services.lambda.runtime.events.DynamodbEvent;
+import com.amazonaws.services.lambda.runtime.events.S3Event;
+import com.amazonaws.services.lambda.runtime.events.SNSEvent;
+import com.amazonaws.services.s3.event.S3EventNotification;
+import com.amazonaws.util.IOUtils;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+/**
+ * Helper utilities for testing Lambda functions.
+ */
+public class TestUtils {
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+    private static final ObjectMapper snsEventMapper = new ObjectMapper();
+    private static final ObjectMapper dynamodbEventMapper = new ObjectMapper();
+
+    static {
+        mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
+        mapper.setPropertyNamingStrategy(new UpperCaseRecordsPropertyNamingStrategy());
+        mapper.registerModule(new TestJacksonMapperModule());
+
+        snsEventMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
+        snsEventMapper.setPropertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE);
+        snsEventMapper.registerModule(new TestJacksonMapperModule());
+
+        dynamodbEventMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
+        dynamodbEventMapper.setPropertyNamingStrategy(new UpperCaseRecordsPropertyNamingStrategy());
+        dynamodbEventMapper.registerModule(new TestJacksonMapperModule());
+        dynamodbEventMapper.addMixIn(Record.class, DynamodbEventMixin.RecordMixin.class);
+        dynamodbEventMapper.addMixIn(StreamRecord.class, DynamodbEventMixin.StreamRecordMixin.class);
+        dynamodbEventMapper.addMixIn(AttributeValue.class, DynamodbEventMixin.AttributeValueMixIn.class);
+    }
+
+    private static final DateTimeFormatter dateTimeFormatter =
+            ISODateTimeFormat.dateTime()
+                        .withZone(new FixedDateTimeZone("GMT", "GMT", 0, 0));
+
+    /**
+     * Helper method that parses a JSON object from a resource on the classpath
+     * as an instance of the provided type.
+     *
+     * @param resource the path to the resource (relative to this class)
+     * @param clazz the type to parse the JSON into
+     */
+    public static <T> T parse(String resource, Class<T> clazz)
+            throws IOException {
+
+        InputStream stream = TestUtils.class.getResourceAsStream(resource);
+        try {
+            if (clazz == S3Event.class) {
+                String json = IOUtils.toString(stream);
+                S3EventNotification event = S3EventNotification.parseJson(json);
+
+                @SuppressWarnings("unchecked")
+                T result = (T) new S3Event(event.getRecords());
+                return result;
+
+            } else if (clazz == SNSEvent.class) {
+                return snsEventMapper.readValue(stream, clazz);
+            } else if (clazz == DynamodbEvent.class) {
+                return dynamodbEventMapper.readValue(stream, clazz);
+            } else {
+                return mapper.readValue(stream, clazz);
+            }
+        } finally {
+            stream.close();
+        }
+    }
+
+    private static class TestJacksonMapperModule extends SimpleModule {
+
+        private static final long serialVersionUID = 1L;
+
+        public TestJacksonMapperModule() {
+            super("TestJacksonMapperModule");
+
+            super.addSerializer(DateTime.class, new DateTimeSerializer());
+            super.addDeserializer(DateTime.class, new DateTimeDeserializer());
+        }
+    }
+
+    private static class DateTimeSerializer extends JsonSerializer<DateTime> {
+
+        @Override
+        public void serialize(
+                DateTime value,
+                JsonGenerator gen,
+                SerializerProvider provider) throws IOException {
+
+            gen.writeString(dateTimeFormatter.print(value));
+        }
+    }
+
+    private static class DateTimeDeserializer
+            extends JsonDeserializer<DateTime> {
+
+        @Override
+        public DateTime deserialize(
+                JsonParser parser,
+                DeserializationContext context) throws IOException {
+
+            return dateTimeFormatter.parseDateTime(parser.getText());
+        }
+    }
+
+    private static class UpperCaseRecordsPropertyNamingStrategy
+            extends PropertyNamingStrategy.PropertyNamingStrategyBase {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        public String translate(String propertyName) {
+            if (propertyName.equals("records")) {
+                return "Records";
+            }
+            return propertyName;
+        }
+    }
+
+    private static interface DynamodbEventMixin {
+        public static final String L = "L";
+        public static final String M = "M";
+        public static final String BS = "BS";
+        public static final String NS = "NS";
+        public static final String SS = "SS";
+        public static final String BOOL = "BOOL";
+        public static final String NULL = "NULL";
+        public static final String B = "B";
+        public static final String N = "N";
+        public static final String S = "S";
+        public static final String OLD_IMAGE = "OldImage";
+        public static final String NEW_IMAGE = "NewImage";
+        public static final String STREAM_VIEW_TYPE = "StreamViewType";
+        public static final String SEQUENCE_NUMBER = "SequenceNumber";
+        public static final String SIZE_BYTES = "SizeBytes";
+        public static final String KEYS = "Keys";
+        public static final String AWS_REGION = "awsRegion";
+        public static final String DYNAMODB = "dynamodb";
+        public static final String EVENT_ID = "eventID";
+        public static final String EVENT_NAME = "eventName";
+        public static final String EVENT_SOURCE = "eventSource";
+        public static final String EVENT_VERSION = "eventVersion";
+        public static final String EVENT_SOURCE_ARN = "eventSourceARN";
+        public static final String APPROXIMATE_CREATION_DATE_TIME = "ApproximateCreationDateTime";
+
+        @JsonProperty(value = "Records")
+        public List<?> getRecords();
+
+        static interface RecordMixin {
+            @JsonProperty(AWS_REGION) public String getAwsRegion();
+            @JsonProperty(AWS_REGION) public void setAwsRegion(String awsRegion);
+            @JsonProperty(DYNAMODB) public Object getDynamodb();
+            @JsonProperty(DYNAMODB) public void setDynamodb(Object dynamodb);
+            @JsonProperty(EVENT_ID) public String getEventID();
+            @JsonProperty(EVENT_ID) public void setEventID(String eventID);
+            @JsonProperty(EVENT_NAME) public String getEventName();
+            @JsonProperty(EVENT_NAME) public void setEventName(String eventName);
+            @JsonProperty(EVENT_SOURCE) public String getEventSource();
+            @JsonProperty(EVENT_SOURCE) public void setEventSource(String eventSource);
+            @JsonProperty(EVENT_VERSION) public String getEventVersion();
+            @JsonProperty(EVENT_VERSION) public void setEventVersion(String eventVersion);
+            @JsonProperty(EVENT_SOURCE_ARN) public String getEventSourceArn();
+            @JsonProperty(EVENT_SOURCE_ARN) public void setEventSourceArn(String eventSourceArn);
+        }
+
+        static interface StreamRecordMixin {
+
+            @JsonProperty(KEYS) public Map<String, Object> getKeys();
+            @JsonProperty(KEYS) public void setKeys(Map<String, Object> keys);
+            @JsonProperty(SIZE_BYTES) public Long getSizeBytes();
+            @JsonProperty(SIZE_BYTES) public void setSizeBytes(Long sizeBytes);
+            @JsonProperty(SEQUENCE_NUMBER) public String getSequenceNumber();
+            @JsonProperty(SEQUENCE_NUMBER) public void setSequenceNumber(String sequenceNumber);
+            @JsonProperty(STREAM_VIEW_TYPE) public String getStreamViewType();
+            @JsonProperty(STREAM_VIEW_TYPE) public void setStreamViewType(String streamViewType);
+            @JsonProperty(NEW_IMAGE) public Map<String, Object> getNewImage();
+            @JsonProperty(NEW_IMAGE) public void setNewImage(Map<String, Object> newImage);
+            @JsonProperty(OLD_IMAGE) public Map<String, Object> getOldImage();
+            @JsonProperty(OLD_IMAGE) public void setOldImage(Map<String, Object> oldImage);
+            @JsonProperty(APPROXIMATE_CREATION_DATE_TIME) public Date getApproximateCreationDateTime();
+            @JsonProperty(APPROXIMATE_CREATION_DATE_TIME) public void setApproximateCreationDateTime(Date approximateCreationDateTime);
+        }
+
+        static interface AttributeValueMixIn {
+            @JsonProperty(S) public String getS();
+            @JsonProperty(S) public void setS(String s);
+            @JsonProperty(N) public String getN();
+            @JsonProperty(N) public void setN(String n);
+            @JsonProperty(B) public ByteBuffer getB();
+            @JsonProperty(B) public void setB(ByteBuffer b);
+            @JsonProperty(NULL) public Boolean isNULL();
+            @JsonProperty(NULL) public void setNULL(Boolean nU);
+            @JsonProperty(BOOL) public Boolean getBOOL();
+            @JsonProperty(BOOL) public void setBOOL(Boolean bO);
+            @JsonProperty(SS) public List<String> getSS();
+            @JsonProperty(SS) public void setSS(List<String> sS);
+            @JsonProperty(NS) public List<String> getNS();
+            @JsonProperty(NS) public void setNS(List<String> nS);
+            @JsonProperty(BS) public List<String> getBS();
+            @JsonProperty(BS) public void setBS(List<String> bS);
+            @JsonProperty(M) public Map<String, Object> getM();
+            @JsonProperty(M) public void setM(Map<String, Object> val);
+            @JsonProperty(L) public List<Object> getL();
+            @JsonProperty(L) public void setL(List<Object> val);
+        }
+    }
+}