├── .dict
├── resources
├── .htaccess
├── favicon.ico
├── ext
│ ├── favicon.png
│ ├── 3p
│ │ └── IBM-Plex-Mono.ttf
│ ├── js
│ │ ├── abuse.js
│ │ ├── keyboard.js
│ │ ├── nonprinting.js
│ │ ├── admin.js
│ │ ├── types.js
│ │ ├── bulk.js
│ │ ├── type.js
│ │ ├── session.js
│ │ ├── limits.js
│ │ └── decode.js
│ └── css
│ │ └── multi.css
├── robots.txt
├── multi.html
├── abuse.html
├── nonprinting.html
├── license.html
├── types.html
├── admin.html
├── support.html
├── type.html
└── limits.html
├── src
├── main
│ ├── resources
│ │ ├── app.version
│ │ └── strings
│ │ │ └── codes.en_US.properties
│ └── java
│ │ └── org
│ │ └── barcodeapi
│ │ ├── server
│ │ ├── core
│ │ │ ├── RateLimitException.java
│ │ │ ├── BackgroundTask.java
│ │ │ ├── GenerationException.java
│ │ │ ├── TypeSelector.java
│ │ │ ├── GeneratorPoolController.java
│ │ │ ├── CodeTypes.java
│ │ │ └── CodeGenerators.java
│ │ ├── gen
│ │ │ ├── types
│ │ │ │ ├── Ean8Generator.java
│ │ │ │ ├── UPCAGenerator.java
│ │ │ │ ├── UPCEGenerator.java
│ │ │ │ ├── Ean13Generator.java
│ │ │ │ ├── ITF14Generator.java
│ │ │ │ ├── Code39Generator.java
│ │ │ │ ├── PDF417Generator.java
│ │ │ │ ├── CodabarGenerator.java
│ │ │ │ ├── Code128Generator.java
│ │ │ │ ├── RoyalMailGenerator.java
│ │ │ │ ├── USPSMailGenerator.java
│ │ │ │ ├── AztecGenerator.java
│ │ │ │ ├── QRCodeGenerator.java
│ │ │ │ ├── DataMatrixGenerator.java
│ │ │ │ └── AprilTagGenerator.java
│ │ │ ├── CodeGenerator.java
│ │ │ ├── impl
│ │ │ │ ├── DefaultZXingProvider.java
│ │ │ │ └── DefaultBarcode4JProvider.java
│ │ │ └── BarcodeCanvasProvider.java
│ │ ├── api
│ │ │ ├── LimiterHandler.java
│ │ │ ├── PlansHandler.java
│ │ │ ├── InfoHandler.java
│ │ │ ├── SessionHandler.java
│ │ │ ├── TypeHandler.java
│ │ │ └── StaticHandler.java
│ │ ├── tasks
│ │ │ ├── ShareCleanupTask.java
│ │ │ ├── LimiterCleanupTask.java
│ │ │ ├── SessionCleanupTask.java
│ │ │ ├── BarcodeCleanupTask.java
│ │ │ ├── LimiterMintingTask.java
│ │ │ └── StatsDumpTask.java
│ │ ├── admin
│ │ │ ├── SubscriberReloadHandler.java
│ │ │ ├── SessionFlushHandler.java
│ │ │ ├── ServerStatsHandler.java
│ │ │ ├── ShareListHandler.java
│ │ │ ├── SessionListHandler.java
│ │ │ ├── CacheFlushHandler.java
│ │ │ ├── LimiterListHandler.java
│ │ │ ├── LimiterFlushHandler.java
│ │ │ └── CacheDumpHandler.java
│ │ └── cache
│ │ │ ├── LimiterCache.java
│ │ │ ├── CachedShare.java
│ │ │ ├── Subscriber.java
│ │ │ ├── CachedSession.java
│ │ │ └── CachedBarcode.java
│ │ ├── core
│ │ ├── utils
│ │ │ └── AuthUtils.java
│ │ ├── Config.java
│ │ └── ServerRuntime.java
│ │ └── Launcher.java
└── test
│ └── java
│ └── org
│ └── barcodeapi
│ └── test
│ ├── gen
│ ├── types
│ │ ├── TestAprilTag.java
│ │ ├── TestEan8.java
│ │ ├── TestUPCE.java
│ │ ├── TestUPCA.java
│ │ └── TestEan13.java
│ ├── TestBlacklist.java
│ ├── TestSpecial.java
│ ├── TestOutputFormat.java
│ └── TestURLs.java
│ ├── api
│ ├── TestServerStatsHandler.java
│ ├── TestServerTypeHandler.java
│ ├── TestPlansHandler.java
│ ├── TestInfoHandler.java
│ ├── TestServerRoot.java
│ ├── TestServerHeaders.java
│ ├── TestSessionHandler.java
│ └── TestLimiterHandler.java
│ ├── core
│ ├── TestAuthUtils.java
│ └── TestBarcodeOptions.java
│ └── cust
│ └── TestMiscCustomer.java
├── config
├── community
│ ├── admins.json
│ ├── blacklist.json
│ ├── subscribers.json
│ ├── plans.json
│ └── app.json
├── apptest
│ ├── blacklist.json
│ ├── admins.json
│ ├── subscribers.json
│ ├── plans.json
│ └── app.json
└── types
│ ├── AprilTag.json
│ ├── Aztec.json
│ ├── QRCode.json
│ ├── DataMatrix.json
│ ├── PDF417.json
│ ├── RoyalMail.json
│ ├── USPSMail.json
│ ├── CODABAR.json
│ ├── EAN8.json
│ ├── ITF14.json
│ ├── Code39.json
│ ├── EAN13.json
│ ├── UPC_E.json
│ ├── UPC_A.json
│ └── Code128.json
├── .env
├── .gitignore
├── Dockerfile
├── ThirdParty.md
├── README.md
├── .gitea
└── workflows
│ └── build.yaml
└── .github
└── workflows
└── docker-publish.yml
/.dict:
--------------------------------------------------------------------------------
1 | barcode
2 |
--------------------------------------------------------------------------------
/resources/.htaccess:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/app.version:
--------------------------------------------------------------------------------
1 | 0
--------------------------------------------------------------------------------
/config/community/admins.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "changeme"
3 | }
4 |
--------------------------------------------------------------------------------
/config/community/blacklist.json:
--------------------------------------------------------------------------------
1 | {
2 | "blacklist": [
3 | ]
4 | }
--------------------------------------------------------------------------------
/config/community/subscribers.json:
--------------------------------------------------------------------------------
1 | {
2 | "subscribers": [
3 | ]
4 | }
--------------------------------------------------------------------------------
/config/apptest/blacklist.json:
--------------------------------------------------------------------------------
1 | {
2 | "blacklist": [
3 | "^_tstblk_$"
4 | ]
5 | }
--------------------------------------------------------------------------------
/resources/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BarcodeAPI/server/HEAD/resources/favicon.ico
--------------------------------------------------------------------------------
/resources/ext/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BarcodeAPI/server/HEAD/resources/ext/favicon.png
--------------------------------------------------------------------------------
/config/apptest/admins.json:
--------------------------------------------------------------------------------
1 | {
2 | "admin": "5E884898DA28047151D0E56F8DC6292773603D0D6AABBDD62A11EF721D1542D8"
3 | }
4 |
--------------------------------------------------------------------------------
/resources/ext/3p/IBM-Plex-Mono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BarcodeAPI/server/HEAD/resources/ext/3p/IBM-Plex-Mono.ttf
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # Override default log name (server)
2 | #LOG_NAME=server
3 |
4 | # List of log streams
5 | LOG_STREAMS=console:/;file:/
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath
2 | .project
3 | .settings/
4 | /bin/
5 | /target/
6 | /cache/
7 | /logs/
8 | /uploads/
9 | /cache/
10 | /config/production/
11 | *.snap
12 |
--------------------------------------------------------------------------------
/config/community/plans.json:
--------------------------------------------------------------------------------
1 | {
2 | "free": {
3 | "limit": -1,
4 | "enforce": false,
5 | "batch": 5000,
6 | "description": "Self hosted community server.",
7 | "support": "Contact server admin for details."
8 | },
9 | "paid": []
10 | }
--------------------------------------------------------------------------------
/resources/ext/js/abuse.js:
--------------------------------------------------------------------------------
1 | //
2 | // BarcodeAPI.org, 2017-2025
3 | // abuse.js // abuse.html
4 | //
5 |
6 | window.addEventListener("load", init);
7 |
8 | function init() {
9 | trackingEvent("AppEvents", "AppLoad", "Abuse", setupMillis);
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/core/RateLimitException.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.core;
2 |
3 | /**
4 | * RateLimitException.java
5 | *
6 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
7 | */
8 | public class RateLimitException extends RuntimeException {
9 | private static final long serialVersionUID = 20241123L;
10 | }
11 |
--------------------------------------------------------------------------------
/resources/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /about.html
3 | Allow: /api.html
4 | Allow: /bulk.html
5 | Allow: /decode.html
6 | Allow: /index.html
7 | Allow: /license.html
8 | Allow: /limits.html
9 | Allow: /nonprinting.html
10 | Allow: /support.html
11 | Allow: /types.html
12 | Disallow: /multi.html
13 | Disallow: /type.html
14 | Disallow: /session.html
15 | Disallow: /api/
16 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/gen/types/TestAprilTag.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.gen.types;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.barcodeapi.server.core.GenerationException.ExceptionType;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | public class TestAprilTag extends ServerTestBase {
9 |
10 | @Test
11 | public void testAprilTag_UnsupportedFamily() {
12 |
13 | apiGet("april/tagWoah:1");
14 |
15 | Assert.assertEquals("Response Code", //
16 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/Ean8Generator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.upcean.EAN8Bean;
6 |
7 | /**
8 | * Ean8Generator.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
11 | */
12 | public class Ean8Generator extends DefaultBarcode4JProvider {
13 |
14 | public Ean8Generator(CodeType codeType) {
15 |
16 | // Setup EAN8 generator
17 | super(codeType, new EAN8Bean());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/UPCAGenerator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.upcean.UPCABean;
6 |
7 | /**
8 | * UPCAGenerator.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
11 | */
12 | public class UPCAGenerator extends DefaultBarcode4JProvider {
13 |
14 | public UPCAGenerator(CodeType codeType) {
15 |
16 | // Setup UPC-A generator
17 | super(codeType, new UPCABean());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/UPCEGenerator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.upcean.UPCEBean;
6 |
7 | /**
8 | * UPCEGenerator.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
11 | */
12 | public class UPCEGenerator extends DefaultBarcode4JProvider {
13 |
14 | public UPCEGenerator(CodeType codeType) {
15 |
16 | // Setup UPC-E generator
17 | super(codeType, new UPCEBean());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/Ean13Generator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.upcean.EAN13Bean;
6 |
7 | /**
8 | * Ean13Generator.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
11 | */
12 | public class Ean13Generator extends DefaultBarcode4JProvider {
13 |
14 | public Ean13Generator(CodeType codeType) {
15 |
16 | // Setup EAN13 generator
17 | super(codeType, new EAN13Bean());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/ITF14Generator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.int2of5.ITF14Bean;
6 |
7 | /**
8 | * ITF14Generator.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
11 | */
12 | public class ITF14Generator extends DefaultBarcode4JProvider {
13 |
14 | public ITF14Generator(CodeType codeType) {
15 |
16 | // Setup ITF14 generator
17 | super(codeType, new ITF14Bean());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/Code39Generator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.code39.Code39Bean;
6 |
7 | /**
8 | * Code39Generator.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
11 | */
12 | public class Code39Generator extends DefaultBarcode4JProvider {
13 |
14 | public Code39Generator(CodeType codeType) {
15 |
16 | // Setup Code39 generator
17 | super(codeType, new Code39Bean());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/PDF417Generator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.pdf417.PDF417Bean;
6 |
7 | /**
8 | * PDF417Generator.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
11 | */
12 | public class PDF417Generator extends DefaultBarcode4JProvider {
13 |
14 | public PDF417Generator(CodeType codeType) {
15 |
16 | // Setup PDF417 generator
17 | super(codeType, new PDF417Bean());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/config/apptest/subscribers.json:
--------------------------------------------------------------------------------
1 | {
2 | "subscribers": [
3 | {
4 | "customer": "AppTest",
5 | "subscribed": 1735693200000,
6 | "active": true,
7 | "enforce": true,
8 | "limit": 1000,
9 | "batch": 100,
10 | "ips": [],
11 | "keys": [
12 | "appTest"
13 | ],
14 | "apps": []
15 | },
16 | {
17 | "customer": "BarcodeAPI.org",
18 | "subscribed": 1735693200000,
19 | "active": true,
20 | "enforce": false,
21 | "limit": -1,
22 | "batch": 500,
23 | "ips": [
24 | "127.0.0.1"
25 | ],
26 | "keys": [],
27 | "apps": []
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/CodabarGenerator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.codabar.CodabarBean;
6 |
7 | /**
8 | * CodabarGenerator.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
11 | */
12 | public class CodabarGenerator extends DefaultBarcode4JProvider {
13 |
14 | public CodabarGenerator(CodeType codeType) {
15 |
16 | // Setup Codabar generator
17 | super(codeType, new CodabarBean());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/Code128Generator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.code128.Code128Bean;
6 |
7 | /**
8 | * Code128Generator.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
11 | */
12 | public class Code128Generator extends DefaultBarcode4JProvider {
13 |
14 | public Code128Generator(CodeType codeType) {
15 |
16 | // Setup Code128 generator
17 | super(codeType, new Code128Bean());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/config/apptest/plans.json:
--------------------------------------------------------------------------------
1 | {
2 | "free": {
3 | "limit": 1000,
4 | "enforce": true,
5 | "batch": 25,
6 | "description": "System Test: Up to 1,000 tokens per day.",
7 | "support": "Not safe for Humans."
8 | },
9 | "paid": [
10 | {
11 | "name": "AppTest Subscribers",
12 | "description": "System Test: More tokens for paying users.",
13 | "support": "Application is provided as-is.",
14 | "price": [
15 | {
16 | "name": "$10/mo",
17 | "link": false
18 | },
19 | {
20 | "name": "$100/yr",
21 | "link": false
22 | }
23 | ]
24 | }
25 | ]
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/resources/ext/js/keyboard.js:
--------------------------------------------------------------------------------
1 | //
2 | // BarcodeAPI.org, 2017-2025
3 | // keyboard.js // keyboard.html
4 | //
5 |
6 | function init() {
7 | var btns = document.getElementsByClassName("button");
8 | for (var x in btns) {
9 | if (btns[x].addEventListener) {
10 | btns[x].addEventListener('click', addChar);
11 | }
12 | }
13 | }
14 |
15 | function addChar(event) {
16 |
17 | var val = event.target.value;
18 | window.opener.addCharacter(val)
19 | }
20 |
21 | function _help() {
22 | window.opener.actionNonprintingHelp();
23 | }
24 |
25 | function _close() {
26 | window.opener.actionCloseKeyboard();
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/RoyalMailGenerator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.fourstate.RoyalMailCBCBean;
6 |
7 | /**
8 | * RoyalMailGenerator.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
11 | */
12 | public class RoyalMailGenerator extends DefaultBarcode4JProvider {
13 |
14 | public RoyalMailGenerator(CodeType codeType) {
15 |
16 | // Setup RoyalMail generator
17 | super(codeType, new RoyalMailCBCBean());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 |
2 | # Build
3 | FROM maven:3-openjdk-11-slim AS build
4 | COPY src /home/app/src
5 | COPY pom.xml /home/app
6 | RUN mvn clean compile assembly:single -f /home/app/pom.xml
7 |
8 | # Package
9 | FROM openjdk:11-jre-slim
10 | RUN apt update && apt install -y libfreetype-dev && rm -rf /var/lib/apt/lists/*
11 | COPY --from=build /home/app/target/server.jar /usr/local/lib/server.jar
12 | COPY config /config
13 | COPY resources /resources
14 |
15 | # Mounts
16 | VOLUME /cache
17 | VOLUME /config
18 |
19 | # Ports
20 | EXPOSE 8080
21 |
22 | # Command
23 | CMD [ "java", "-jar", "/usr/local/lib/server.jar", "--language", "en_US", "--port", "8080" ]
24 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/USPSMailGenerator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.fourstate.USPSIntelligentMailBean;
6 |
7 | /**
8 | * USPSMailGenerator.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
11 | */
12 | public class USPSMailGenerator extends DefaultBarcode4JProvider {
13 |
14 | public USPSMailGenerator(CodeType codeType) {
15 |
16 | // Setup USPS-Mail generator
17 | super(codeType, new USPSIntelligentMailBean());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/AztecGenerator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultZXingProvider;
5 |
6 | import com.google.zxing.BarcodeFormat;
7 | import com.google.zxing.aztec.AztecWriter;
8 |
9 | /**
10 | * AztecGenerator.java
11 | *
12 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
13 | */
14 | public class AztecGenerator extends DefaultZXingProvider {
15 |
16 | public AztecGenerator(CodeType codeType) {
17 |
18 | // Setup Aztec generator
19 | super(codeType, BarcodeFormat.AZTEC, new AztecWriter());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/QRCodeGenerator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultZXingProvider;
5 |
6 | import com.google.zxing.BarcodeFormat;
7 | import com.google.zxing.qrcode.QRCodeWriter;
8 |
9 | /**
10 | * QRCodeGenerator.java
11 | *
12 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
13 | */
14 | public class QRCodeGenerator extends DefaultZXingProvider {
15 |
16 | public QRCodeGenerator(CodeType codeType) {
17 |
18 | // Setup QR generator
19 | super(codeType, BarcodeFormat.QR_CODE, new QRCodeWriter());
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/api/TestServerStatsHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.api;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.eclipse.jetty.http.HttpStatus;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | /**
9 | * TestServerStatsHandler.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
12 | */
13 | public class TestServerStatsHandler extends ServerTestBase {
14 |
15 | @Test
16 | public void testServer_TestStatsEndpoind() {
17 |
18 | serverGet("/server/stats/");
19 |
20 | Assert.assertEquals("Response Code", //
21 | HttpStatus.UNAUTHORIZED_401, getResponseCode());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/core/utils/AuthUtils.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.core.utils;
2 |
3 | import com.mclarkdev.tools.libextras.LibExtrasHashes;
4 |
5 | /**
6 | * AuthUtils.java
7 | *
8 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
9 | */
10 | public class AuthUtils {
11 |
12 | public static void main(String[] args) {
13 |
14 | System.out.println(//
15 | formatUser(args[0], passHash(args[1])));
16 | }
17 |
18 | public static String passHash(String pass) {
19 | return LibExtrasHashes.sumSHA256(pass.getBytes());
20 | }
21 |
22 | public static String formatUser(String user, String hash) {
23 | return String.format("%s:%s", user, hash);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/gen/TestBlacklist.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.gen;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.barcodeapi.server.core.GenerationException;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | /**
9 | * TestBlacklist.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
12 | */
13 | public class TestBlacklist extends ServerTestBase {
14 |
15 | @Test
16 | public void testBlacklist_testBlacklistItem() {
17 |
18 | apiGet("_tstblk_");
19 |
20 | Assert.assertEquals("Response Code", //
21 | GenerationException.ExceptionType.BLACKLIST.getStatusCode(), getResponseCode());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ThirdParty.md:
--------------------------------------------------------------------------------
1 | ## Third-Party
2 |
3 | BarcodeAPI.org is only made possible with the use of third-party software.
4 |
5 | **Jetty, [Apache 2.0](https://www.eclipse.org/jetty/licenses.html)**
6 |
7 | * The BarcodeAPI server was built around the [Jetty](https://www.eclipse.org/jetty/) web server framework.
8 |
9 | **Barcode4J, [Apache 2.0](http://barcode4j.sourceforge.net/#Introduction)**
10 |
11 | * [Barcode4J](http://barcode4j.sourceforge.net/) is an open source barcode generator; it is used for the generation of the following code types:
12 |
13 | **ZXing, [Apache 2.0](https://github.com/zxing/zxing/blob/master/LICENSE)**
14 |
15 | * [ZXing](https://github.com/zxing/zxing/) is a barcode processing library that makes QR code generation possible.
16 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/core/TestAuthUtils.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.core;
2 |
3 | import org.barcodeapi.core.utils.AuthUtils;
4 | import org.barcodeapi.server.ServerTestBase;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | /**
9 | * TestAuthUtils.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
12 | */
13 | public class TestAuthUtils extends ServerTestBase {
14 |
15 | @Test
16 | public void TestAuthUtils_TestPassGen() {
17 |
18 | String testUserHash = AuthUtils.passHash("test");
19 | String testUserAuth = AuthUtils.formatUser("testUser", testUserHash);
20 |
21 | Assert.assertEquals("AuthString", //
22 | "testUser:9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08", testUserAuth);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/config/apptest/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "types": [
3 | "UPC_E",
4 | "UPC_A",
5 | "EAN8",
6 | "EAN13",
7 | "CODABAR",
8 | "ITF14",
9 | "Code39",
10 | "Code128",
11 | "Aztec",
12 | "QRCode",
13 | "DataMatrix",
14 | "PDF417",
15 | "USPSMail",
16 | "RoyalMail",
17 | "AprilTag"
18 | ],
19 | "tasks": [],
20 | "cache": {
21 | "_snapshots": "./cache/",
22 | "barcode": {
23 | "life": 10080,
24 | "shortLife": 240
25 | },
26 | "session": {
27 | "life": 20160,
28 | "shortLife": 60
29 | },
30 | "limiter": {
31 | "life": 4320,
32 | "shortLife": 360
33 | },
34 | "share": {
35 | "life": 129600,
36 | "shortLife": 360
37 | }
38 | },
39 | "client": {
40 | "cacheStatic": 1440,
41 | "cacheBarcode": 1440
42 | }
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/gen/TestSpecial.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.gen;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.eclipse.jetty.http.HttpStatus;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | /**
9 | * TestSpecial.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
12 | */
13 | public class TestSpecial extends ServerTestBase {
14 |
15 | @Test
16 | public void testSpecial_Price() {
17 |
18 | apiGet("$12.34");
19 |
20 | Assert.assertEquals("Response Code", //
21 | HttpStatus.OK_200, getResponseCode());
22 |
23 | Assert.assertEquals("Code Type", //
24 | "Code39", getHeader("X-Barcode-Type"));
25 |
26 | Assert.assertEquals("Code Data", //
27 | encode("$12.34"), getHeader("X-Barcode-Content"));
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/DataMatrixGenerator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.gen.impl.DefaultBarcode4JProvider;
5 | import org.krysalis.barcode4j.impl.datamatrix.DataMatrixBean;
6 | import org.krysalis.barcode4j.impl.datamatrix.SymbolShapeHint;
7 |
8 | /**
9 | * DataMatrixGenerator.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
12 | */
13 | public class DataMatrixGenerator extends DefaultBarcode4JProvider {
14 |
15 | public DataMatrixGenerator(CodeType codeType) {
16 | super(codeType, initBean());
17 | }
18 |
19 | private static DataMatrixBean initBean() {
20 | DataMatrixBean bean = new DataMatrixBean();
21 | bean.setShape(SymbolShapeHint.FORCE_SQUARE);
22 | return bean;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/resources/ext/js/nonprinting.js:
--------------------------------------------------------------------------------
1 | //
2 | // BarcodeAPI.org, 2017-2025
3 | // nonprinting.js // nonprinting.html
4 | //
5 |
6 | window.addEventListener("load", function() {
7 |
8 | document.supported = document.getElementById("types_supported");
9 |
10 | fetch('/type/')
11 | .then(response => {
12 | return response.json();
13 | })
14 | .then(data => {
15 | for (var x in data) {
16 | if (data[x].nonprinting) {
17 | addSupported(data[x]);
18 | }
19 | }
20 | });
21 | });
22 |
23 | function addSupported(type) {
24 |
25 | var dType = document.createElement("a");
26 | dType.innerHTML = type.display;
27 | dType.href = ("index.html#" + type.targets[0]);
28 |
29 | var iType = document.createElement("li");
30 | iType.appendChild(dType);
31 |
32 | document.supported.appendChild(iType);
33 | }
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://barcodeapi.org/)
2 |
3 | # BarcodeAPI.org
4 |
5 | The BarcodeAPI.org server was designed to provide an easy-to-use barcode image generation API served through the HTTP protocol.
6 |
7 | By serving images with this approach, any application where barcode generation libraries may not exist or resources may be limited can continue to serve barcode images, provided network access is available.
8 |
9 | The WebUI is also designed to be responsive, allowing users to generate barcodes that can be scanned directly from their web browser or downloaded after testing.
10 |
11 | Comprehensive documentation regarding server capabilities is built directly into the server, ensuring users have access to all necessary information for efficient usage.
12 |
13 | For detailed instructions and examples, check out our new [User Manual](https://barcodeapi.org/api.html).
14 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/api/LimiterHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.api;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import org.barcodeapi.server.core.RequestContext;
8 | import org.barcodeapi.server.core.RestHandler;
9 | import org.json.JSONObject;
10 |
11 | /**
12 | * SessionDetailsHandler.java
13 | *
14 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
15 | */
16 | public class LimiterHandler extends RestHandler {
17 |
18 | public LimiterHandler() {
19 | super();
20 | }
21 |
22 | @Override
23 | protected void onRequest(RequestContext c, HttpServletResponse r) throws IOException {
24 |
25 | // Print response to client
26 | r.setStatus(HttpServletResponse.SC_OK);
27 | r.setContentType("application/json");
28 | r.getOutputStream().println(//
29 | c.getLimiter().asJSON().toString(4));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/tasks/ShareCleanupTask.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.tasks;
2 |
3 | import org.barcodeapi.server.cache.ObjectCache;
4 | import org.barcodeapi.server.core.BackgroundTask;
5 |
6 | import com.mclarkdev.tools.liblog.LibLog;
7 |
8 | /**
9 | * ShareCleanupTask.java
10 | *
11 | * A background task which periodically removes stale shares from the cache.
12 | * Additionally saves a cache snapshot to disk, to be used on server restart.
13 | *
14 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
15 | */
16 | public class ShareCleanupTask extends BackgroundTask {
17 |
18 | private final ObjectCache shares = //
19 | ObjectCache.getCache(ObjectCache.CACHE_SHARE);
20 |
21 | public ShareCleanupTask() {
22 | super();
23 | }
24 |
25 | @Override
26 | public void onRun() {
27 |
28 | // Cleanup Share cache
29 | int removed = shares.expireOldObjects(), active = shares.count();
30 | LibLog._clogF("I2611", removed, active);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/tasks/LimiterCleanupTask.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.tasks;
2 |
3 | import org.barcodeapi.server.cache.ObjectCache;
4 | import org.barcodeapi.server.core.BackgroundTask;
5 |
6 | import com.mclarkdev.tools.liblog.LibLog;
7 |
8 | /**
9 | * LimiterCleanupTask.java
10 | *
11 | * A background task which periodically removes stale limiters from the cache.
12 | *
13 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
14 | */
15 | public class LimiterCleanupTask extends BackgroundTask {
16 |
17 | public LimiterCleanupTask() {
18 | super();
19 | }
20 |
21 | @Override
22 | public void onRun() {
23 |
24 | // Get the requested limiter cache
25 | ObjectCache cache = ObjectCache.getCache(ObjectCache.CACHE_LIMITERS);
26 |
27 | // Remove expired objects and log current counts
28 | int removed = cache.expireOldObjects();
29 | int active = cache.count();
30 |
31 | // Log the session cache info
32 | LibLog._clogF("I2601", removed, active);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/resources/multi.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | BarcodeAPI.org - MultiLabel Generator
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Home
19 |
20 | Add Barcode
21 | Share
22 | Print
23 | Clear
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/tasks/SessionCleanupTask.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.tasks;
2 |
3 | import org.barcodeapi.server.cache.ObjectCache;
4 | import org.barcodeapi.server.core.BackgroundTask;
5 |
6 | import com.mclarkdev.tools.liblog.LibLog;
7 |
8 | /**
9 | * SessionCleanupTask.java
10 | *
11 | * A background task which periodically removes stale sessions from the cache.
12 | * Additionally saves a cache snapshot to disk, to be used on server restart.
13 | *
14 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
15 | */
16 | public class SessionCleanupTask extends BackgroundTask {
17 |
18 | private final ObjectCache sessions = //
19 | ObjectCache.getCache(ObjectCache.CACHE_SESSIONS);
20 |
21 | public SessionCleanupTask() {
22 | super();
23 | }
24 |
25 | @Override
26 | public void onRun() {
27 |
28 | // Remove expired objects and log current counts
29 | int removed = sessions.expireOldObjects(), active = sessions.count();
30 | LibLog._clogF("I2401", removed, active);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/api/PlansHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.api;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import org.barcodeapi.core.Config;
8 | import org.barcodeapi.core.Config.Cfg;
9 | import org.barcodeapi.server.core.RequestContext;
10 | import org.barcodeapi.server.core.RestHandler;
11 | import org.json.JSONException;
12 | import org.json.JSONObject;
13 |
14 | /**
15 | * PlansHandler.java
16 | *
17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
18 | */
19 | public class PlansHandler extends RestHandler {
20 |
21 | private static final JSONObject plansInfo = Config.get(Cfg.Plans);
22 |
23 | public PlansHandler() {
24 | super();
25 | }
26 |
27 | @Override
28 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
29 |
30 | // Print response to client
31 | r.setStatus(HttpServletResponse.SC_OK);
32 | r.setContentType("application/json");
33 | r.getOutputStream().println(plansInfo.toString());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/config/types/AprilTag.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "AprilTag",
3 | "display": "April Tag",
4 | "show": false,
5 | "decode": false,
6 | "cache": true,
7 | "target": [
8 | "april",
9 | "apriltag"
10 | ],
11 | "generator": ".AprilTagGenerator",
12 | "threads": 2,
13 | "priority": 65,
14 | "cost": {
15 | "basic": 1,
16 | "custom": 1
17 | },
18 | "checkdigit": 0,
19 | "nonprinting": false,
20 | "example": [
21 | "tag36h11:0",
22 | "tag16h5:0"
23 | ],
24 | "pattern": {
25 | "auto": "^tag.*:[0-9]{1,5}$",
26 | "extended": "^tag.*:[0-9]{1,5}$"
27 | },
28 | "description": {
29 | "en": "AprilTag.\n\nSupported types:\ntag16h5\ntag25h9\ntag36h9\ntag36h10\ntag36h11\ntagCircle21h7\ntagCircle49h12\ntagCustom48h\ntagStandard41h12\ntagStandard52h13\n\nCall as: /api/april/$type:$id"
30 | },
31 | "wiki": {
32 | "en": "https://github.com/AprilRobotics/apriltag"
33 | },
34 | "options": {
35 | "scale": {
36 | "name": "Scale",
37 | "type": "number",
38 | "default": 8,
39 | "min": 1,
40 | "max": 20,
41 | "step": 1
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/api/TestServerTypeHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.api;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.eclipse.jetty.http.HttpStatus;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | /**
9 | * TestServerTypeHandler.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
12 | */
13 | public class TestServerTypeHandler extends ServerTestBase {
14 |
15 | @Test
16 | public void testServer_TestTypeEndpoind() {
17 |
18 | serverGet("/type/?type=a");
19 |
20 | Assert.assertEquals("Response Code", //
21 | HttpStatus.OK_200, getResponseCode());
22 | }
23 |
24 | @Test
25 | public void testServer_TestTypeEndpointNoArgs() {
26 |
27 | serverGet("/type/?");
28 |
29 | Assert.assertEquals("Response Code", //
30 | HttpStatus.OK_200, getResponseCode());
31 | }
32 |
33 | @Test
34 | public void testServer_TestTypeEndpointWrongTarget() {
35 |
36 | serverGet("/type/?type=abc");
37 |
38 | Assert.assertEquals("Response Code", //
39 | HttpStatus.BAD_REQUEST_400, getResponseCode());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/resources/ext/js/admin.js:
--------------------------------------------------------------------------------
1 | //
2 | // BarcodeAPI.org, 2017-2025
3 | // admin.js // admin.html
4 | //
5 |
6 | function zero(x) {
7 | return ((x < 10) ? ("0" + x) : x);
8 | }
9 |
10 | function loadInfo() {
11 | fetch("/info/").then(response => {
12 | return response.json();
13 | }).then(onLoadInfo);
14 | }
15 |
16 | function onLoadInfo(info) {
17 |
18 | // display hostname, version, dist
19 | document.getElementById("appHost").innerHTML = info.hostname;
20 | document.getElementById("appVersion").innerHTML = info.version;
21 | document.getElementById("appDist").innerHTML = info.dist;
22 |
23 | // calculate uptime in HH:MM
24 | var sinceH = Math.floor((info.uptime / 1000 / 60 / 60)).toFixed(0);
25 | var sinceM = Math.floor((info.uptime / 1000 / 60 % 60)).toFixed(0);
26 |
27 | // display uptime
28 | var uptime = ("+" + zero(sinceH) + ":" + zero(sinceM));
29 | document.getElementById("appUptime").innerHTML = uptime;
30 |
31 | // Log tracking event
32 | var setupMillis = ((new Date()) - timeStart);
33 | trackingEvent("AppEvents", "AppLoad", "Admin", setupMillis);
34 | }
35 |
--------------------------------------------------------------------------------
/.gitea/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: Java Build
2 |
3 | on: [push]
4 |
5 | jobs:
6 | Build-Artifacts:
7 | runs-on: linux-amd64
8 | steps:
9 | - name: Set up JDK
10 | uses: actions/setup-java@v4
11 | with:
12 | distribution: 'adopt'
13 | java-version: '11'
14 | - name: Clone Repository
15 | run: |
16 | repo="ssh://git@git.mclarkdev.com:2222/${{ gitea.repository }}"
17 | echo "Cloning $repo" && git clone --recurse-submodules $repo .
18 | echo "Checking out $GITHUB_REF_NAME"; git checkout $GITHUB_REF_NAME
19 | - name: Set version number
20 | run: |
21 | echo -n "${{ github.run_id }}" > src/main/resources/app.version
22 | - name: Build artifacts with Maven
23 | run: mvn clean install
24 | - name: Package the server
25 | run: |
26 | mkdir server/
27 | cp -rv config server/
28 | cp -rv resources server/
29 | cp target/server.jar server/
30 | - name: Archive job artifact
31 | uses: actions/upload-artifact@v3
32 | with:
33 | name: server-${{ github.run_id }}
34 | path: |
35 | server/
36 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/admin/SubscriberReloadHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.admin;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import org.barcodeapi.server.cache.SubscriberCache;
8 | import org.barcodeapi.server.core.RequestContext;
9 | import org.barcodeapi.server.core.RestHandler;
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 |
13 | /**
14 | * SubscriberReloadHandler.java
15 | *
16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
17 | */
18 | public class SubscriberReloadHandler extends RestHandler {
19 |
20 | public SubscriberReloadHandler() {
21 | super(true, false, false);
22 | }
23 |
24 | @Override
25 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
26 |
27 | // Reload the subscribers list
28 | SubscriberCache.reload();
29 |
30 | // Print response to client
31 | r.setStatus(HttpServletResponse.SC_OK);
32 | r.setContentType("application/json");
33 | r.getOutputStream().println((new JSONObject()//
34 | .put("code", 200)//
35 | .put("message", "users reloaded")//
36 | ).toString());
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/CodeGenerator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen;
2 |
3 | import org.barcodeapi.server.core.CodeType;
4 | import org.barcodeapi.server.core.GenerationException;
5 |
6 | /**
7 | * CodeGenerator.java
8 | *
9 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
10 | */
11 | public abstract class CodeGenerator {
12 |
13 | private final CodeType codeType;
14 |
15 | public CodeGenerator(CodeType codeType) {
16 | this.codeType = codeType;
17 | }
18 |
19 | /**
20 | * Returns the CodeType associated with the generator.
21 | *
22 | * @return associated CodeType
23 | */
24 | public CodeType getType() {
25 | return codeType;
26 | }
27 |
28 | /**
29 | * Default validation does not modifications.
30 | *
31 | * @param data
32 | * @return
33 | * @throws GenerationException
34 | */
35 | public void onValidateRequest(BarcodeRequest data) throws GenerationException {
36 | }
37 |
38 | /**
39 | * Implemented by the specific generator.
40 | *
41 | * Called when a barcode should be rendered.
42 | *
43 | * @param data
44 | * @return
45 | */
46 | public abstract byte[] onRender(BarcodeRequest request) throws Exception;
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/api/InfoHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.api;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import org.barcodeapi.core.Config;
8 | import org.barcodeapi.core.ServerRuntime;
9 | import org.barcodeapi.server.core.RequestContext;
10 | import org.barcodeapi.server.core.RestHandler;
11 | import org.json.JSONException;
12 | import org.json.JSONObject;
13 |
14 | /**
15 | * InfoHandler.java
16 | *
17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
18 | */
19 | public class InfoHandler extends RestHandler {
20 |
21 | public InfoHandler() {
22 | super();
23 | }
24 |
25 | @Override
26 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
27 |
28 | // Print response to client
29 | r.setStatus(HttpServletResponse.SC_OK);
30 | r.setContentType("application/json");
31 | r.getOutputStream().println((new JSONObject()//
32 | .put("uptime", ServerRuntime.getTimeRunning())//
33 | .put("hostname", ServerRuntime.getHostname())//
34 | .put("version", ServerRuntime.getVersion())//
35 | .put("dist", Config.dist())//
36 | ).toString());
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/tasks/BarcodeCleanupTask.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.tasks;
2 |
3 | import org.barcodeapi.server.cache.ObjectCache;
4 | import org.barcodeapi.server.core.BackgroundTask;
5 | import org.barcodeapi.server.core.CodeTypes;
6 |
7 | import com.mclarkdev.tools.liblog.LibLog;
8 |
9 | /**
10 | * BarcodeCleanupTask.java
11 | *
12 | * A background task which periodically removes stale barcodes from the cache.
13 | * Additionally saves a cache snapshot to disk, to be used on server restart.
14 | *
15 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
16 | */
17 | public class BarcodeCleanupTask extends BackgroundTask {
18 |
19 | public BarcodeCleanupTask() {
20 | super();
21 | }
22 |
23 | @Override
24 | public void onRun() {
25 |
26 | // Loop each supported type
27 | int removed = 0, active = 0;
28 | for (String type : CodeTypes.inst().getTypes()) {
29 |
30 | // Get the type cache
31 | ObjectCache cache = ObjectCache.getCache(type);
32 |
33 | // Expire and count objects
34 | removed += cache.expireOldObjects();
35 | active += cache.count();
36 | }
37 |
38 | // Log the cache counts
39 | LibLog._clogF("I2201", removed, active);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/admin/SessionFlushHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.admin;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import org.barcodeapi.server.cache.ObjectCache;
8 | import org.barcodeapi.server.core.RequestContext;
9 | import org.barcodeapi.server.core.RestHandler;
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 |
13 | /**
14 | * SessionFlushHandler.java
15 | *
16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
17 | */
18 | public class SessionFlushHandler extends RestHandler {
19 |
20 | public SessionFlushHandler() {
21 | super(true, false, false);
22 | }
23 |
24 | @Override
25 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
26 |
27 | // Clear cache and get count
28 | double count = ObjectCache.getCache(//
29 | ObjectCache.CACHE_SESSIONS).clearCache();
30 |
31 | // Print response to client
32 | r.setStatus(HttpServletResponse.SC_OK);
33 | r.setContentType("application/json");
34 | r.getOutputStream().println((new JSONObject()//
35 | .put("message", "sessions flushed")//
36 | .put("count", count)//
37 | ).toString());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/config/types/Aztec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Aztec",
3 | "display": "Aztec",
4 | "show": true,
5 | "decode": true,
6 | "cache": true,
7 | "target": [
8 | "aztec"
9 | ],
10 | "generator": ".AztecGenerator",
11 | "threads": 2,
12 | "priority": 52,
13 | "cost": {
14 | "basic": 3,
15 | "custom": 3
16 | },
17 | "checkdigit": 0,
18 | "nonprinting": true,
19 | "example": [
20 | "Aztec Barcode"
21 | ],
22 | "pattern": {
23 | "auto": "^[ !#$()*.\\/0-9=?A-Z_a-z~]{1,16}$",
24 | "extended": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]+$"
25 | },
26 | "description": {
27 | "en": "A 2D data barcode whose timing markers radiate outward from the middle."
28 | },
29 | "wiki": {
30 | "en": "https://en.wikipedia.org/wiki/Aztec_Code"
31 | },
32 | "options": {
33 | "size": {
34 | "name": "Size",
35 | "type": "number",
36 | "default": 275,
37 | "min": 50,
38 | "max": 500
39 | },
40 | "qz": {
41 | "name": "Quiet Zone",
42 | "type": "number",
43 | "default": 2,
44 | "min": 0,
45 | "max": 8
46 | },
47 | "correction": {
48 | "name": "Error Correction",
49 | "type": "number",
50 | "default": 33,
51 | "min": 8,
52 | "max": 65
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/api/TestPlansHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.api;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.eclipse.jetty.http.HttpStatus;
5 | import org.json.JSONArray;
6 | import org.json.JSONObject;
7 | import org.junit.Assert;
8 | import org.junit.Test;
9 |
10 | /**
11 | * TestServerTypesHandler.java
12 | *
13 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
14 | */
15 | public class TestPlansHandler extends ServerTestBase {
16 |
17 | @Test
18 | public void testServer_TestPlansEndpoint() {
19 |
20 | serverGet("/plans/");
21 |
22 | Assert.assertEquals("Response Code", //
23 | HttpStatus.OK_200, getResponseCode());
24 |
25 | Assert.assertEquals("Response Header", //
26 | "application/json;charset=utf-8", getHeader("Content-Type"));
27 |
28 | JSONObject response = getResponseAsJSON();
29 |
30 | JSONObject free = response.getJSONObject("free");
31 |
32 | Assert.assertEquals("Free Plan Limit", //
33 | 1000, free.getInt("limit"));
34 |
35 | Assert.assertEquals("Free Plan Enforcement", //
36 | true, free.getBoolean("enforce"));
37 |
38 | JSONArray paid = response.getJSONArray("paid");
39 |
40 | Assert.assertNotEquals("Paid Plan Info", 0, paid.length());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/config/types/QRCode.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "QRCode",
3 | "display": "QR Code",
4 | "show": true,
5 | "decode": true,
6 | "cache": true,
7 | "target": [
8 | "qr",
9 | "qr-code",
10 | "qrcode"
11 | ],
12 | "generator": ".QRCodeGenerator",
13 | "threads": 2,
14 | "priority": 58,
15 | "cost": {
16 | "basic": 3,
17 | "custom": 3
18 | },
19 | "checkdigit": 0,
20 | "nonprinting": true,
21 | "example": [
22 | "QR Barcode"
23 | ],
24 | "pattern": {
25 | "auto": "^.{1,64}$",
26 | "extended": "^.{1,65535}$"
27 | },
28 | "description": {
29 | "en": "A 2D data barcode whose timing markers are aligned at the corners."
30 | },
31 | "wiki": {
32 | "en": "https://en.wikipedia.org/wiki/QR_code"
33 | },
34 | "options": {
35 | "size": {
36 | "name": "Size",
37 | "type": "number",
38 | "default": 275,
39 | "min": 50,
40 | "max": 500
41 | },
42 | "qz": {
43 | "name": "Quiet Zone",
44 | "type": "number",
45 | "default": 2,
46 | "min": 0,
47 | "max": 8
48 | },
49 | "correction": {
50 | "name": "Error Correction",
51 | "type": "option",
52 | "default": "M",
53 | "options": [
54 | "M",
55 | "L",
56 | "H",
57 | "Q"
58 | ]
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/admin/ServerStatsHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.admin;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import org.barcodeapi.server.core.RequestContext;
8 | import org.barcodeapi.server.core.RestHandler;
9 | import org.json.JSONException;
10 | import org.json.JSONObject;
11 |
12 | import com.mclarkdev.tools.libmetrics.LibMetrics;
13 |
14 | /**
15 | * ServerStatsHandler.java
16 | *
17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
18 | */
19 | public class ServerStatsHandler extends RestHandler {
20 |
21 | public ServerStatsHandler() {
22 | super(true, false, false);
23 | }
24 |
25 | @Override
26 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
27 |
28 | // Determine which stats cache to use
29 | String cache = c.getRequest().getParameter("cache");
30 | cache = (cache != null) ? cache : "default";
31 |
32 | // Get the details from the requested instance
33 | JSONObject stats = LibMetrics.instance(cache).getDetails();
34 |
35 | // Print response to client
36 | r.setStatus(HttpServletResponse.SC_OK);
37 | r.setContentType("application/json");
38 | r.getOutputStream().println(stats.toString());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/admin/ShareListHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.admin;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import org.barcodeapi.server.cache.ObjectCache;
8 | import org.barcodeapi.server.core.RequestContext;
9 | import org.barcodeapi.server.core.RestHandler;
10 | import org.json.JSONArray;
11 | import org.json.JSONException;
12 | import org.json.JSONObject;
13 |
14 | /**
15 | * ShareListHandler.java
16 | *
17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
18 | */
19 | public class ShareListHandler extends RestHandler {
20 |
21 | public ShareListHandler() {
22 | super(true, false, false);
23 | }
24 |
25 | @Override
26 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
27 |
28 | // Loop all sessions
29 | JSONArray shares = new JSONArray();
30 | for (String key : ObjectCache.getCache(//
31 | ObjectCache.CACHE_SHARE).raw().keySet()) {
32 | shares.put(key);
33 | }
34 |
35 | // Print response to client
36 | r.setStatus(HttpServletResponse.SC_OK);
37 | r.setContentType("application/json");
38 | r.getOutputStream().println((new JSONObject()//
39 | .put("shares", shares)//
40 | .put("count", shares.length())//
41 | ).toString());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/admin/SessionListHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.admin;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import org.barcodeapi.server.cache.ObjectCache;
8 | import org.barcodeapi.server.core.RequestContext;
9 | import org.barcodeapi.server.core.RestHandler;
10 | import org.json.JSONArray;
11 | import org.json.JSONException;
12 | import org.json.JSONObject;
13 |
14 | /**
15 | * SessionListHandler.java
16 | *
17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
18 | */
19 | public class SessionListHandler extends RestHandler {
20 |
21 | public SessionListHandler() {
22 | super(true, false, false);
23 | }
24 |
25 | @Override
26 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
27 |
28 | // Loop all sessions
29 | JSONArray sessions = new JSONArray();
30 | for (String key : ObjectCache.getCache(//
31 | ObjectCache.CACHE_SESSIONS).raw().keySet()) {
32 | sessions.put(key);
33 | }
34 |
35 | // Print response to client
36 | r.setStatus(HttpServletResponse.SC_OK);
37 | r.setContentType("application/json");
38 | r.getOutputStream().println((new JSONObject()//
39 | .put("sessions", sessions)//
40 | .put("count", sessions.length())//
41 | ).toString());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/admin/CacheFlushHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.admin;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import org.barcodeapi.server.cache.ObjectCache;
8 | import org.barcodeapi.server.core.CodeTypes;
9 | import org.barcodeapi.server.core.RequestContext;
10 | import org.barcodeapi.server.core.RestHandler;
11 | import org.json.JSONException;
12 | import org.json.JSONObject;
13 |
14 | /**
15 | * CacheFlushHandler.java
16 | *
17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
18 | */
19 | public class CacheFlushHandler extends RestHandler {
20 |
21 | public CacheFlushHandler() {
22 | super(true, false, false);
23 | }
24 |
25 | @Override
26 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
27 |
28 | JSONObject counts = new JSONObject();
29 |
30 | // Loop all configured types
31 | for (String type : CodeTypes.inst().getTypes()) {
32 |
33 | // Clear the cache and add count to map
34 | counts.put(type, ObjectCache.getCache(type).clearCache());
35 | }
36 |
37 | // Print response to client
38 | r.setStatus(HttpServletResponse.SC_OK);
39 | r.setContentType("application/json");
40 | r.getOutputStream().println((new JSONObject()//
41 | .put("code", 200)//
42 | .put("message", "caches flushed")//
43 | .put("counts", counts)//
44 | ).toString());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/api/TestInfoHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.api;
2 |
3 | import java.io.IOException;
4 |
5 | import org.barcodeapi.server.ServerTestBase;
6 | import org.eclipse.jetty.http.HttpStatus;
7 | import org.json.JSONObject;
8 | import org.junit.Assert;
9 | import org.junit.Test;
10 |
11 | import com.mclarkdev.tools.libextras.LibExtrasStreams;
12 |
13 | /**
14 | * TestServerTypesHandler.java
15 | *
16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
17 | */
18 | public class TestInfoHandler extends ServerTestBase {
19 |
20 | @Test
21 | public void testServer_TestInfoEndpoint() {
22 |
23 | serverGet("/info/");
24 |
25 | Assert.assertEquals("Response Code", //
26 | HttpStatus.OK_200, getResponseCode());
27 |
28 | Assert.assertEquals("Response Header", //
29 | "application/json;charset=utf-8", getHeader("Content-Type"));
30 |
31 | try {
32 |
33 | String response = LibExtrasStreams.readStream(getResponse());
34 | JSONObject parsed = new JSONObject(response);
35 |
36 | // Server Uptime is set
37 | Assert.assertTrue("Server Uptime", //
38 | parsed.getLong("uptime") > 0);
39 |
40 | // Server Hostname is set
41 | Assert.assertTrue("Server Hostname", //
42 | parsed.getString("hostname").length() > 0);
43 |
44 | // Server version is set
45 | Assert.assertTrue("Server Version", //
46 | parsed.getInt("version") >= 0);
47 |
48 | } catch (IOException e) {
49 | Assert.fail(e.getMessage());
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/resources/ext/css/multi.css:
--------------------------------------------------------------------------------
1 | @page {
2 | size: auto;
3 | margin: 0;
4 | }
5 |
6 | .multis-barcode {
7 | margin-top: 3em;
8 | margin-bottom: 3em;
9 | max-width: 80mm;
10 | margin-left: 3em;
11 | margin-right: 3em;
12 | }
13 |
14 | #barcodes {
15 | margin-top: 1.5em;
16 | border-spacing: 10mm;
17 | }
18 |
19 | .controller {
20 | position: fixed;
21 | top: 0px;
22 | left: 0px;
23 | display: inline-grid;
24 | }
25 |
26 | .button {
27 | border: none;
28 | color: white;
29 | padding: 10px 32px;
30 | text-align: center;
31 | text-decoration: none;
32 | display: inline-block;
33 | font-size: 16px;
34 | }
35 |
36 | .button-home {
37 | width: 100px;
38 | grid-column: 1;
39 | background-color: rgb(0, 0, 255);
40 | }
41 |
42 | .button-generate {
43 | width: 185px;
44 | grid-column: 3;
45 | background-color: #04AA6D;
46 | }
47 |
48 | .button-share {
49 | width: 112px;
50 | grid-column: 4;
51 | background-color: rgb(255, 128, 0);
52 | }
53 |
54 | .button-print {
55 | width: 112px;
56 | grid-column: 5;
57 | background-color: rgb(127, 0, 255);
58 | }
59 |
60 | .button-clear {
61 | width: 112px;
62 | grid-column: 6;
63 | background-color: rgb(200, 20, 20);
64 | }
65 |
66 | #input {
67 | grid-column: 2;
68 | width: 350px;
69 | height: 36px;
70 | resize: none;
71 |
72 | padding: 8px;
73 | box-sizing: border-box;
74 | border: 2px solid #ccc;
75 | background-color: #f8f8f8;
76 | font-size: 16px;
77 | }
78 |
79 | #input:focus {
80 | outline: none;
81 | }
82 |
83 | @media print {
84 | .controller {
85 | display: none;
86 | }
87 | }
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/api/TestServerRoot.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.api;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.eclipse.jetty.http.HttpStatus;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | /**
9 | * TestServerRoot.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
12 | */
13 | public class TestServerRoot extends ServerTestBase {
14 |
15 | @Test
16 | public void testServer_RootRedirect() {
17 |
18 | serverGet("/");
19 |
20 | Assert.assertEquals("Response Code", //
21 | HttpStatus.FOUND_302, getResponseCode());
22 |
23 | Assert.assertTrue("Redirect", //
24 | getHeader("Location").endsWith("/index.html"));
25 | }
26 |
27 | @Test
28 | public void testServer_StaticRoot() {
29 |
30 | serverGet("/index.html");
31 |
32 | Assert.assertEquals("Response Code", //
33 | HttpStatus.OK_200, getResponseCode());
34 |
35 | // TODO Assert loaded response body
36 | }
37 |
38 | @Test
39 | public void testServer_StaticResource() {
40 |
41 | serverGet("/ext/logo.svg");
42 |
43 | Assert.assertEquals("Response Code", //
44 | HttpStatus.OK_200, getResponseCode());
45 |
46 | // TODO Assert loaded image
47 | }
48 |
49 | @Test
50 | public void testServer_302RedirectToAPI() {
51 |
52 | serverGet("/unknown.html");
53 |
54 | Assert.assertEquals("Response Code", //
55 | HttpStatus.FOUND_302, getResponseCode());
56 |
57 | Assert.assertTrue("Redirect", //
58 | getHeader("Location").endsWith("/api/auto/unknown.html"));
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/admin/LimiterListHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.admin;
2 |
3 | import java.io.IOException;
4 | import java.util.Map;
5 |
6 | import javax.servlet.http.HttpServletResponse;
7 |
8 | import org.barcodeapi.server.cache.CachedLimiter;
9 | import org.barcodeapi.server.cache.CachedObject;
10 | import org.barcodeapi.server.cache.ObjectCache;
11 | import org.barcodeapi.server.core.RequestContext;
12 | import org.barcodeapi.server.core.RestHandler;
13 | import org.json.JSONException;
14 | import org.json.JSONObject;
15 |
16 | /**
17 | * LimiterListHandler.java
18 | *
19 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
20 | */
21 | public class LimiterListHandler extends RestHandler {
22 |
23 | public LimiterListHandler() {
24 | super(true, false, false);
25 | }
26 |
27 | @Override
28 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
29 |
30 | // List limiters
31 | JSONObject byKey = new JSONObject();
32 | ObjectCache keyCache = ObjectCache.getCache(ObjectCache.CACHE_LIMITERS);
33 | for (Map.Entry
entry : keyCache.raw().entrySet()) {
34 | CachedLimiter limiter = (CachedLimiter) entry.getValue();
35 | byKey.put(limiter.getCaller(), limiter.getTokenCount());
36 | }
37 |
38 | // Print response to client
39 | r.setStatus(HttpServletResponse.SC_OK);
40 | r.setContentType("application/json");
41 | r.getOutputStream().println((new JSONObject()//
42 | .put("limiters", byKey)//
43 | ).toString());
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/tasks/LimiterMintingTask.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.tasks;
2 |
3 | import java.util.Map;
4 |
5 | import org.barcodeapi.server.cache.CachedLimiter;
6 | import org.barcodeapi.server.cache.CachedObject;
7 | import org.barcodeapi.server.cache.ObjectCache;
8 | import org.barcodeapi.server.core.BackgroundTask;
9 |
10 | import com.mclarkdev.tools.liblog.LibLog;
11 |
12 | /**
13 | * LimiterMintingTask.java
14 | *
15 | * A background task which periodically mints rate limiting tokens.
16 | *
17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
18 | */
19 | public class LimiterMintingTask extends BackgroundTask {
20 |
21 | public LimiterMintingTask() {
22 | super();
23 | }
24 |
25 | @Override
26 | public void onRun() {
27 | double tokensMinted = 0;
28 |
29 | // Mint tokens for IP cache
30 | tokensMinted += mintTokens(ObjectCache.CACHE_LIMITERS);
31 |
32 | // Log number of tokens minted
33 | LibLog._clogF("I2621", tokensMinted);
34 | getStats().hitCounter(tokensMinted, "task", getName(), "minted");
35 | }
36 |
37 | private double mintTokens(String cacheName) {
38 | double tokensMinted = 0;
39 |
40 | // Lookup the requested cache
41 | ObjectCache cache = ObjectCache.getCache(cacheName);
42 |
43 | // Loop each cache entry
44 | for (Map.Entry entry : cache.raw().entrySet()) {
45 |
46 | // Mint tokens for the limiter
47 | tokensMinted += ((CachedLimiter) entry.getValue()).mintTokens();
48 | }
49 |
50 | // Return number minted
51 | return tokensMinted;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/config/community/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "types": [
3 | "UPC_E",
4 | "UPC_A",
5 | "EAN8",
6 | "EAN13",
7 | "CODABAR",
8 | "ITF14",
9 | "Code39",
10 | "Code128",
11 | "Aztec",
12 | "QRCode",
13 | "DataMatrix",
14 | "PDF417",
15 | "USPSMail",
16 | "RoyalMail",
17 | "AprilTag"
18 | ],
19 | "tasks": [
20 | {
21 | "name": "Watchdog",
22 | "impl": ".WatchdogTask",
23 | "interval": 15
24 | },
25 | {
26 | "name": "Stats",
27 | "impl": ".StatsDumpTask",
28 | "interval": 300
29 | },
30 | {
31 | "name": "Session Cleanup",
32 | "impl": ".SessionCleanupTask",
33 | "interval": 1800
34 | },
35 | {
36 | "name": "Barcode Cleanup",
37 | "impl": ".BarcodeCleanupTask",
38 | "interval": 3600
39 | },
40 | {
41 | "name": "Limiter Cleanup",
42 | "impl": ".LimiterCleanupTask",
43 | "interval": 3600
44 | },
45 | {
46 | "name": "Limiter Minting",
47 | "impl": ".LimiterMintingTask",
48 | "interval": 600
49 | },
50 | {
51 | "name": "Share Cleanup",
52 | "impl": ".ShareCleanupTask",
53 | "interval": 3600
54 | }
55 | ],
56 | "cache": {
57 | "_snapshots": "./cache/",
58 | "barcode": {
59 | "life": 10080,
60 | "shortLife": 240
61 | },
62 | "session": {
63 | "life": 20160,
64 | "shortLife": 60
65 | },
66 | "limiter": {
67 | "life": 4320,
68 | "shortLife": 360
69 | },
70 | "share": {
71 | "life": 129600,
72 | "shortLife": 360
73 | }
74 | },
75 | "client": {
76 | "cacheStatic": 1440,
77 | "cacheBarcode": 1440
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/resources/abuse.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | BarcodeAPI.org - Rate Limits
10 |
11 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
43 |
API Abuse
44 |
45 | Abusers will be IP banned.
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/core/BackgroundTask.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.core;
2 |
3 | import java.util.TimerTask;
4 |
5 | import com.mclarkdev.tools.liblog.LibLog;
6 | import com.mclarkdev.tools.libmetrics.LibMetrics;
7 |
8 | /**
9 | * BackgroundTask.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
12 | */
13 | public abstract class BackgroundTask extends TimerTask {
14 |
15 | private final LibMetrics stats = LibMetrics.instance();
16 |
17 | private final String name;
18 |
19 | public BackgroundTask() {
20 |
21 | String clazz = getClass().getName();
22 | name = clazz.substring(clazz.lastIndexOf('.') + 1);
23 | }
24 |
25 | /**
26 | * Returns an instance of the stats collector.
27 | *
28 | * @return an instance of the stats collector
29 | */
30 | protected LibMetrics getStats() {
31 | return stats;
32 | }
33 |
34 | /**
35 | * Returns the name of the background task.
36 | *
37 | * @return the name of the background task
38 | */
39 | public String getName() {
40 | return name;
41 | }
42 |
43 | @Override
44 | public void run() {
45 |
46 | // Log the task name
47 | LibLog._clogF("I2001", getName());
48 | long timeStart = System.currentTimeMillis();
49 |
50 | // Call implemented method
51 | this.onRun();
52 |
53 | // Hit the counter
54 | getStats().hitCounter("task", getName(), "count");
55 | long timeTask = System.currentTimeMillis() - timeStart;
56 | getStats().hitCounter(timeTask, "task", getName(), "time");
57 |
58 | // Clean garbage
59 | System.gc();
60 | }
61 |
62 | /**
63 | * The implemented logic to run on execution of the BackgroundTask.
64 | */
65 | public abstract void onRun();
66 | }
67 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/cust/TestMiscCustomer.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.cust;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.eclipse.jetty.http.HttpStatus;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | /**
9 | * TestMiscCustomer.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
12 | */
13 | public class TestMiscCustomer extends ServerTestBase {
14 |
15 | @Test
16 | public void testMiscCustomer_CustomEAN13() {
17 |
18 | apiGet("ean13/8859178779797", "width=2&height=20&format=png");
19 |
20 | Assert.assertEquals("Response Code", //
21 | HttpStatus.OK_200, getResponseCode());
22 |
23 | Assert.assertEquals("Code Type", //
24 | "EAN13", getHeader("X-Barcode-Type"));
25 |
26 | Assert.assertEquals("Code Data", //
27 | "8859178779797", getHeader("X-Barcode-Content"));
28 |
29 | Assert.assertEquals("Code Format", //
30 | "image/png;charset=utf-8", getHeader("Content-Type"));
31 | }
32 |
33 | @Test
34 | public void testMiscCustomer_CustomCode128() {
35 |
36 | apiGet("128/RD309874", "dpi=100&rotation=0&color=%23000000");
37 |
38 | Assert.assertEquals("Response Code", //
39 | HttpStatus.OK_200, getResponseCode());
40 |
41 | Assert.assertEquals("Code Type", //
42 | "Code128", getHeader("X-Barcode-Type"));
43 |
44 | Assert.assertEquals("Code Data", //
45 | "RD309874", getHeader("X-Barcode-Content"));
46 |
47 | Assert.assertEquals("Code Format", //
48 | "image/png;charset=utf-8", getHeader("Content-Type"));
49 | }
50 |
51 | @Test
52 | public void testMiscCustomer_MACAddress() {
53 |
54 | apiGet("aa:11:bb:22:cc:33");
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/config/types/DataMatrix.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DataMatrix",
3 | "display": "DataMatrix",
4 | "show": true,
5 | "decode": true,
6 | "cache": true,
7 | "target": [
8 | "dm",
9 | "data-matrix",
10 | "datamatrix",
11 | "matrix",
12 | "data"
13 | ],
14 | "generator": ".DataMatrixGenerator",
15 | "threads": 2,
16 | "priority": 55,
17 | "cost": {
18 | "basic": 3,
19 | "custom": 5
20 | },
21 | "checkdigit": 0,
22 | "nonprinting": true,
23 | "example": [
24 | "Data Matrix Barcode"
25 | ],
26 | "pattern": {
27 | "auto": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]{1,2335}$",
28 | "extended": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]{1,2335}$"
29 | },
30 | "description": {
31 | "en": "A 2D data barode whose timing markers are along the the edges."
32 | },
33 | "wiki": {
34 | "en": "https://en.wikipedia.org/wiki/Data_Matrix"
35 | },
36 | "options": {
37 | "dpi": {
38 | "name": "Resolution",
39 | "type": "number",
40 | "default": 150,
41 | "min": 50,
42 | "max": 300,
43 | "step": 10
44 | },
45 | "module": {
46 | "name": "Module",
47 | "type": "number",
48 | "default": 10,
49 | "min": 1,
50 | "max": 35,
51 | "step": 1
52 | },
53 | "qz": {
54 | "name": "Quiet Zone",
55 | "type": "number",
56 | "default": 3,
57 | "min": 0,
58 | "max": 20,
59 | "step": 1
60 | },
61 | "bg": {
62 | "name": "Background",
63 | "type": "text",
64 | "default": "ffffff",
65 | "pattern": "[0-9A-F]{6}"
66 | },
67 | "fg": {
68 | "name": "Foreground",
69 | "type": "text",
70 | "default": "000000",
71 | "pattern": "[0-9A-F]{6}"
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/core/GenerationException.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.core;
2 |
3 | import org.barcodeapi.server.cache.CachedBarcode;
4 | import org.barcodeapi.server.gen.BarcodeGenerator;
5 |
6 | import com.mclarkdev.tools.liblog.LibLog;
7 |
8 | /**
9 | * GenerationException.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
12 | */
13 | public class GenerationException extends Exception {
14 |
15 | private static final long serialVersionUID = 20241123L;
16 |
17 | public enum ExceptionType {
18 | EMPTY(400, "/128/$$@EMPTY$$@"),
19 |
20 | INVALID(400, "/128/$$@INVALID$$@"),
21 |
22 | BLACKLIST(403, "/128/$$@BLACKLIST$$@"),
23 |
24 | CHECKSUM(409, "/128/$$@CHECKSUM$$@"),
25 |
26 | LIMITED(429, "/128/$$@RATE$$@$$@LIMIT$$@"),
27 |
28 | FAILED(500, "/128/$$@FAILED$$@"),
29 |
30 | BUSY(503, "/128/$$@BUSY$$@");
31 |
32 | private final int status;
33 |
34 | private final CachedBarcode image;
35 |
36 | ExceptionType(int status, String target) {
37 | this.status = status;
38 | try {
39 | this.image = BarcodeGenerator.requestBarcode(target);
40 | } catch (GenerationException e) {
41 | throw LibLog._clog("E0789", e)//
42 | .asException(IllegalStateException.class);
43 | }
44 | }
45 |
46 | public int getStatusCode() {
47 | return status;
48 | }
49 |
50 | public CachedBarcode getBarcodeImage() {
51 | return image;
52 | }
53 | }
54 |
55 | private final ExceptionType type;
56 |
57 | public GenerationException(ExceptionType type, Throwable throwable) {
58 | super(throwable.getCause() != null ? throwable.getCause() : throwable);
59 |
60 | this.type = type;
61 | }
62 |
63 | public ExceptionType getExceptionType() {
64 |
65 | return type;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/config/types/PDF417.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "PDF417",
3 | "display": "PDF-417",
4 | "show": true,
5 | "decode": true,
6 | "cache": true,
7 | "target": [
8 | "417",
9 | "pdf417",
10 | "pdf"
11 | ],
12 | "generator": ".PDF417Generator",
13 | "threads": 2,
14 | "priority": 45,
15 | "cost": {
16 | "basic": 2,
17 | "custom": 4
18 | },
19 | "checkdigit": 0,
20 | "nonprinting": true,
21 | "example": [
22 | "PDF - 417"
23 | ],
24 | "pattern": {
25 | "auto": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]{1,2335}$",
26 | "extended": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]{1,2335}$"
27 | },
28 | "description": {
29 | "en": "A stacked linear barcode most commonly found on identification cards."
30 | },
31 | "wiki": {
32 | "en": "https://en.wikipedia.org/wiki/PDF417"
33 | },
34 | "options": {
35 | "dpi": {
36 | "name": "Resolution",
37 | "type": "number",
38 | "default": 150,
39 | "min": 50,
40 | "max": 300,
41 | "step": 10
42 | },
43 | "module": {
44 | "name": "Module",
45 | "type": "number",
46 | "default": 6,
47 | "min": 1,
48 | "max": 35,
49 | "step": 1
50 | },
51 | "qz": {
52 | "name": "Quiet Zone",
53 | "type": "number",
54 | "default": 4,
55 | "min": 0,
56 | "max": 20,
57 | "step": 1
58 | },
59 | "height": {
60 | "name": "Height",
61 | "type": "number",
62 | "default": 6,
63 | "min": 2,
64 | "max": 150,
65 | "step": 5
66 | },
67 | "bg": {
68 | "name": "Background",
69 | "type": "text",
70 | "default": "ffffff",
71 | "pattern": "[0-9A-F]{6}"
72 | },
73 | "fg": {
74 | "name": "Foreground",
75 | "type": "text",
76 | "default": "000000",
77 | "pattern": "[0-9A-F]{6}"
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/resources/nonprinting.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | BarcodeAPI.org - Nonprinting Characters
10 |
11 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
43 |
44 |
Non-Printing Characters
45 |
46 | Additional details coming soon.
47 |
48 |
49 |
50 |
51 |
Supported Types
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/core/TypeSelector.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.core;
2 |
3 | import java.util.HashMap;
4 |
5 | import com.mclarkdev.tools.libmetrics.LibMetrics;
6 |
7 | /**
8 | * TypeSelector.java
9 | *
10 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
11 | */
12 | public class TypeSelector {
13 |
14 | private static HashMap typeCache = new HashMap<>();
15 |
16 | static {
17 |
18 | // Loop all known types
19 | CodeTypes types = CodeTypes.inst();
20 | for (String name : types.getTypes()) {
21 |
22 | // Add types strings to cache
23 | CodeType type = types.getType(name);
24 | for (String target : type.getTargets()) {
25 | typeCache.put(target.toLowerCase(), type);
26 | }
27 | }
28 | }
29 |
30 | /**
31 | * Get a CodeType object by any of its associated string IDs.
32 | *
33 | * Will return null if none are found.
34 | *
35 | * @param codeType
36 | * @return
37 | */
38 | public static CodeType getTypeFromString(String codeType) {
39 | LibMetrics.hitMethodRunCounter();
40 |
41 | return ((codeType == null) ? null : typeCache.get(codeType.toLowerCase()));
42 | }
43 |
44 | /**
45 | * Returns a CodeType object best suited for the given data string.
46 | *
47 | * @param data
48 | * @return
49 | */
50 | public static CodeType getTypeFromData(String data) {
51 | LibMetrics.hitMethodRunCounter();
52 |
53 | int priority = 0;
54 | CodeType type = null;
55 | CodeTypes types = CodeTypes.inst();
56 |
57 | for (String name : types.getTypes()) {
58 | CodeType target = types.getType(name);
59 | if (data.matches(target.getPatternAuto())) {
60 | if (target.getPriority() > priority) {
61 | priority = target.getPriority();
62 | type = target;
63 | }
64 | }
65 | }
66 |
67 | return type;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/config/types/RoyalMail.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RoyalMail",
3 | "display": "Royal Mail",
4 | "show": false,
5 | "decode": false,
6 | "cache": true,
7 | "target": [
8 | "royal",
9 | "royal-mail"
10 | ],
11 | "generator": ".RoyalMailGenerator",
12 | "threads": 2,
13 | "priority": 8,
14 | "cost": {
15 | "basic": 1,
16 | "custom": 1
17 | },
18 | "checkdigit": 0,
19 | "nonprinting": false,
20 | "example": [
21 | "11212345612345678"
22 | ],
23 | "pattern": {
24 | "auto": "^[0-9]{1,32}$",
25 | "extended": "^[0-9]{1,32}$"
26 | },
27 | "description": {
28 | "en": "Royal Mail"
29 | },
30 | "wiki": {
31 | "en": "https://en.wikipedia.org/wiki/PostBar"
32 | },
33 | "options": {
34 | "dpi": {
35 | "name": "Resolution",
36 | "type": "number",
37 | "default": 180,
38 | "min": 50,
39 | "max": 300,
40 | "step": 10
41 | },
42 | "module": {
43 | "name": "Module",
44 | "type": "number",
45 | "default": 10,
46 | "min": 1,
47 | "max": 35,
48 | "step": 1
49 | },
50 | "qz": {
51 | "name": "Quiet Zone",
52 | "type": "number",
53 | "default": 2,
54 | "min": 0,
55 | "max": 20,
56 | "step": 1
57 | },
58 | "height": {
59 | "name": "Height",
60 | "type": "number",
61 | "default": 22,
62 | "min": 5,
63 | "max": 150,
64 | "step": 5
65 | },
66 | "font": {
67 | "name": "Font Size",
68 | "type": "number",
69 | "default": 4,
70 | "min": 0,
71 | "max": 20,
72 | "step": 2
73 | },
74 | "bg": {
75 | "name": "Background",
76 | "type": "text",
77 | "default": "ffffff",
78 | "pattern": "[0-9A-F]{6}"
79 | },
80 | "fg": {
81 | "name": "Foreground",
82 | "type": "text",
83 | "default": "000000",
84 | "pattern": "[0-9A-F]{6}"
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/admin/LimiterFlushHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.admin;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import org.barcodeapi.server.cache.ObjectCache;
8 | import org.barcodeapi.server.core.RequestContext;
9 | import org.barcodeapi.server.core.RestHandler;
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 |
13 | /**
14 | * LimiterFlushHandler.java
15 | *
16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
17 | */
18 | public class LimiterFlushHandler extends RestHandler {
19 |
20 | public LimiterFlushHandler() {
21 | super(true, false, false);
22 | }
23 |
24 | @Override
25 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
26 |
27 | // Check & flush caches
28 | String id = c.getRequest().getParameter("id");
29 | boolean flushed = flush(ObjectCache.CACHE_LIMITERS, id);
30 |
31 | if (!flushed) {
32 |
33 | // Print failure to client
34 | r.setStatus(HttpServletResponse.SC_BAD_REQUEST);
35 | r.setContentType("application/json");
36 | r.getOutputStream().println((new JSONObject()//
37 | .put("code", 400)//
38 | .put("message", "limiter not found")//
39 | ).toString());
40 | return;
41 | }
42 |
43 | // Print response to client
44 | r.setStatus(HttpServletResponse.SC_OK);
45 | r.setContentType("application/json");
46 | r.getOutputStream().println((new JSONObject()//
47 | .put("code", 200)//
48 | .put("message", "limiter flushed")//
49 | ).toString());
50 | }
51 |
52 | private boolean flush(String cache, String limiter) {
53 | ObjectCache objCache = ObjectCache.getCache(cache);
54 | if ((limiter != null) && objCache.has(limiter)) {
55 | objCache.remove(limiter);
56 | return true;
57 | }
58 |
59 | return false;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/gen/TestOutputFormat.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.gen;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.eclipse.jetty.http.HttpStatus;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | /**
9 | * TestOutputFormat.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
12 | */
13 | public class TestOutputFormat extends ServerTestBase {
14 |
15 | @Test
16 | public void testOutputFormat_default() {
17 |
18 | apiGet("$12.34");
19 |
20 | Assert.assertEquals("Response Code", //
21 | HttpStatus.OK_200, getResponseCode());
22 |
23 | Assert.assertEquals("Code Format", //
24 | "image/png;charset=utf-8", getHeader("Content-Type"));
25 | }
26 |
27 | @Test
28 | public void testOutputFormat_JSON() {
29 |
30 | // Preconditions, set request header
31 | setHeader("Accept", "application/json");
32 |
33 | apiGet("$12.34");
34 |
35 | Assert.assertEquals("Response Code", //
36 | HttpStatus.OK_200, getResponseCode());
37 |
38 | Assert.assertEquals("Code Format", //
39 | "application/json;charset=utf-8", getHeader("Content-Type"));
40 | }
41 |
42 | @Test
43 | public void testOutputFormat_HTML() {
44 |
45 | // Preconditions, set request header
46 | setHeader("Accept", "text/html");
47 |
48 | apiGet("$12.34");
49 |
50 | Assert.assertEquals("Response Code", //
51 | HttpStatus.OK_200, getResponseCode());
52 |
53 | Assert.assertEquals("Code Format", //
54 | "text/html;charset=utf-8", getHeader("Content-Type"));
55 | }
56 |
57 | @Test
58 | public void testOutputFormat_PNG() {
59 |
60 | // Preconditions, set request header
61 | setHeader("Accept", "image/png");
62 |
63 | apiGet("$12.34");
64 |
65 | Assert.assertEquals("Response Code", //
66 | HttpStatus.OK_200, getResponseCode());
67 |
68 | Assert.assertEquals("Code Format", //
69 | "image/png;charset=utf-8", getHeader("Content-Type"));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/api/TestServerHeaders.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.api;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.eclipse.jetty.http.HttpStatus;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | /**
9 | * TestServerHeaders.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
12 | */
13 | public class TestServerHeaders extends ServerTestBase {
14 |
15 | @Test
16 | public void testServerHeaders_testServerNameHeader() {
17 |
18 | apiGet("test-headers");
19 |
20 | Assert.assertEquals("Response Code", //
21 | HttpStatus.OK_200, getResponseCode());
22 |
23 | Assert.assertEquals("Server Name Header", //
24 | "BarcodeAPI.org", getHeader("Server"));
25 | }
26 |
27 | @Test
28 | public void testServerHeaders_testServerNodeHeader() {
29 |
30 | apiGet("test-headers");
31 |
32 | Assert.assertEquals("Response Code", //
33 | HttpStatus.OK_200, getResponseCode());
34 |
35 | Assert.assertNotNull("Server Node Header", getHeader("Server-Node"));
36 | }
37 |
38 | @Test
39 | public void testServerHeaders_testServerCacheHeaders() {
40 |
41 | apiGet("test-headers");
42 |
43 | Assert.assertEquals("Response Code", //
44 | HttpStatus.OK_200, getResponseCode());
45 |
46 | Assert.assertEquals("Access Control Max Age Header", //
47 | "86400", getHeader("Access-Control-Max-Age"));
48 |
49 | Assert.assertEquals("Access Control Allow Header", //
50 | "true", getHeader("Access-Control-Allow-Credentials"));
51 |
52 | Assert.assertEquals("Access Control Origin Header", //
53 | "*", getHeader("Access-Control-Allow-Origin"));
54 | }
55 |
56 | @Test
57 | public void testServerHeaders_testServerTokens() {
58 |
59 | apiGet("test-headers");
60 |
61 | Assert.assertEquals("Response Code", //
62 | HttpStatus.OK_200, getResponseCode());
63 |
64 | Assert.assertNotNull("RateLimit Token Header", getHeader("X-RateLimit-Tokens"));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/resources/strings/codes.en_US.properties:
--------------------------------------------------------------------------------
1 | I0001=Initializing API server...
2 | I0002=Starting system tasks...
3 | I0003=Starting server...
4 |
5 | I0011=Creating Jetty API Server
6 | I0012=Initializing static resource handler
7 |
8 | I0021=Initializing handler: %s
9 |
10 | I0031=Registering shutdown hook.
11 | I0036=Setting up task: %s
12 |
13 | I0042=Saving cache snapshot.
14 | I0043=Saved cache snapshots. (%d ips, %d keys, %d sessions)
15 | E0049=Failed to save cache snapshot.
16 |
17 | E0051=Failed initializing CodeType.
18 |
19 | I0061=Loading type config: %s
20 | I0062=Initialized CodeType: %s (max: %d)
21 |
22 | I0101=Initializing cache: %s
23 |
24 | I0180=Creating new generator: %s
25 | I0181=Destroying generator: %s
26 |
27 | I2001=Running task: %s
28 |
29 | I2201=Cache Info: Stale [ %d ] Active [ %d ]
30 | I2401=Session Info: Stale [ %d ] Active [ %d ]
31 | I2601=Limiter Info: Stale [ %d ] Active [ %d ]
32 | I2602=Saved cache snapshot. (%s : %d entries)
33 | I2611=Share Cache: Stale [ %d ] Active [ %d ]
34 | I2621=Tokens minted: %.2f
35 |
36 | I2604=Uploading metrics.
37 |
38 | I4001=%s : %s : %s : %s
39 | I4002=%s : %s : %s : %s : %s
40 |
41 | I0601=Rendered [ %s ] with [ %s ] size [ %dB ] in [ %dms ]
42 |
43 | E0006=Failed to load app config.
44 |
45 | E0008=Failed to start server.
46 | E0009=Failed to stop server.
47 |
48 | E0039=Failed to setup task.
49 |
50 | E0059=Failed setting up code generator.
51 | E0069=Failed loading type config.
52 |
53 | E0509=Failed to generate bulk barcodes. (%s)
54 |
55 | E0539=Failed to add request to share. (%s : %s)
56 |
57 | E0608=Failed to decode URL.
58 | E0609=Client is out of tokens: %s
59 |
60 | E0699=Encountered an error while handling the request.
61 |
62 | E2602=Failed to save cache snapshot.
63 | E2603=Failed to load cache snapshot.
64 |
65 | E2609=Failed to upload metrics: %s
66 |
67 | E6009=Failed [ %s ] reason [ %s ]
68 |
69 | E0789=Failed to create default barcodes.
70 |
--------------------------------------------------------------------------------
/resources/ext/js/types.js:
--------------------------------------------------------------------------------
1 | //
2 | // BarcodeAPI.org, 2017-2025
3 | // types.js // types.html
4 | //
5 |
6 | window.onhashchange = function() {
7 | location.reload();
8 | }
9 |
10 | function init() {
11 |
12 | fetch('/type/')
13 | .then(response => {
14 | return response.json();
15 | }).then((data) => {
16 |
17 | data.sort((a, b) => {
18 | if (a.name < b.name) {
19 | return -1;
20 | }
21 | if (a.name > b.name) {
22 | return 1;
23 | }
24 | return 0;
25 | });
26 |
27 | return data;
28 | }).then(loadTypes);
29 | }
30 |
31 | function loadTypes(data) {
32 |
33 | for (var x in data) {
34 | addType(data[x]);
35 | }
36 |
37 | delTemplate();
38 |
39 | // Log tracking event
40 | var setupMillis = ((new Date()) - timeStart);
41 | trackingEvent("AppEvents", "AppLoad", "Types", setupMillis);
42 | }
43 |
44 | function addType(type) {
45 |
46 | var target = type.targets[0];
47 |
48 | var info = document.getElementById("barcode-template").cloneNode(true);
49 | info.setAttribute("id", "barcode-type-" + target);
50 |
51 | var link = ("/api/" + target + "/" + type.example[0]);
52 | info.querySelector(".type-example").src = link;
53 |
54 | info.querySelector(".type-name").innerHTML = type.display;
55 | info.querySelector(".type-target").innerHTML = ('/' + target + '/');
56 | info.querySelector(".type-cost-basic").innerHTML = type.costBasic;
57 | info.querySelector(".type-cost-custom").innerHTML = type.costCustom;
58 |
59 | var homeLink = ("index.html#" + target);
60 | info.querySelector(".type-gen").href = homeLink;
61 |
62 | var moreLink = ("type.html#" + target);
63 | info.querySelector(".type-more").href = moreLink;
64 | info.querySelector(".type-example-link").href = moreLink;
65 |
66 | document.getElementById("barcode-types").append(info);
67 | }
68 |
69 | function delTemplate() {
70 | document.getElementById("barcode-types")//
71 | .removeChild(document.getElementById("barcode-template"));
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/cache/LimiterCache.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.cache;
2 |
3 | import org.barcodeapi.core.Config;
4 | import org.barcodeapi.core.Config.Cfg;
5 | import org.json.JSONObject;
6 |
7 | import com.mclarkdev.tools.libmetrics.LibMetrics;
8 |
9 | /**
10 | * LimiterCache.java
11 | *
12 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
13 | */
14 | public class LimiterCache {
15 |
16 | // Default values for new limiters
17 | private static final int DEFLIMIT_RATE;
18 | private static final boolean DEFLIMIT_ENFORCE;
19 |
20 | // Local instance of the limiters cache
21 | private static final ObjectCache _LIMITERS;
22 |
23 | static {
24 |
25 | // Load plan from configuration
26 | JSONObject freePlan = Config//
27 | .get(Cfg.Plans).getJSONObject("free");
28 |
29 | // Free plan defaults
30 | DEFLIMIT_RATE = freePlan.getInt("limit");
31 | DEFLIMIT_ENFORCE = freePlan.getBoolean("enforce");
32 |
33 | // Get the limiter cache
34 | _LIMITERS = ObjectCache.getCache(ObjectCache.CACHE_LIMITERS);
35 | }
36 |
37 | public static CachedLimiter getLimiter(Subscriber sub, String address) {
38 | LibMetrics.hitMethodRunCounter();
39 |
40 | // User ID is customer or address
41 | String userID = (sub != null) ? sub.getCustomer() : address;
42 |
43 | // Determine if limiter exists
44 | CachedLimiter limiter;
45 | if (_LIMITERS.has(userID)) {
46 |
47 | // Get the existing limiter from the cache
48 | limiter = (CachedLimiter) _LIMITERS.get(userID);
49 | } else {
50 |
51 | // Determine enforce / limits for the new limiter
52 | int limit = (sub != null) ? sub.getLimit() : DEFLIMIT_RATE;
53 | boolean enforce = (sub != null) ? sub.isEnforced() : DEFLIMIT_ENFORCE;
54 |
55 | // Create a new limiter and add it to the cache
56 | limiter = new CachedLimiter(enforce, userID, limit);
57 | _LIMITERS.put(userID, limiter);
58 | }
59 |
60 | // Return the limiter
61 | return limiter;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/api/TestSessionHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.api;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.eclipse.jetty.http.HttpStatus;
5 | import org.json.JSONObject;
6 | import org.junit.Assert;
7 | import org.junit.Ignore;
8 | import org.junit.Test;
9 |
10 | /**
11 | * TestServerTypesHandler.java
12 | *
13 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
14 | */
15 | public class TestSessionHandler extends ServerTestBase {
16 |
17 | @Test
18 | @Ignore
19 | public void testServer_TestGetSessionEndpoint() {
20 |
21 | serverGet("/session/");
22 |
23 | Assert.assertEquals("Response Code", //
24 | HttpStatus.OK_200, getResponseCode());
25 |
26 | Assert.assertEquals("Response Header", //
27 | "application/json;charset=utf-8", getHeader("Content-Type"));
28 |
29 | JSONObject response = getResponseAsJSON();
30 |
31 | // Key is set
32 | Assert.assertEquals("Session Key", //
33 | 36, response.getString("key").length());
34 |
35 | // Created time of session
36 | Assert.assertTrue("Creation Time", //
37 | response.getLong("created") <= System.currentTimeMillis());
38 |
39 | // Expiration time of session
40 | Assert.assertTrue("Expiration Time", //
41 | response.getLong("expires") > System.currentTimeMillis());
42 |
43 | // Last touched time
44 | Assert.assertTrue("Last Touched", //
45 | response.getLong("last") <= System.currentTimeMillis());
46 |
47 | // Total hit count for session
48 | Assert.assertTrue("Hit Count", //
49 | response.getLong("count") >= 1);
50 |
51 | // Addresses list []
52 |
53 | // Request list []
54 |
55 | }
56 |
57 | @Test
58 | @Ignore
59 | public void testServer_TestDeleteSessionEndpoint() {
60 |
61 | serverGet("/session/");
62 | JSONObject response = getResponseAsJSON();
63 | String key = response.getString("key");
64 |
65 | setHeader("session", key);
66 | serverDelete("/session/");
67 |
68 | Assert.assertEquals("Response Code", //
69 | HttpStatus.OK_200, getResponseCode());
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/tasks/StatsDumpTask.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.tasks;
2 |
3 | import java.net.HttpURLConnection;
4 | import java.net.URL;
5 | import java.net.URLConnection;
6 |
7 | import org.barcodeapi.server.core.BackgroundTask;
8 |
9 | import com.mclarkdev.tools.libargs.LibArgs;
10 | import com.mclarkdev.tools.liblog.LibLog;
11 |
12 | /**
13 | * StatsDumpTask.java
14 | *
15 | * A background task which periodically logs the server stats.
16 | *
17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
18 | */
19 | public class StatsDumpTask extends BackgroundTask {
20 |
21 | private static final String _TELEM_TARGET = "https://barcodeapi.org/stats/upload";
22 | private static final boolean _TELEM_ENABLED = (!LibArgs.instance().getBoolean("no-telemetry"));
23 |
24 | public StatsDumpTask() {
25 | super();
26 | }
27 |
28 | @Override
29 | public void onRun() {
30 |
31 | // Get and log metrics data
32 | String data = getStats().getDetails().toString();
33 | LibLog.log("stats", data);
34 |
35 | // Upload if enabled
36 | if (_TELEM_ENABLED) {
37 | sendUsageReport(data);
38 | }
39 | }
40 |
41 | /**
42 | * Send usage statistics to a remote server to monitoring.
43 | *
44 | * @param data usage statistics
45 | */
46 | private void sendUsageReport(String data) {
47 |
48 | LibLog._clog("I2604");
49 |
50 | try {
51 |
52 | // Create HTTP client
53 | URL url = new URL(_TELEM_TARGET);
54 | URLConnection con = url.openConnection();
55 | HttpURLConnection http = (HttpURLConnection) con;
56 |
57 | // Set request options
58 | http.setDoOutput(true);
59 | http.setRequestMethod("POST");
60 | http.setFixedLengthStreamingMode(data.length());
61 | http.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
62 |
63 | // Connect
64 | http.connect();
65 |
66 | // Write metrics data
67 | http.getOutputStream().write(data.getBytes());
68 | http.getResponseCode();
69 |
70 | } catch (Exception | Error e) {
71 |
72 | LibLog._clog("E2609", e);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/admin/CacheDumpHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.admin;
2 |
3 | import java.io.IOException;
4 | import java.util.Map;
5 |
6 | import javax.servlet.http.HttpServletResponse;
7 |
8 | import org.barcodeapi.server.cache.CachedObject;
9 | import org.barcodeapi.server.cache.ObjectCache;
10 | import org.barcodeapi.server.core.CodeTypes;
11 | import org.barcodeapi.server.core.RequestContext;
12 | import org.barcodeapi.server.core.RestHandler;
13 | import org.json.JSONArray;
14 | import org.json.JSONException;
15 | import org.json.JSONObject;
16 |
17 | /**
18 | * CacheDumpHandler.java
19 | *
20 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
21 | */
22 | public class CacheDumpHandler extends RestHandler {
23 |
24 | public CacheDumpHandler() {
25 | super(true, false, false);
26 | }
27 |
28 | @Override
29 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
30 |
31 | // Loop all caches
32 | JSONObject types = new JSONObject();
33 | for (String type : CodeTypes.inst().getTypes()) {
34 |
35 | // Loop all cached objects
36 | JSONArray entries = new JSONArray();
37 | ObjectCache cache = ObjectCache.getCache(type);
38 | for (Map.Entry entry : cache.raw().entrySet()) {
39 |
40 | CachedObject obj = entry.getValue();
41 |
42 | entries.put(new JSONObject()//
43 | .put("text", entry.getKey())//
44 | .put("hits", obj.getAccessCount())//
45 | .put("created", obj.getTimeCreated())//
46 | .put("expires", obj.getTimeExpires())//
47 | .put("shortLived", obj.isShortLived()));
48 | }
49 |
50 | // Add to master element
51 | types.put(type, entries);
52 | }
53 |
54 | // Print response to client
55 | r.setStatus(HttpServletResponse.SC_OK);
56 | r.setContentType("application/json");
57 | r.setHeader("Content-Disposition", "attachment, filename=cache.json");
58 | r.getOutputStream().println((new JSONObject()//
59 | .put("code", 200)//
60 | .put("cache", types)//
61 | ).toString());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/config/types/USPSMail.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "USPSMail",
3 | "display": "USPS Mail",
4 | "show": false,
5 | "decode": false,
6 | "cache": true,
7 | "target": [
8 | "usps",
9 | "uspsmail",
10 | "intelligent-mail"
11 | ],
12 | "generator": ".USPSMailGenerator",
13 | "threads": 2,
14 | "priority": 10,
15 | "cost": {
16 | "basic": 1,
17 | "custom": 2
18 | },
19 | "checkdigit": 0,
20 | "nonprinting": false,
21 | "example": [
22 | "0123456709498765432101234567891"
23 | ],
24 | "pattern": {
25 | "auto": "^[0-9]{1,32}$",
26 | "extended": "^[0-9]{1,32}$"
27 | },
28 | "description": {
29 | "en": "Used by the United States Postal Service for Intelligent Mail sorting."
30 | },
31 | "wiki": {
32 | "en": "https://en.wikipedia.org/wiki/Intelligent_Mail_barcode"
33 | },
34 | "options": {
35 | "dpi": {
36 | "name": "Resolution",
37 | "type": "number",
38 | "default": 180,
39 | "min": 50,
40 | "max": 300,
41 | "step": 10
42 | },
43 | "module": {
44 | "name": "Module",
45 | "type": "number",
46 | "default": 10,
47 | "min": 1,
48 | "max": 35,
49 | "step": 1
50 | },
51 | "qz": {
52 | "name": "Quiet Zone",
53 | "type": "number",
54 | "default": 2,
55 | "min": 0,
56 | "max": 20,
57 | "step": 1
58 | },
59 | "height": {
60 | "name": "Height",
61 | "type": "number",
62 | "default": 22,
63 | "min": 5,
64 | "max": 150,
65 | "step": 5
66 | },
67 | "font": {
68 | "name": "Font Size",
69 | "type": "number",
70 | "default": 4,
71 | "min": 0,
72 | "max": 20,
73 | "step": 2
74 | },
75 | "text": {
76 | "name": "Text Placement",
77 | "type": "option",
78 | "default": "bottom",
79 | "options": [
80 | "bottom",
81 | "top",
82 | "none"
83 | ]
84 | },
85 | "bg": {
86 | "name": "Background",
87 | "type": "text",
88 | "default": "ffffff",
89 | "pattern": "[0-9A-F]{6}"
90 | },
91 | "fg": {
92 | "name": "Foreground",
93 | "type": "text",
94 | "default": "000000",
95 | "pattern": "[0-9A-F]{6}"
96 | }
97 | }
98 | }
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/api/SessionHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.api;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.http.HttpServletResponse;
6 |
7 | import org.barcodeapi.server.core.RequestContext;
8 | import org.barcodeapi.server.core.RestHandler;
9 | import org.barcodeapi.server.core.SessionHelper;
10 | import org.json.JSONObject;
11 |
12 | /**
13 | * SessionDetailsHandler.java
14 | *
15 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
16 | */
17 | public class SessionHandler extends RestHandler {
18 |
19 | public SessionHandler() {
20 | super(
21 | // Authentication not required
22 | false,
23 | // Do not use client rate limit
24 | false,
25 | // Do not create new session
26 | false);
27 | }
28 |
29 | @Override
30 | protected void onRequest(RequestContext c, HttpServletResponse r) throws IOException {
31 |
32 | if (!c.hasSession()) {
33 | r.setStatus(HttpServletResponse.SC_BAD_REQUEST);
34 | r.setContentType("application/json");
35 | r.getOutputStream().println((new JSONObject() //
36 | .put("code", 400)//
37 | .put("message", "no session in request")//
38 | ).toString());
39 | return;
40 | }
41 |
42 | switch (c.getMethod()) {
43 | case "GET":
44 | doGET(c, r);
45 | break;
46 |
47 | case "DELETE":
48 | doDELETE(c, r);
49 | break;
50 |
51 | default:
52 | r.setStatus(HttpServletResponse.SC_BAD_REQUEST);
53 | break;
54 | }
55 | }
56 |
57 | protected void doGET(RequestContext c, HttpServletResponse r) throws IOException {
58 |
59 | // Print response to client
60 | r.setStatus(HttpServletResponse.SC_OK);
61 | r.setContentType("application/json");
62 | r.getOutputStream().println(//
63 | c.getSession().asJSON().toString(4));
64 | }
65 |
66 | protected void doDELETE(RequestContext c, HttpServletResponse r) throws IOException {
67 |
68 | // Attempt session deletion
69 | boolean deleted = SessionHelper.deleteSession(c.getSession().getKey());
70 |
71 | // Print response to client
72 | r.setStatus((deleted) ? //
73 | HttpServletResponse.SC_OK : HttpServletResponse.SC_BAD_REQUEST);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/.github/workflows/docker-publish.yml:
--------------------------------------------------------------------------------
1 | name: Docker
2 |
3 | # This workflow uses actions that are not certified by GitHub.
4 | # They are provided by a third-party and are governed by
5 | # separate terms of service, privacy policy, and support
6 | # documentation.
7 |
8 | on:
9 | schedule:
10 | - cron: '29 3 5 * *'
11 | push:
12 | branches: [ master ]
13 | # Publish semver tags as releases.
14 | tags: [ 'v*.*.*' ]
15 | pull_request:
16 | branches: [ master ]
17 |
18 | env:
19 | # Use docker.io for Docker Hub if empty
20 | REGISTRY: ghcr.io
21 | # github.repository as /
22 | IMAGE_NAME: ${{ github.repository }}
23 |
24 |
25 | jobs:
26 | build:
27 |
28 | runs-on: ubuntu-latest
29 | permissions:
30 | contents: read
31 | packages: write
32 |
33 | steps:
34 | - name: Checkout repository
35 | uses: actions/checkout@v2
36 |
37 | # Login against a Docker registry except on PR
38 | # https://github.com/docker/login-action
39 | - name: Log into registry ${{ env.REGISTRY }}
40 | if: github.event_name != 'pull_request'
41 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
42 | with:
43 | registry: ${{ env.REGISTRY }}
44 | username: ${{ github.actor }}
45 | password: ${{ secrets.GITHUB_TOKEN }}
46 |
47 | # Extract metadata (tags, labels) for Docker
48 | # https://github.com/docker/metadata-action
49 | - name: Extract Docker metadata
50 | id: meta
51 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
52 | with:
53 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
54 |
55 | # Build and push Docker image with Buildx (don't push on PR)
56 | # https://github.com/docker/build-push-action
57 | - name: Build and push Docker image
58 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
59 | with:
60 | context: .
61 | push: ${{ github.event_name != 'pull_request' }}
62 | tags: ${{ steps.meta.outputs.tags }}
63 | labels: ${{ steps.meta.outputs.labels }}
64 |
--------------------------------------------------------------------------------
/resources/license.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | BarcodeAPI.org - License
10 |
11 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
39 |
40 |
41 |
42 |
License
43 |
44 | BarcodeAPI.org
45 | Copyright 2017-2024
46 |
47 |
48 | Licensed under the Apache License, Version 2.0 (the "License");
49 | you may not use this file except in compliance with the License.
50 | You may obtain a copy of the License at
51 |
52 | http://www.apache.org/licenses/LICENSE-2.0
53 |
54 | Unless required by applicable law or agreed to in writing, software
55 | distributed under the License is distributed on an "AS IS" BASIS,
56 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
57 | See the License for the specific language governing permissions and
58 | limitations under the License.
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/core/GeneratorPoolController.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.core;
2 |
3 | import java.lang.reflect.Constructor;
4 |
5 | import org.barcodeapi.server.gen.CodeGenerator;
6 |
7 | import com.mclarkdev.tools.liblog.LibLog;
8 | import com.mclarkdev.tools.libmetrics.LibMetrics;
9 | import com.mclarkdev.tools.libobjectpooler.LibObjectPoolerController;
10 |
11 | /**
12 | * GeneratorPoolController.java
13 | *
14 | * Manages the creation and destruction of pooled code generators.
15 | *
16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
17 | */
18 | public class GeneratorPoolController implements LibObjectPoolerController {
19 |
20 | private final CodeType type;
21 | private final Class extends CodeGenerator> clazz;
22 | private final Constructor extends CodeGenerator> constructor;
23 |
24 | @SuppressWarnings("unchecked")
25 | public GeneratorPoolController(CodeType type) {
26 |
27 | try {
28 |
29 | this.type = type;
30 | this.clazz = (Class extends CodeGenerator>) Class.forName(type.getGeneratorClass());
31 | this.constructor = clazz.getDeclaredConstructor(CodeType.class);
32 |
33 | } catch (Exception e) {
34 |
35 | // Log the initialization failure
36 | throw LibLog._clog("E0059", e).asException();
37 | }
38 | }
39 |
40 | @Override
41 | public CodeGenerator onCreate() {
42 | LibMetrics.hitMethodRunCounter();
43 |
44 | // Log the object creation
45 | LibLog._clogF("I0180", type.getName());
46 | LibMetrics.instance().hitCounter("generators", type.getName(), "pool", "created");
47 |
48 | try {
49 |
50 | // Create and return new object instance
51 | return constructor.newInstance(new Object[] { type });
52 | } catch (Exception | Error e) {
53 |
54 | // Log the failure
55 | LibLog._clog("E0059", e);
56 | return null;
57 | }
58 | }
59 |
60 | @Override
61 | public void onDestroy(CodeGenerator t) {
62 | LibMetrics.hitMethodRunCounter();
63 |
64 | // Log the object destruction
65 | LibLog._clogF("I0181", type.getName());
66 | LibMetrics.instance().hitCounter("generators", type.getName(), "pool", "destroyed");
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/impl/DefaultZXingProvider.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.impl;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | import org.barcodeapi.server.core.CodeType;
8 | import org.barcodeapi.server.gen.BarcodeRequest;
9 | import org.barcodeapi.server.gen.CodeGenerator;
10 | import org.json.JSONObject;
11 |
12 | import com.google.zxing.BarcodeFormat;
13 | import com.google.zxing.EncodeHintType;
14 | import com.google.zxing.Writer;
15 | import com.google.zxing.client.j2se.MatrixToImageWriter;
16 | import com.google.zxing.common.BitMatrix;
17 |
18 | public abstract class DefaultZXingProvider extends CodeGenerator {
19 |
20 | private final Writer generator;
21 |
22 | private final BarcodeFormat format;
23 |
24 | public DefaultZXingProvider(CodeType codeType, BarcodeFormat format, Writer bean) {
25 | super(codeType);
26 |
27 | this.format = format;
28 | this.generator = bean;
29 | }
30 |
31 | @Override
32 | public byte[] onRender(BarcodeRequest request) throws Exception {
33 |
34 | JSONObject options = request.getOptions();
35 |
36 | HashMap defaults = //
37 | request.getType().getDefaults();
38 |
39 | int size = options.optInt("size", //
40 | (Integer) defaults.getOrDefault("size", 275));
41 |
42 | int qz = options.optInt("qz", //
43 | (Integer) defaults.getOrDefault("qz", 4));
44 |
45 | // String correction = options.optString("correction", //
46 | // (String) defaults.getOrDefault("correction", "M"));
47 |
48 | Map hintsMap = new HashMap<>();
49 | // hintsMap.put(EncodeHintType.CHARACTER_SET, "utf-8");
50 | // hintsMap.put(EncodeHintType.ERROR_CORRECTION, //
51 | // ErrorCorrectionLevel.valueOf(correction));
52 | hintsMap.put(EncodeHintType.MARGIN, qz);
53 |
54 | synchronized (generator) {
55 |
56 | BitMatrix bitMatrix = generator.encode(//
57 | request.getData(), format, size, size, hintsMap);
58 |
59 | ByteArrayOutputStream out = new ByteArrayOutputStream();
60 | MatrixToImageWriter.writeToStream(bitMatrix, "png", out);
61 |
62 | return out.toByteArray();
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/api/TestLimiterHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.api;
2 |
3 | import java.io.IOException;
4 |
5 | import org.barcodeapi.server.ServerTestBase;
6 | import org.eclipse.jetty.http.HttpStatus;
7 | import org.json.JSONObject;
8 | import org.junit.Assert;
9 | import org.junit.Test;
10 |
11 | import com.mclarkdev.tools.libextras.LibExtrasStreams;
12 |
13 | /**
14 | * TestServerTypesHandler.java
15 | *
16 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
17 | */
18 | public class TestLimiterHandler extends ServerTestBase {
19 |
20 | @Test
21 | public void testServer_TestLimiterEndpoint() {
22 |
23 | setHeader("Authorization", "Token=appTest");
24 | serverGet("/limiter/");
25 |
26 | Assert.assertEquals("Response Code", //
27 | HttpStatus.OK_200, getResponseCode());
28 |
29 | Assert.assertEquals("Response Header", //
30 | "application/json;charset=utf-8", getHeader("Content-Type"));
31 |
32 | try {
33 |
34 | String response = LibExtrasStreams.readStream(getResponse());
35 | JSONObject parsed = new JSONObject(response);
36 |
37 | // Caller is set
38 | Assert.assertEquals("Caller", //
39 | "AppTest", parsed.getString("caller"));
40 |
41 | // Creation time of limiter
42 | Assert.assertTrue("Created", //
43 | parsed.getLong("created") <= System.currentTimeMillis());
44 |
45 | // Expiration time of limiter
46 | Assert.assertTrue("Expiration time", //
47 | parsed.getLong("expires") > System.currentTimeMillis());
48 |
49 | // Last touched time
50 | Assert.assertTrue("Last Touched", //
51 | parsed.getLong("last") <= System.currentTimeMillis());
52 |
53 | // Limiter enforcement
54 | Assert.assertTrue("Limiter Enforcement", //
55 | parsed.getBoolean("enforce") == true);
56 |
57 | // Token limit is set
58 | Assert.assertTrue("Token Limit", //
59 | parsed.getDouble("tokenLimit") == 1000);
60 |
61 | // Token count is set
62 | Assert.assertTrue("Token Count", //
63 | parsed.getDouble("tokenCount") >= 100);
64 |
65 | // Token spend is set
66 | Assert.assertTrue("Token Spend", //
67 | parsed.getDouble("tokenSpend") >= 0);
68 |
69 | } catch (IOException e) {
70 | Assert.fail(e.getMessage());
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/config/types/CODABAR.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "CODABAR",
3 | "display": "CODABAR",
4 | "show": true,
5 | "decode": false,
6 | "cache": true,
7 | "target": [
8 | "codabar"
9 | ],
10 | "generator": ".CodabarGenerator",
11 | "threads": 2,
12 | "priority": 78,
13 | "cost": {
14 | "basic": 1,
15 | "custom": 3
16 | },
17 | "checkdigit": 0,
18 | "nonprinting": false,
19 | "example": [
20 | "1234567890"
21 | ],
22 | "pattern": {
23 | "auto": "^[0-9:$]{4,12}$",
24 | "extended": "^[0-9\\-:$\\/.+]{1,22}$"
25 | },
26 | "description": {
27 | "en": "An early barcode design, initially used with dot-matrix printers."
28 | },
29 | "wiki": {
30 | "en": "https://en.wikipedia.org/wiki/Codabar"
31 | },
32 | "options": {
33 | "dpi": {
34 | "name": "Resolution",
35 | "type": "number",
36 | "default": 150,
37 | "min": 50,
38 | "max": 300,
39 | "step": 10
40 | },
41 | "module": {
42 | "name": "Module",
43 | "type": "number",
44 | "default": 4,
45 | "min": 1,
46 | "max": 35,
47 | "step": 1
48 | },
49 | "qz": {
50 | "name": "Quiet Zone",
51 | "type": "number",
52 | "default": 4,
53 | "min": 0,
54 | "max": 20,
55 | "step": 1
56 | },
57 | "height": {
58 | "name": "Height",
59 | "type": "number",
60 | "default": 22,
61 | "min": 5,
62 | "max": 150,
63 | "step": 5
64 | },
65 | "font": {
66 | "name": "Font Size",
67 | "type": "number",
68 | "default": 5,
69 | "min": 0,
70 | "max": 20,
71 | "step": 2
72 | },
73 | "text": {
74 | "name": "Text Placement",
75 | "type": "option",
76 | "default": "bottom",
77 | "options": [
78 | "bottom",
79 | "top",
80 | "none"
81 | ]
82 | },
83 | "pattern": {
84 | "name": "Pattern",
85 | "type": "text",
86 | "default": "_",
87 | "pattern": "[ _]+"
88 | },
89 | "bg": {
90 | "name": "Background",
91 | "type": "text",
92 | "default": "ffffff",
93 | "pattern": "[0-9A-F]{6}"
94 | },
95 | "fg": {
96 | "name": "Foreground",
97 | "type": "text",
98 | "default": "000000",
99 | "pattern": "[0-9A-F]{6}"
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/cache/CachedShare.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.cache;
2 |
3 | import java.util.List;
4 |
5 | import org.barcodeapi.server.gen.BarcodeRequest;
6 | import org.json.JSONArray;
7 | import org.json.JSONObject;
8 |
9 | import com.mclarkdev.tools.libextras.LibExtrasHashes;
10 |
11 | /**
12 | * CachedShare.java
13 | *
14 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
15 | */
16 | public class CachedShare extends CachedObject {
17 |
18 | private static final long serialVersionUID = 20241222L;
19 |
20 | private final String data;
21 |
22 | private final int entries;
23 |
24 | private final String hash;
25 |
26 | /**
27 | * Create a new share object from a given set of BarcodeRequest objects.
28 | *
29 | * @param requests
30 | */
31 | public CachedShare(List requests) {
32 | super("share");
33 |
34 | // Turn requests into JSON array
35 | JSONArray reqs = new JSONArray();
36 | for (BarcodeRequest r : requests) {
37 | reqs.put(r.encodeURI());
38 | }
39 |
40 | // Hash the data and save for later
41 | this.data = reqs.toString();
42 | this.entries = reqs.length();
43 | this.hash = LibExtrasHashes.sumMD5(data);
44 | }
45 |
46 | /**
47 | * Returns the data contained in the share.
48 | *
49 | * @return the data contained in the share
50 | */
51 | public String getData() {
52 |
53 | this.touch();
54 | return data;
55 | }
56 |
57 | /**
58 | * Returns the number of request entries in the share.
59 | *
60 | * @return the number of request entries in the share
61 | */
62 | public int getNumEntries() {
63 |
64 | return entries;
65 | }
66 |
67 | /**
68 | * Returns the hash of the share data, this is used as the access key.
69 | *
70 | * @return the hash of the share data
71 | */
72 | public String getHash() {
73 |
74 | return hash;
75 | }
76 |
77 | /**
78 | * Returns the share data encoded in a JSON object.
79 | *
80 | * @return share data in JSON format
81 | */
82 | public String encodeJSON() {
83 |
84 | return ((new JSONObject()) //
85 | .put("created", getTimeCreated())//
86 | .put("expires", getTimeExpires())//
87 | .put("accessed", getAccessCount())//
88 | .put("hash", getHash())//
89 | .put("data", getData())//
90 | ).toString();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/config/types/EAN8.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "EAN8",
3 | "display": "EAN 8",
4 | "show": true,
5 | "decode": true,
6 | "cache": true,
7 | "target": [
8 | "8",
9 | "ean-8",
10 | "ean8"
11 | ],
12 | "generator": ".Ean8Generator",
13 | "threads": 2,
14 | "priority": 88,
15 | "cost": {
16 | "basic": 1,
17 | "custom": 2
18 | },
19 | "checkdigit": 8,
20 | "nonprinting": false,
21 | "example": [
22 | "01234565"
23 | ],
24 | "pattern": {
25 | "auto": "^[0-9]{8}$",
26 | "extended": "^[0-9]{7,8}(\\+(([0-9]{2})|([0-9]{5}))){0,1}$"
27 | },
28 | "description": {
29 | "en": "Derived from the longer EAN-13 UPC code. For smaller packages."
30 | },
31 | "wiki": {
32 | "en": "https://en.wikipedia.org/wiki/EAN-8"
33 | },
34 | "options": {
35 | "dpi": {
36 | "name": "Resolution",
37 | "type": "number",
38 | "default": 150,
39 | "min": 50,
40 | "max": 300,
41 | "step": 10
42 | },
43 | "module": {
44 | "name": "Module",
45 | "type": "number",
46 | "default": 4,
47 | "min": 1,
48 | "max": 35,
49 | "step": 1
50 | },
51 | "qz": {
52 | "name": "Quiet Zone",
53 | "type": "number",
54 | "default": 4,
55 | "min": 0,
56 | "max": 20,
57 | "step": 1
58 | },
59 | "height": {
60 | "name": "Height",
61 | "type": "number",
62 | "default": 22,
63 | "min": 5,
64 | "max": 150,
65 | "step": 5
66 | },
67 | "font": {
68 | "name": "Font Size",
69 | "type": "number",
70 | "default": 5,
71 | "min": 0,
72 | "max": 20,
73 | "step": 2
74 | },
75 | "text": {
76 | "name": "Text Placement",
77 | "type": "option",
78 | "default": "bottom",
79 | "options": [
80 | "bottom",
81 | "top",
82 | "none"
83 | ]
84 | },
85 | "pattern": {
86 | "name": "Pattern",
87 | "type": "text",
88 | "default": "_",
89 | "pattern": "[ _]+"
90 | },
91 | "bg": {
92 | "name": "Background",
93 | "type": "text",
94 | "default": "ffffff",
95 | "pattern": "[0-9A-F]{6}"
96 | },
97 | "fg": {
98 | "name": "Foreground",
99 | "type": "text",
100 | "default": "000000",
101 | "pattern": "[0-9A-F]{6}"
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/config/types/ITF14.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ITF14",
3 | "display": "ITF-14",
4 | "show": true,
5 | "decode": true,
6 | "cache": true,
7 | "target": [
8 | "14",
9 | "itf14",
10 | "itf-14",
11 | "scc-14",
12 | "gtin"
13 | ],
14 | "generator": ".ITF14Generator",
15 | "threads": 2,
16 | "priority": 48,
17 | "cost": {
18 | "basic": 1,
19 | "custom": 2
20 | },
21 | "checkdigit": 0,
22 | "nonprinting": false,
23 | "example": [
24 | "98765432109213"
25 | ],
26 | "pattern": {
27 | "auto": "",
28 | "extended": "^[0-9]{14}$"
29 | },
30 | "description": {
31 | "en": "Interleaved 2 of 5, type 14."
32 | },
33 | "wiki": {
34 | "en": "https://en.wikipedia.org/wiki/Interleaved_2_of_5"
35 | },
36 | "options": {
37 | "dpi": {
38 | "name": "Resolution",
39 | "type": "number",
40 | "default": 150,
41 | "min": 50,
42 | "max": 300,
43 | "step": 10
44 | },
45 | "module": {
46 | "name": "Module",
47 | "type": "number",
48 | "default": 4,
49 | "min": 1,
50 | "max": 20,
51 | "step": 1
52 | },
53 | "qz": {
54 | "name": "Quiet Zone",
55 | "type": "number",
56 | "default": 4,
57 | "min": 4,
58 | "max": 25,
59 | "step": 2
60 | },
61 | "height": {
62 | "name": "Height",
63 | "type": "number",
64 | "default": 25,
65 | "min": 5,
66 | "max": 150,
67 | "step": 5
68 | },
69 | "font": {
70 | "name": "Font Size",
71 | "type": "number",
72 | "default": 5,
73 | "min": 0,
74 | "max": 20,
75 | "step": 2
76 | },
77 | "text": {
78 | "name": "Text Placement",
79 | "type": "option",
80 | "default": "bottom",
81 | "options": [
82 | "bottom",
83 | "top",
84 | "none"
85 | ]
86 | },
87 | "pattern": {
88 | "name": "Pattern",
89 | "type": "text",
90 | "default": "_ __ _____ _____ _",
91 | "pattern": "[ _]+"
92 | },
93 | "bg": {
94 | "name": "Background",
95 | "type": "text",
96 | "default": "ffffff",
97 | "pattern": "[0-9A-F]{6}"
98 | },
99 | "fg": {
100 | "name": "Foreground",
101 | "type": "text",
102 | "default": "000000",
103 | "pattern": "[0-9A-F]{6}"
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/config/types/Code39.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Code39",
3 | "display": "Code 39",
4 | "show": true,
5 | "decode": true,
6 | "cache": true,
7 | "target": [
8 | "39",
9 | "code-39",
10 | "code39"
11 | ],
12 | "generator": ".Code39Generator",
13 | "threads": 2,
14 | "priority": 68,
15 | "cost": {
16 | "basic": 2,
17 | "custom": 3
18 | },
19 | "checkdigit": 0,
20 | "nonprinting": false,
21 | "example": [
22 | "TRY 39"
23 | ],
24 | "pattern": {
25 | "auto": "^[A-Z0-9 $.\\/]{1,12}$",
26 | "extended": "^[A-Z*0-9 \\-$%.\\/+]{1,30}$"
27 | },
28 | "description": {
29 | "en": "A basic alphanumeric barcode. Uppercase letters and numbers only."
30 | },
31 | "wiki": {
32 | "en": "https://en.wikipedia.org/wiki/Code_39"
33 | },
34 | "options": {
35 | "dpi": {
36 | "name": "Resolution",
37 | "type": "number",
38 | "default": 150,
39 | "min": 50,
40 | "max": 300,
41 | "step": 10
42 | },
43 | "module": {
44 | "name": "Module",
45 | "type": "number",
46 | "default": 4,
47 | "min": 1,
48 | "max": 35,
49 | "step": 1
50 | },
51 | "qz": {
52 | "name": "Quiet Zone",
53 | "type": "number",
54 | "default": 4,
55 | "min": 0,
56 | "max": 20,
57 | "step": 1
58 | },
59 | "height": {
60 | "name": "Height",
61 | "type": "number",
62 | "default": 22,
63 | "min": 5,
64 | "max": 150,
65 | "step": 5
66 | },
67 | "font": {
68 | "name": "Font Size",
69 | "type": "number",
70 | "default": 5,
71 | "min": 0,
72 | "max": 20,
73 | "step": 2
74 | },
75 | "text": {
76 | "name": "Text Placement",
77 | "type": "option",
78 | "default": "bottom",
79 | "options": [
80 | "bottom",
81 | "top",
82 | "none"
83 | ]
84 | },
85 | "pattern": {
86 | "name": "Pattern",
87 | "type": "text",
88 | "default": "_",
89 | "pattern": "[ _]+"
90 | },
91 | "bg": {
92 | "name": "Background",
93 | "type": "text",
94 | "default": "ffffff",
95 | "pattern": "[0-9A-F]{6}"
96 | },
97 | "fg": {
98 | "name": "Foreground",
99 | "type": "text",
100 | "default": "000000",
101 | "pattern": "[0-9A-F]{6}"
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/config/types/EAN13.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "EAN13",
3 | "display": "EAN 13",
4 | "show": true,
5 | "decode": true,
6 | "cache": true,
7 | "target": [
8 | "13",
9 | "ean-13",
10 | "ean13"
11 | ],
12 | "generator": ".Ean13Generator",
13 | "threads": 2,
14 | "priority": 85,
15 | "cost": {
16 | "basic": 1,
17 | "custom": 2
18 | },
19 | "checkdigit": 13,
20 | "nonprinting": false,
21 | "example": [
22 | "1234567890128",
23 | "1123332122321+11252"
24 | ],
25 | "pattern": {
26 | "auto": "^[0-9]{13}$",
27 | "extended": "^[0-9]{12,13}(\\+(([0-9]{2})|([0-9]{5}))){0,1}$"
28 | },
29 | "description": {
30 | "en": "A globally recognized product identification code. Encodes manufacturer, product / variant, and store use codes."
31 | },
32 | "wiki": {
33 | "en": "https://en.wikipedia.org/wiki/International_Article_Number"
34 | },
35 | "options": {
36 | "dpi": {
37 | "name": "Resolution",
38 | "type": "number",
39 | "default": 150,
40 | "min": 50,
41 | "max": 300,
42 | "step": 10
43 | },
44 | "module": {
45 | "name": "Module",
46 | "type": "number",
47 | "default": 4,
48 | "min": 1,
49 | "max": 35,
50 | "step": 1
51 | },
52 | "qz": {
53 | "name": "Quiet Zone",
54 | "type": "number",
55 | "default": 4,
56 | "min": 0,
57 | "max": 20,
58 | "step": 1
59 | },
60 | "height": {
61 | "name": "Height",
62 | "type": "number",
63 | "default": 22,
64 | "min": 5,
65 | "max": 150,
66 | "step": 5
67 | },
68 | "font": {
69 | "name": "Font Size",
70 | "type": "number",
71 | "default": 5,
72 | "min": 0,
73 | "max": 20,
74 | "step": 2
75 | },
76 | "text": {
77 | "name": "Text Placement",
78 | "type": "option",
79 | "default": "bottom",
80 | "options": [
81 | "bottom",
82 | "top",
83 | "none"
84 | ]
85 | },
86 | "pattern": {
87 | "name": "Pattern",
88 | "type": "text",
89 | "default": "_",
90 | "pattern": "[ _]+"
91 | },
92 | "bg": {
93 | "name": "Background",
94 | "type": "text",
95 | "default": "ffffff",
96 | "pattern": "[0-9A-F]{6}"
97 | },
98 | "fg": {
99 | "name": "Foreground",
100 | "type": "text",
101 | "default": "000000",
102 | "pattern": "[0-9A-F]{6}"
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/config/types/UPC_E.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "UPC_E",
3 | "display": "UPC E",
4 | "show": true,
5 | "decode": true,
6 | "cache": true,
7 | "target": [
8 | "e",
9 | "upc-e",
10 | "upce"
11 | ],
12 | "generator": ".UPCEGenerator",
13 | "threads": 2,
14 | "priority": 98,
15 | "cost": {
16 | "basic": 1,
17 | "custom": 3
18 | },
19 | "checkdigit": 8,
20 | "nonprinting": false,
21 | "example": [
22 | "01023459"
23 | ],
24 | "pattern": {
25 | "auto": "^(0|1)[0-9]{7}$",
26 | "extended": "^(0|1)[0-9]{6,7}(\\+(([0-9]{2})|([0-9]{5}))){0,1}$"
27 | },
28 | "description": {
29 | "en": "A compact version of UPC-A, removing unneeded '0's."
30 | },
31 | "wiki": {
32 | "en": "https://en.wikipedia.org/wiki/Universal_Product_Code"
33 | },
34 | "options": {
35 | "dpi": {
36 | "name": "Resolution",
37 | "type": "number",
38 | "default": 150,
39 | "min": 50,
40 | "max": 300,
41 | "step": 10
42 | },
43 | "module": {
44 | "name": "Module",
45 | "type": "number",
46 | "default": 4,
47 | "min": 1,
48 | "max": 35,
49 | "step": 1
50 | },
51 | "qz": {
52 | "name": "Quiet Zone",
53 | "type": "number",
54 | "default": 4,
55 | "min": 0,
56 | "max": 20,
57 | "step": 1
58 | },
59 | "height": {
60 | "name": "Height",
61 | "type": "number",
62 | "default": 22,
63 | "min": 5,
64 | "max": 150,
65 | "step": 5
66 | },
67 | "font": {
68 | "name": "Font Size",
69 | "type": "number",
70 | "default": 5,
71 | "min": 0,
72 | "max": 20,
73 | "step": 2
74 | },
75 | "text": {
76 | "name": "Text Placement",
77 | "type": "option",
78 | "default": "bottom",
79 | "options": [
80 | "bottom",
81 | "top",
82 | "none"
83 | ]
84 | },
85 | "pattern": {
86 | "name": "Pattern",
87 | "type": "text",
88 | "default": "_",
89 | "pattern": "[ _]+"
90 | },
91 | "bg": {
92 | "name": "Background",
93 | "type": "text",
94 | "default": "ffffff",
95 | "pattern": "[0-9A-F]{6}"
96 | },
97 | "fg": {
98 | "name": "Foreground",
99 | "type": "text",
100 | "default": "000000",
101 | "pattern": "[0-9A-F]{6}"
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/Launcher.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi;
2 |
3 | import org.barcodeapi.core.ServerLauncher;
4 |
5 | /**
6 | * Launcher.java
7 | *
8 | * This class should handle the master life-cycle of the BarcodeAPI.org
9 | * application, web server, and sub components there-in.
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
12 | */
13 | public class Launcher {
14 |
15 | // The instance of the running application.
16 | private static ServerLauncher apiServer;
17 |
18 | /**
19 | * This method should serve as the main entry point for the application when
20 | * being started by any means, it should catch and properly log any fatal
21 | * exceptions which might arise at any time in the applications life before it
22 | * is properly terminated.
23 | *
24 | * @param args Any arguments passed to the application from the command line.
25 | */
26 | public static void main(String[] args) {
27 |
28 | // Print startup message
29 | System.out.println("");
30 | System.out.println("Starting...");
31 | System.out.println("");
32 |
33 | try {
34 |
35 | // Instantiate the loader
36 | apiServer = new ServerLauncher(args);
37 |
38 | // Launch the system
39 | apiServer.launch();
40 |
41 | } catch (Exception e) {
42 |
43 | // Log launch exception and quit
44 | e.printStackTrace(System.err);
45 | Launcher.invokeSystemShutdown(1);
46 |
47 | } catch (NoClassDefFoundError e) {
48 |
49 | // Log error and quit on launch dependency failure
50 | System.out.println("Missing required libraries. Exiting.");
51 | System.err.println("Missing required libraries. Exiting.");
52 | e.printStackTrace(System.err);
53 | Launcher.invokeSystemShutdown(1);
54 | }
55 | }
56 |
57 | /**
58 | * This method may be called by any class, at any time, to request that the
59 | * application be terminated with the given exit code.
60 | *
61 | * @param exitCode The exit code to be returned to the operating system.
62 | */
63 | public static void invokeSystemShutdown(final int exitCode) {
64 |
65 | // Attempt a graceful shutdown the server
66 | apiServer.stop();
67 |
68 | // Run this in a dedicated thread so the call returns
69 | new Thread(new Runnable() {
70 |
71 | public void run() {
72 |
73 | // Exit the JVM with a given status code
74 | System.exit(exitCode);
75 | }
76 | }).start();
77 | }
78 | }
--------------------------------------------------------------------------------
/config/types/UPC_A.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "UPC_A",
3 | "display": "UPC A",
4 | "show": true,
5 | "decode": true,
6 | "cache": true,
7 | "target": [
8 | "a",
9 | "upc-a",
10 | "upca",
11 | "upc"
12 | ],
13 | "generator": ".UPCAGenerator",
14 | "threads": 2,
15 | "priority": 95,
16 | "cost": {
17 | "basic": 1,
18 | "custom": 3
19 | },
20 | "checkdigit": 12,
21 | "nonprinting": false,
22 | "example": [
23 | "123456789012"
24 | ],
25 | "pattern": {
26 | "auto": "^[0-9]{12}$",
27 | "extended": "^[0-9]{11,12}(\\+(([0-9]{2})|([0-9]{5}))){0,1}$"
28 | },
29 | "description": {
30 | "en": "A universally recognized 12 digit barcode. Encodes manufacturer, product / variant, and store use codes."
31 | },
32 | "wiki": {
33 | "en": "https://en.wikipedia.org/wiki/Universal_Product_Code"
34 | },
35 | "options": {
36 | "dpi": {
37 | "name": "Resolution",
38 | "type": "number",
39 | "default": 150,
40 | "min": 50,
41 | "max": 300,
42 | "step": 10
43 | },
44 | "module": {
45 | "name": "Module",
46 | "type": "number",
47 | "default": 4,
48 | "min": 1,
49 | "max": 35,
50 | "step": 1
51 | },
52 | "qz": {
53 | "name": "Quiet Zone",
54 | "type": "number",
55 | "default": 4,
56 | "min": 0,
57 | "max": 20,
58 | "step": 1
59 | },
60 | "height": {
61 | "name": "Height",
62 | "type": "number",
63 | "default": 22,
64 | "min": 5,
65 | "max": 150,
66 | "step": 5
67 | },
68 | "font": {
69 | "name": "Font Size",
70 | "type": "number",
71 | "default": 5,
72 | "min": 0,
73 | "max": 20,
74 | "step": 2
75 | },
76 | "text": {
77 | "name": "Text Placement",
78 | "type": "option",
79 | "default": "bottom",
80 | "options": [
81 | "bottom",
82 | "top",
83 | "none"
84 | ]
85 | },
86 | "pattern": {
87 | "name": "Pattern",
88 | "type": "text",
89 | "default": "_",
90 | "pattern": "[ _]+"
91 | },
92 | "bg": {
93 | "name": "Background",
94 | "type": "text",
95 | "default": "ffffff",
96 | "pattern": "[0-9A-F]{6}"
97 | },
98 | "fg": {
99 | "name": "Foreground",
100 | "type": "text",
101 | "default": "000000",
102 | "pattern": "[0-9A-F]{6}"
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/config/types/Code128.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Code128",
3 | "display": "Code 128",
4 | "show": true,
5 | "decode": true,
6 | "cache": true,
7 | "target": [
8 | "128",
9 | "code-128",
10 | "code128"
11 | ],
12 | "generator": ".Code128Generator",
13 | "threads": 2,
14 | "priority": 65,
15 | "cost": {
16 | "basic": 2,
17 | "custom": 3
18 | },
19 | "checkdigit": 0,
20 | "nonprinting": true,
21 | "example": [
22 | "Try Me!",
23 | "ABCD-1234"
24 | ],
25 | "pattern": {
26 | "auto": "^[ !#$()*.\\/0-9=?A-Za-z~]{1,12}$",
27 | "extended": "^[ !\\\"#$%&\\'()*+,\\-.\\/0-9:;<=>?@A-Z\\[\\]^_`a-z{|}~]{1,48}$"
28 | },
29 | "description": {
30 | "en": "A medium densisty alphanumeric barcode with a larger character set."
31 | },
32 | "wiki": {
33 | "en": "https://en.wikipedia.org/wiki/Code_128"
34 | },
35 | "options": {
36 | "dpi": {
37 | "name": "Resolution",
38 | "type": "number",
39 | "default": 150,
40 | "min": 50,
41 | "max": 300,
42 | "step": 10
43 | },
44 | "module": {
45 | "name": "Module",
46 | "type": "number",
47 | "default": 4,
48 | "min": 1,
49 | "max": 35,
50 | "step": 1
51 | },
52 | "qz": {
53 | "name": "Quiet Zone",
54 | "type": "number",
55 | "default": 4,
56 | "min": 0,
57 | "max": 20,
58 | "step": 1
59 | },
60 | "height": {
61 | "name": "Height",
62 | "type": "number",
63 | "default": 22,
64 | "min": 5,
65 | "max": 150,
66 | "step": 5
67 | },
68 | "font": {
69 | "name": "Font Size",
70 | "type": "number",
71 | "default": 5,
72 | "min": 0,
73 | "max": 20,
74 | "step": 2
75 | },
76 | "text": {
77 | "name": "Text Placement",
78 | "type": "option",
79 | "default": "bottom",
80 | "options": [
81 | "bottom",
82 | "top",
83 | "none"
84 | ]
85 | },
86 | "pattern": {
87 | "name": "Pattern",
88 | "type": "text",
89 | "default": "_",
90 | "pattern": "[ _]+"
91 | },
92 | "bg": {
93 | "name": "Background",
94 | "type": "text",
95 | "default": "ffffff",
96 | "pattern": "[0-9A-F]{6}"
97 | },
98 | "fg": {
99 | "name": "Foreground",
100 | "type": "text",
101 | "default": "000000",
102 | "pattern": "[0-9A-F]{6}"
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/core/CodeTypes.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.core;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.HashMap;
6 | import java.util.Set;
7 |
8 | import org.json.JSONException;
9 | import org.json.JSONObject;
10 |
11 | import com.mclarkdev.tools.libextras.LibExtrasStreams;
12 | import com.mclarkdev.tools.liblog.LibLog;
13 |
14 | /**
15 | * CodeTypes.java
16 | *
17 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
18 | */
19 | public class CodeTypes {
20 |
21 | private static final CodeTypes typeCache = new CodeTypes();
22 |
23 | public static CodeTypes inst() {
24 | return typeCache;
25 | }
26 |
27 | private final File typeDir = new File("config", "types");
28 |
29 | private final HashMap codeTypes;
30 |
31 | private CodeTypes() {
32 | codeTypes = new HashMap<>();
33 | }
34 |
35 | /**
36 | * Initializes a give CodeType from config.
37 | *
38 | * @param name the name of the config file
39 | * @return the loaded CodeType
40 | */
41 | public CodeType loadType(String name) {
42 |
43 | // Setup and log config file name
44 | File typeFile = new File(typeDir, (name + ".json"));
45 | LibLog._clogF("I0061", typeFile.getPath());
46 |
47 | try {
48 |
49 | // Read config file, parse as JSON
50 | JSONObject typeConfig = new JSONObject(//
51 | LibExtrasStreams.readFile(typeFile));
52 |
53 | // Initialize and log the CodeType
54 | CodeType codeType = CodeType.fromJSON(typeConfig);
55 | LibLog._clogF("I0062", codeType.getName(), codeType.getNumThreads());
56 |
57 | // Add to map and return the type
58 | codeTypes.put(name, codeType);
59 | return codeType;
60 |
61 | } catch (JSONException | IOException e) {
62 |
63 | // Print and throw failure loading type
64 | throw LibLog._clog("E0069", e).asException();
65 | }
66 | }
67 |
68 | /**
69 | * Returns a list of all supported types.
70 | *
71 | * @return a list of all supported types
72 | */
73 | public Set getTypes() {
74 |
75 | return codeTypes.keySet();
76 | }
77 |
78 | /**
79 | * Returns the CodeType object for a give name.
80 | *
81 | * @param name the name of the CodeType
82 | * @return the CodeType object
83 | */
84 | public CodeType getType(String name) {
85 |
86 | return codeTypes.get(name);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/api/TypeHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.api;
2 |
3 | import java.io.IOException;
4 | import java.util.HashMap;
5 |
6 | import javax.servlet.http.HttpServletResponse;
7 |
8 | import org.barcodeapi.server.core.CodeType;
9 | import org.barcodeapi.server.core.CodeTypes;
10 | import org.barcodeapi.server.core.RequestContext;
11 | import org.barcodeapi.server.core.RestHandler;
12 | import org.barcodeapi.server.core.TypeSelector;
13 | import org.json.JSONArray;
14 | import org.json.JSONException;
15 | import org.json.JSONObject;
16 |
17 | /**
18 | * TypeHandler.java
19 | *
20 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
21 | */
22 | public class TypeHandler extends RestHandler {
23 |
24 | // Static map of JSON responses
25 | private static final HashMap typesConfig;
26 |
27 | private static final String typesComplete;
28 |
29 | static {
30 | typesConfig = new HashMap<>();
31 | CodeTypes types = CodeTypes.inst();
32 |
33 | // Convert each type to JSON, and cache
34 | JSONArray complete = new JSONArray();
35 | for (String type : types.getTypes()) {
36 | CodeType t = types.getType(type);
37 | JSONObject details = CodeType.toJSON(t);
38 |
39 | complete.put(details);
40 |
41 | typesConfig.put(t, details.toString());
42 | }
43 |
44 | typesComplete = complete.toString();
45 | }
46 |
47 | public TypeHandler() {
48 | super();
49 | }
50 |
51 | @Override
52 | protected void onRequest(RequestContext c, HttpServletResponse r) throws JSONException, IOException {
53 |
54 | // Check for user requested type string
55 | String typeStr = c.getRequest().getParameter("type");
56 | if (typeStr == null || typeStr.trim().equals("")) {
57 |
58 | // Print all types response to client
59 | r.setStatus(HttpServletResponse.SC_OK);
60 | r.setContentType("application/json");
61 | r.getOutputStream().println(typesComplete);
62 | return;
63 | }
64 |
65 | // Lookup requested type string
66 | CodeType type = TypeSelector.getTypeFromString(typeStr);
67 |
68 | // Fail if type not found
69 | if (type == null) {
70 |
71 | // Print error to client
72 | r.setStatus(HttpServletResponse.SC_BAD_REQUEST);
73 | return;
74 | }
75 |
76 | // Print single type response to client
77 | r.setStatus(HttpServletResponse.SC_OK);
78 | r.setContentType("application/json");
79 | r.getOutputStream().println(typesConfig.get(type));
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/resources/ext/js/bulk.js:
--------------------------------------------------------------------------------
1 | //
2 | // BarcodeAPI.org, 2017-2025
3 | // bulk.js // bulk.html
4 | //
5 |
6 | var submitButton;
7 |
8 | function init() {
9 |
10 | submitButton = document.getElementById("generate-bc");
11 |
12 | document.getElementsByClassName("link-csv")[0].addEventListener('click', actionDownloadCSV);
13 | document.getElementById("csvFile").addEventListener('change', checkIfFileSelected);
14 |
15 | // Log tracking event
16 | var setupMillis = ((new Date()) - timeStart);
17 | trackingEvent("AppEvents", "AppLoad", "Bulk", setupMillis);
18 | }
19 |
20 | function actionDownloadCSV() {
21 |
22 | var blob = "";
23 | for (var line in csvExample) {
24 | blob += (csvExample[line] + "\n");
25 | }
26 |
27 | // Create download link
28 | var file = new Blob([blob], { type: 'text/csv' });
29 | var a = document.createElement("a");
30 | a.href = URL.createObjectURL(file);
31 | a.download = 'bulk-barcodes-example.csv';
32 | document.body.appendChild(a);
33 | a.click();
34 | document.body.removeChild(a);
35 |
36 | // Log tracking event
37 | trackingEvent("Bulk", "Download", "Example");
38 | }
39 |
40 | function checkIfFileSelected(obj) {
41 |
42 | const fileInput = obj.target;
43 | const file = fileInput.files[0];
44 |
45 | if (file) {
46 | const fileName = file.name.toLowerCase();
47 | const isCSV = fileName.endsWith('.csv');
48 |
49 | if (isCSV) {
50 | submitButton.removeAttribute("disabled");
51 | } else {
52 | submitButton.setAttribute("disabled", "true");
53 | alert("Please select a CSV file.");
54 | }
55 | } else {
56 | submitButton.setAttribute("disabled", "true");
57 | }
58 | }
59 |
60 | function onUpload(e) {
61 |
62 | // Log tracking event
63 | trackingEvent("Bulk", "Upload");
64 |
65 | return false;
66 | }
67 |
68 | var csvExample = [
69 | "Aztec Barcode,aztec",
70 | "1234567890,codabar",
71 | "Try Me!,128",
72 | "TRY 39 ME,39",
73 | "Data Matrix Barcode,dm",
74 | "1234567890128,13",
75 | "01234565,8",
76 | "98765432109213,14",
77 | "PDF - 417,417",
78 | "QR Barcode,qr",
79 | "11212345612345678,royal",
80 | "123456789012,a",
81 | "01023459,e",
82 | "0123456709498765432101234567891,usps",
83 | "test-128-colors,128,fg=206060,bg=c0c0c0",
84 | "test-128-notext,128,height=8,text=none",
85 | "test-dm-color,dm,fg=602060,bg=d0d0d0",
86 | "test-dm-noqz,dm,qz=0",
87 | "00000000,8,text=none,fg=ff0000"
88 | ];
89 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/api/StaticHandler.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.api;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.ServletException;
6 | import javax.servlet.http.HttpServletRequest;
7 | import javax.servlet.http.HttpServletResponse;
8 |
9 | import org.barcodeapi.core.Config;
10 | import org.barcodeapi.core.Config.Cfg;
11 | import org.barcodeapi.server.core.RequestContext;
12 | import org.barcodeapi.server.core.RestHandler;
13 | import org.eclipse.jetty.server.Request;
14 | import org.eclipse.jetty.server.Server;
15 | import org.eclipse.jetty.server.handler.ResourceHandler;
16 |
17 | /**
18 | * StaticHandler.java
19 | *
20 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
21 | */
22 | public class StaticHandler extends RestHandler {
23 |
24 | private static final int CACHED_LIFE_MIN = Config.get(Cfg.App)//
25 | .getJSONObject("client").getInt("cacheStatic");
26 |
27 | private static final int CACHED_LIFE_SEC = (CACHED_LIFE_MIN * 60);
28 |
29 | private static final int CACHED_LIFE_MS = (CACHED_LIFE_SEC * 1000);
30 |
31 | private ResourceHandler resources = new ResourceHandler();
32 |
33 | public StaticHandler(Server server) throws Exception {
34 | super();
35 |
36 | // Load the Jetty resource handler
37 | resources = new ResourceHandler();
38 | resources.setServer(server);
39 | resources.setResourceBase("resources");
40 | resources.setRedirectWelcome(true);
41 | resources.setWelcomeFiles(new String[] { "index.html" });
42 | resources.setCacheControl(("max-age=" + CACHED_LIFE_SEC));
43 | resources.start();
44 | }
45 |
46 | @Override
47 | public void _impl(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
48 | throws IOException, ServletException {
49 |
50 | // Calculate and set cache expiration time
51 | response.setDateHeader("expires", //
52 | (System.currentTimeMillis() + CACHED_LIFE_MS));
53 |
54 | // Call through to resources
55 | baseRequest.setHandled(false);
56 | resources.handle(target, baseRequest, request, response);
57 |
58 | // Send non resources to API
59 | if (!baseRequest.isHandled()) {
60 | response.sendRedirect("/api/auto" + request.getPathInfo());
61 | }
62 | }
63 |
64 | @Override
65 | protected void onRequest(RequestContext c, HttpServletResponse r) throws Exception {
66 |
67 | // Do Nothing.
68 | // This class overrides the lower level (handle) method to serve static files.
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/gen/TestURLs.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.gen;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.eclipse.jetty.http.HttpStatus;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 |
8 | /**
9 | * TestURLs.java
10 | *
11 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
12 | */
13 | public class TestURLs extends ServerTestBase {
14 |
15 | @Test
16 | public void testURL_TestHTTP() {
17 |
18 | apiGet("http://barcodeapi.org/");
19 |
20 | Assert.assertEquals("Response Code", //
21 | HttpStatus.OK_200, getResponseCode());
22 |
23 | Assert.assertEquals("Code Type", //
24 | "QRCode", getHeader("X-Barcode-Type"));
25 |
26 | Assert.assertEquals("Code Data", //
27 | encode("http://barcodeapi.org/"), getHeader("X-Barcode-Content"));
28 | }
29 |
30 | @Test
31 | public void testURL_TestHTTPS() {
32 |
33 | apiGet("https://barcodeapi.org/");
34 |
35 | Assert.assertEquals("Response Code", //
36 | HttpStatus.OK_200, getResponseCode());
37 |
38 | Assert.assertEquals("Code Type", //
39 | "QRCode", getHeader("X-Barcode-Type"));
40 |
41 | Assert.assertEquals("Code Data", //
42 | encode("https://barcodeapi.org/"), getHeader("X-Barcode-Content"));
43 | }
44 |
45 | @Test
46 | public void testURL_TestBarcodeOptions() {
47 |
48 | apiGet("https://barcodeapi.org/", "size=20");
49 |
50 | Assert.assertEquals("Response Code", //
51 | HttpStatus.OK_200, getResponseCode());
52 |
53 | Assert.assertEquals("Code Type", //
54 | "QRCode", getHeader("X-Barcode-Type"));
55 |
56 | Assert.assertEquals("Code Data", //
57 | encode("https://barcodeapi.org/"), getHeader("X-Barcode-Content"));
58 | }
59 |
60 | @Test
61 | public void testURL_TestURLOptions() {
62 |
63 | apiGet("https://barcodeapi.org/", "product=123");
64 |
65 | Assert.assertEquals("Response Code", //
66 | HttpStatus.OK_200, getResponseCode());
67 |
68 | Assert.assertEquals("Code Type", //
69 | "QRCode", getHeader("X-Barcode-Type"));
70 |
71 | Assert.assertEquals("Code Data", //
72 | encode("https://barcodeapi.org/?product=123"), getHeader("X-Barcode-Content"));
73 | }
74 |
75 | @Test
76 | public void testURL_TestURLEncoded() {
77 |
78 | apiGet("https://barcodeapi.org/?product=123");
79 |
80 | Assert.assertEquals("Response Code", //
81 | HttpStatus.OK_200, getResponseCode());
82 |
83 | Assert.assertEquals("Code Type", //
84 | "QRCode", getHeader("X-Barcode-Type"));
85 |
86 | Assert.assertEquals("Code Data", //
87 | encode("https://barcodeapi.org/?product=123"), getHeader("X-Barcode-Content"));
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/core/Config.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.core;
2 |
3 | import java.io.File;
4 | import java.util.Map;
5 | import java.util.concurrent.ConcurrentHashMap;
6 |
7 | import org.json.JSONObject;
8 |
9 | import com.mclarkdev.tools.libargs.LibArgs;
10 | import com.mclarkdev.tools.libextras.LibExtrasStreams;
11 |
12 | public class Config {
13 |
14 | public enum Cfg {
15 | App, Blacklist, Admins, Plans, Subscribers;
16 | }
17 |
18 | private static final String dist = LibArgs.instance().getString("config", "community");
19 |
20 | private static final Map configs = new ConcurrentHashMap();
21 |
22 | /**
23 | * Retrieves the name of the configuration distribution currently in use.
24 | *
25 | * @return name of the configuration distribution in use
26 | */
27 | public static String dist() {
28 | return dist;
29 | }
30 |
31 | /**
32 | * Retrieve application configuration.
33 | *
34 | * @param cfg configuration type
35 | * @return the cached configuration object
36 | */
37 | public static JSONObject get(Cfg cfg) {
38 | return get(cfg, false);
39 | }
40 |
41 | /**
42 | * Retrieve application configuration.
43 | *
44 | * @param cfg configuration type
45 | * @param forceReload force reload from disk
46 | * @return the cached configuration object
47 | */
48 | public static JSONObject get(Cfg cfg, boolean forceReload) {
49 |
50 | // Check if already loaded or forcing a reload
51 | if ((!configs.containsKey(cfg)) || forceReload) {
52 |
53 | // Reload from disk and add to map
54 | configs.put(cfg, loadConfig(cfg));
55 | }
56 |
57 | // Return the cached data
58 | return configs.get(cfg);
59 | }
60 |
61 | /**
62 | * Load a given configuration file from disk.
63 | *
64 | * @param cfg configuration type
65 | * @return the loaded configuration object
66 | */
67 | private static final JSONObject loadConfig(Cfg cfg) {
68 |
69 | try {
70 |
71 | // Determine name of file loaded
72 | String confName = (cfg.toString().toLowerCase());
73 |
74 | // Find file on disk
75 | File confFile = new File(//
76 | new File("config", dist), //
77 | (confName + ".json"));
78 |
79 | // Read the file as a raw string
80 | String raw = LibExtrasStreams.readFile(confFile);
81 |
82 | // Return string parsed as JSON
83 | return new JSONObject(raw);
84 |
85 | } catch (Error | Exception e) {
86 |
87 | // Log general exception
88 | throw new Error("Failed loading app config.", e);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/cache/Subscriber.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.cache;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONObject;
5 |
6 | /**
7 | * CachedSession.java
8 | *
9 | * A user session object allowing for the detailed tracking of individual user
10 | * usage activities across all handlers.
11 | *
12 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
13 | */
14 | public class Subscriber {
15 |
16 | private final String customer;
17 | private final long subscribed;
18 | private final boolean active;
19 | private final boolean enforce;
20 | private final int limit;
21 | private final int batch;
22 |
23 | private final JSONArray ips;
24 | private final JSONArray keys;
25 | private final JSONArray apps;
26 |
27 | public Subscriber(JSONObject def) {
28 |
29 | // Basic subscriber details
30 | this.customer = def.getString("customer");
31 | this.subscribed = def.getLong("subscribed");
32 |
33 | // Account active / enforced
34 | this.active = def.getBoolean("active");
35 | this.enforce = def.getBoolean("enforce");
36 |
37 | // Account limits
38 | this.limit = def.getInt("limit");
39 | this.batch = def.getInt("batch");
40 |
41 | // Account associations
42 | this.ips = def.optJSONArray("ips", new JSONArray());
43 | this.keys = def.optJSONArray("keys", new JSONArray());
44 | this.apps = def.optJSONArray("apps", new JSONArray());
45 | }
46 |
47 | public String getCustomer() {
48 | return this.customer;
49 | }
50 |
51 | public long getSubscribed() {
52 | return this.subscribed;
53 | }
54 |
55 | public boolean getActive() {
56 | return this.active;
57 | }
58 |
59 | public boolean isEnforced() {
60 | return this.enforce;
61 | }
62 |
63 | public int getLimit() {
64 | return this.limit;
65 | }
66 |
67 | public int getMaxBatch() {
68 | return this.batch;
69 | }
70 |
71 | public JSONArray getIPs() {
72 | return this.ips;
73 | }
74 |
75 | public JSONArray getKeys() {
76 | return this.keys;
77 | }
78 |
79 | public JSONArray getApps() {
80 | return this.apps;
81 | }
82 |
83 | /**
84 | * Returns the subscriber info as a JSON object.
85 | *
86 | * @return the subscriber info in JSON format
87 | */
88 | public JSONObject asJSON() {
89 |
90 | return new JSONObject() //
91 | .put("customer", getCustomer())//
92 | .put("subscribed", getSubscribed())//
93 | .put("active", getActive())//
94 | .put("enforce", isEnforced())//
95 | .put("limit", getLimit())//
96 | .put("batch", getMaxBatch())//
97 | .put("ips", getIPs())//
98 | .put("keys", getKeys());
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/resources/types.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | BarcodeAPI.org - Barcode Types
10 |
11 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
43 |
Barcode Types
44 |
45 | The following barcode types are supported by the server.
46 |
47 |
48 | View our
User Manual for additional
49 | information.
50 |
51 |
52 | Click a barcode format to learn more.
53 |
54 |
55 |
56 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/core/TestBarcodeOptions.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.core;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.barcodeapi.server.core.GenerationException;
5 | import org.barcodeapi.server.gen.BarcodeRequest;
6 | import org.eclipse.jetty.http.HttpStatus;
7 | import org.junit.Assert;
8 | import org.junit.Test;
9 |
10 | /**
11 | * TestBarcodeOptions.java
12 | *
13 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2025)
14 | */
15 | public class TestBarcodeOptions extends ServerTestBase {
16 |
17 | @Test
18 | public void testBarcodeRequest_TestStripDefaults() {
19 |
20 | try {
21 | BarcodeRequest req = //
22 | BarcodeRequest.fromURI("/api/auto/abc?fg=000000&bg=ffffff");
23 |
24 | Assert.assertFalse("Request is not Simple", req.isComplex());
25 | } catch (GenerationException e) {
26 |
27 | Assert.fail("Failed to create request.");
28 | }
29 | }
30 |
31 | @Test
32 | public void testBarcodeOptions_Options() {
33 |
34 | try {
35 | BarcodeRequest r = BarcodeRequest//
36 | .fromURI("/api/128/barcode_test?bg=fffff0&fg=00000f");
37 |
38 | Assert.assertEquals("Request Type", //
39 | "Code128", r.getType().getName());
40 |
41 | Assert.assertEquals("Request Data", //
42 | "barcode_test", r.getData());
43 |
44 | Assert.assertEquals("Request Options", //
45 | 2, r.getOptions().length());
46 |
47 | Assert.assertEquals("Request Options (FG)", //
48 | "00000f", r.getOptions().getString("fg"));
49 |
50 | Assert.assertEquals("Request Options (BG)", //
51 | "fffff0", r.getOptions().getString("bg"));
52 |
53 | } catch (GenerationException e) {
54 |
55 | Assert.fail(e.getMessage());
56 | }
57 | }
58 |
59 | @Test
60 | public void testBarcodeOptions_MissingOption() {
61 |
62 | apiGet("128/RD309874", "dpi=100&&size=20");
63 |
64 | Assert.assertEquals("Response Code", //
65 | HttpStatus.OK_200, getResponseCode());
66 |
67 | Assert.assertEquals("Code Type", //
68 | "Code128", getHeader("X-Barcode-Type"));
69 |
70 | Assert.assertEquals("Code Data", //
71 | "RD309874", getHeader("X-Barcode-Content"));
72 |
73 | Assert.assertEquals("Code Format", //
74 | "image/png;charset=utf-8", getHeader("Content-Type"));
75 | }
76 |
77 | @Test
78 | public void testBarcodeOptions_DoubleOption() {
79 |
80 | apiGet("128/RD309874", "dpi=100&dpi=120");
81 |
82 | Assert.assertEquals("Response Code", //
83 | HttpStatus.OK_200, getResponseCode());
84 |
85 | Assert.assertEquals("Code Type", //
86 | "Code128", getHeader("X-Barcode-Type"));
87 |
88 | Assert.assertEquals("Code Data", //
89 | "RD309874", getHeader("X-Barcode-Content"));
90 |
91 | Assert.assertEquals("Code Format", //
92 | "image/png;charset=utf-8", getHeader("Content-Type"));
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/core/CodeGenerators.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.core;
2 |
3 | import java.io.File;
4 | import java.io.FileWriter;
5 | import java.io.IOException;
6 | import java.util.HashMap;
7 |
8 | import org.barcodeapi.core.Config;
9 | import org.barcodeapi.core.Config.Cfg;
10 | import org.barcodeapi.server.gen.CodeGenerator;
11 | import org.json.JSONArray;
12 |
13 | import com.mclarkdev.tools.liblog.LibLog;
14 | import com.mclarkdev.tools.libmetrics.LibMetrics;
15 | import com.mclarkdev.tools.libobjectpooler.LibObjectPooler;
16 |
17 | /**
18 | * CodeGenerators.java
19 | *
20 | * Initializes all supported CodeTypes from configuration and provides access to
21 | * their associated generation object pools.
22 | *
23 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
24 | */
25 | public class CodeGenerators {
26 |
27 | private static CodeGenerators codeGenerators;
28 |
29 | /**
30 | * Returns an instance of the CodeGenerators map.
31 | *
32 | * @return an instance of the CodeGenerators map
33 | */
34 | public static synchronized CodeGenerators getInstance() {
35 | if (codeGenerators == null) {
36 | codeGenerators = new CodeGenerators();
37 | }
38 | return codeGenerators;
39 | }
40 |
41 | /**
42 | * A map of generation pools for all supported CodeTypes.
43 | */
44 | private HashMap> generators = new HashMap<>();
45 |
46 | private CodeGenerators() {
47 |
48 | // Load all enabled code types
49 | JSONArray enabled = Config.get(Cfg.App).getJSONArray("types");
50 |
51 | // Loop all enabled types
52 | for (int x = 0; x < enabled.length(); x++) {
53 |
54 | try {
55 |
56 | // Load type from config and setup pooler
57 | String type = enabled.getString(x);
58 | CodeType config = CodeTypes.inst().loadType(type);
59 | generators.put(config, setupBarcodePooler(config));
60 |
61 | } catch (Exception | Error e) {
62 |
63 | // Log initialization failure
64 | LibLog._clog("E0051", e);
65 | }
66 | }
67 | }
68 |
69 | /**
70 | * Creates a new object pool for the given CodeType.
71 | *
72 | * @param codeType type of pooler to create
73 | * @return the created pooler
74 | */
75 | private LibObjectPooler setupBarcodePooler(final CodeType codeType) {
76 |
77 | LibObjectPooler pooler = //
78 | new LibObjectPooler(3, new GeneratorPoolController(codeType));
79 |
80 | pooler.setMaxLockTime(1000);
81 | pooler.setMaxIdleTime(15 * 60 * 1000);
82 | pooler.setMaxPoolSize(codeType.getNumThreads());
83 |
84 | return pooler;
85 | }
86 |
87 | /**
88 | * Returns the generation pool for the given CodeType.
89 | *
90 | * @param type type of pooler to get
91 | * @return the generation pool
92 | */
93 | public LibObjectPooler getGeneratorPool(CodeType type) {
94 | LibMetrics.hitMethodRunCounter();
95 |
96 | return generators.get(type);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/impl/DefaultBarcode4JProvider.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.impl;
2 |
3 | import java.util.HashMap;
4 |
5 | import org.barcodeapi.server.core.CodeType;
6 | import org.barcodeapi.server.core.GenerationException;
7 | import org.barcodeapi.server.gen.BarcodeCanvasProvider;
8 | import org.barcodeapi.server.gen.BarcodeRequest;
9 | import org.barcodeapi.server.gen.CodeGenerator;
10 | import org.json.JSONObject;
11 | import org.krysalis.barcode4j.HumanReadablePlacement;
12 | import org.krysalis.barcode4j.impl.AbstractBarcodeBean;
13 |
14 | public abstract class DefaultBarcode4JProvider extends CodeGenerator {
15 |
16 | private final AbstractBarcodeBean generator;
17 |
18 | public DefaultBarcode4JProvider(CodeType codeType, AbstractBarcodeBean bean) {
19 | super(codeType);
20 |
21 | this.generator = bean;
22 | }
23 |
24 | protected AbstractBarcodeBean getBean() {
25 |
26 | return this.generator;
27 | }
28 |
29 | @Override
30 | public byte[] onRender(BarcodeRequest request) throws Exception {
31 |
32 | JSONObject options = request.getOptions();
33 |
34 | HashMap defaults = //
35 | request.getType().getDefaults();
36 |
37 | int dpi = options.optInt("dpi", //
38 | (Integer) defaults.getOrDefault("dpi", 100));
39 |
40 | int module = options.optInt("module", //
41 | (Integer) defaults.getOrDefault("module", 10));
42 |
43 | int qz = options.optInt("qz", //
44 | (Integer) defaults.getOrDefault("qz", 3));
45 |
46 | int height = options.optInt("height", //
47 | (Integer) defaults.getOrDefault("height", 22));
48 |
49 | int font = options.optInt("font", //
50 | (Integer) defaults.getOrDefault("font", 5));
51 |
52 | String text = options.optString("text", //
53 | (String) defaults.getOrDefault("text", "bottom"));
54 |
55 | String pattern = options.optString("pattern", //
56 | (String) defaults.getOrDefault("pattern", "_"));
57 |
58 | String colorFG = options.optString("fg", //
59 | (String) defaults.getOrDefault("fg", "000000"));
60 |
61 | String colorBG = options.optString("bg", //
62 | (String) defaults.getOrDefault("bg", "ffffff"));
63 |
64 | byte[] bytes;
65 | synchronized (generator) {
66 |
67 | // Set render options
68 | generator.doQuietZone(true);
69 | generator.setQuietZone(qz);
70 | generator.setHeight(height);
71 | generator.setModuleWidth((module / 10d));
72 | generator.setPattern(pattern);
73 | generator.setFontSize(font);
74 | generator.setMsgPosition(HumanReadablePlacement.byName(text));
75 |
76 | // Create the canvas object
77 | BarcodeCanvasProvider canvasProvider = //
78 | new BarcodeCanvasProvider(dpi, colorBG, colorFG);
79 |
80 | // Render the barcode, get as bytes
81 | generator.generateBarcode(canvasProvider, request.getData());
82 | bytes = canvasProvider.finish();
83 | }
84 |
85 | return bytes;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/resources/admin.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | BarcodeAPI.org - Admin Tools
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
84 |
85 |
86 | Hostname
87 |
88 | _unknown_
89 |
90 |
91 | Uptime
92 |
93 | _unknown_
94 |
95 |
96 | Version
97 |
98 | _unknown_
99 |
100 |
101 | Dist
102 |
103 | _unknown_
104 |
105 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/resources/ext/js/type.js:
--------------------------------------------------------------------------------
1 | //
2 | // BarcodeAPI.org, 2017-2025
3 | // type.js // type.html
4 | //
5 |
6 | window.onhashchange = function() {
7 | location.reload();
8 | }
9 |
10 | function init() {
11 |
12 | // Load the requested type target
13 | var target = window.location.hash.substring(1);
14 |
15 | // Redirect if not set
16 | if (!target) {
17 | window.location = "/types.html";
18 | return;
19 | }
20 |
21 | // Load the type details
22 | fetch(("/type/?type=" + target))
23 | .then(response => {
24 | return (response.status == 200) ? response.json() : false;
25 | }).then(loadType);
26 | }
27 |
28 | function loadType(type) {
29 |
30 | // Redirect if not loaded
31 | if (!type) {
32 | window.location = "/types.html";
33 | return;
34 | }
35 |
36 | // Set the page title
37 | document.title = //
38 | document.title.replace("$TYPE$", type.display);
39 |
40 | // Determine default target
41 | var target = type.targets[0];
42 |
43 | // Determine language
44 | var language = appConfig.userLanguage;
45 |
46 | // Get the DOM template object
47 | var info = document.getElementById("barcode-template");
48 | info.setAttribute("id", "barcode-type-" + target);
49 |
50 | // Update example link
51 | var link = ("/api/" + target + "/" + type.example[0]);
52 | info.querySelector(".type-example").src = link;
53 |
54 | // Update type basic details
55 | info.querySelector(".type-name").innerHTML = type.display;
56 | info.querySelector(".type-target").innerHTML = ('/' + target + '/');
57 | info.querySelector(".type-cost-basic").innerHTML = type.costBasic;
58 | info.querySelector(".type-cost-custom").innerHTML = type.costCustom;
59 |
60 | // Update type extended details
61 | info.querySelector(".type-example-link").href = "index.html#" + target;
62 | info.querySelector(".type-format").innerHTML = type.pattern;
63 | info.querySelector(".type-description").innerHTML = type.description[language];
64 | info.querySelector(".type-wiki").href = type.wiki[language];
65 | info.querySelector(".type-decode").innerHTML = (type.decode ? "Yes" : "No");
66 | info.querySelector(".type-checksum").innerHTML = (type.checksum ? "Yes" : "No");
67 | info.querySelector(".type-nonprinting").innerHTML = (type.nonprinting ? "Yes" : "No");
68 | info.querySelector(".type-options").innerHTML = buildOptions(type.options);
69 | info.querySelector(".type-options-string").innerHTML = buildOptionsString(type.options);
70 |
71 | // Log tracking event
72 | var setupMillis = ((new Date()) - timeStart);
73 | trackingEvent("AppEvents", "AppLoad", "Type", setupMillis);
74 | }
75 |
76 | function buildOptions(options) {
77 |
78 | var response = "";
79 | for (var x in options) {
80 | response += (x + "=" + options[x].default + " ");
81 | }
82 | return response;
83 | }
84 |
85 | function buildOptionsString(options) {
86 |
87 | var response = "?";
88 | for (var x in options) {
89 | response += (x + "=" + options[x].default + "&");
90 | }
91 | return response;
92 | }
93 |
--------------------------------------------------------------------------------
/resources/ext/js/session.js:
--------------------------------------------------------------------------------
1 | //
2 | // BarcodeAPI.org, 2017-2025
3 | // session.js // session.html
4 | //
5 |
6 | function init() {
7 |
8 | // Load the type details
9 | fetch("/session/")
10 | .then(response => {
11 | return (response.status == 200) ? response.json() : false;
12 | }).then(onLoadSession);
13 | fetch("/limiter/")
14 | .then(response => {
15 | return (response.status == 200) ? response.json() : false;
16 | }).then(onLoadLimiter);
17 |
18 | // Log tracking event
19 | var setupMillis = ((new Date()) - timeStart);
20 | trackingEvent("AppEvents", "AppLoad", "Session", setupMillis);
21 | }
22 |
23 | function onLoadSession(data) {
24 |
25 | document.getElementById("session-key").innerHTML = data.key;
26 | document.getElementById("session-created").innerHTML = (new Date(data.created)).toJSON();
27 | document.getElementById("session-expires").innerHTML = (new Date(data.expires)).toJSON();
28 | document.getElementById("session-count").innerHTML = data.count;
29 |
30 | var addresses = "";
31 | for (var a in data.addresses) {
32 | var d = data.addresses[a];
33 |
34 | addresses += //
35 | "" + d.ip + " " + d.hits + " ";
36 | }
37 | document.getElementById("session-addresses").innerHTML = addresses;
38 |
39 | for (var r in data.requests) {
40 | var d = data.requests[r];
41 |
42 | if (d.text.match(/^\/api\/.*/)) {
43 | addEntryAPI(d.text.substr(4), d.hits);
44 | continue;
45 | }
46 |
47 | addEntryOther(d.text, d.hits);
48 | continue;
49 | }
50 | }
51 |
52 | function onLoadLimiter(data) {
53 | document.getElementById("limiter-caller").innerHTML = data.caller;
54 | document.getElementById("limiter-created").innerHTML = (new Date(data.created)).toJSON();
55 | document.getElementById("limiter-expires").innerHTML = (new Date(data.expires)).toJSON();
56 | document.getElementById("limiter-enforce").innerHTML = (data.enforce ? "Yes" : "No");
57 | document.getElementById("limiter-tokenSpend").innerHTML = data.tokenSpend;
58 | document.getElementById("limiter-tokenLimit").innerHTML = data.tokenLimit;
59 | document.getElementById("limiter-tokenCount").innerHTML = Number(data.tokenCount).toFixed(2);
60 | }
61 |
62 | function makeEntryRow(text, hits) {
63 |
64 | return ("" + text + " " + hits + " ");
65 | }
66 |
67 | function addEntryAPI(text, hits) {
68 |
69 | document.getElementById("session-requests-api").innerHTML += makeEntryRow(text, hits);
70 | }
71 |
72 | function addEntryOther(text, hits) {
73 |
74 | document.getElementById("session-requests-other").innerHTML += makeEntryRow(text, hits);
75 | }
76 |
77 | function sessionDelete() {
78 | if (!confirm("Forget this session?")) {
79 | return;
80 | }
81 |
82 | console.log("Requesting session to be deleted.");
83 | fetch('/session/', {
84 | method: 'DELETE'
85 | }).then(response => {
86 |
87 | return response.ok;
88 | }).then(okay => {
89 | if (!okay) {
90 | alert("Failed deleting session!");
91 | return;
92 | }
93 |
94 | alert("Session deleted.");
95 | window.location.reload();
96 | });
97 | }
98 |
--------------------------------------------------------------------------------
/resources/support.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | BarcodeAPI.org - Support
10 |
11 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
39 |
40 |
41 |
42 |
Feedback & Support
43 |
44 | Priority support is available for our paying customers.
45 |
46 |
47 | If you are a free user wishing to increase your rate limit, have questions about using
48 | the service, or just have a suggestion for us, please contact us at the email address
49 | below.
50 |
51 |
52 |
53 |
54 |
55 |
56 |
API Docs
57 |
58 | Trying to use the API?
59 |
60 |
61 | View our docs for usage info.
62 |
63 |
64 |
Learn More
65 |
66 |
67 |
68 |
Rate Limits
69 |
70 | Do your barcodes read
RATE LIMIT ?
71 |
72 |
73 | Be sure to check your
rate limits .
74 |
75 |
76 |
My Account
77 |
78 |
79 |
80 |
81 |
82 |
83 |
Contact Us
84 |
85 | Need to reach us?
86 |
87 | Please send us an email.
88 |
89 | We will respond as soon as possible.
90 |
91 |
92 |
support@barcodeapi.org
93 |
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/core/ServerRuntime.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.core;
2 |
3 | import java.net.InetAddress;
4 | import java.net.UnknownHostException;
5 | import java.util.Timer;
6 | import java.util.UUID;
7 |
8 | import com.mclarkdev.tools.libextras.LibExtrasStreams;
9 | import com.mclarkdev.tools.libmetrics.LibMetrics;
10 |
11 | /**
12 | * ServerRuntime.java
13 | *
14 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
15 | */
16 | public class ServerRuntime {
17 |
18 | // System runtime information
19 | private static final String _RUNTIME_ID;
20 | private static final long _RUNTIME_TIMESTART;
21 | private static final int _RUNTIME_VERSION;
22 | private static final String _RUNTIME_HOST;
23 |
24 | // Background task timer
25 | private static final Timer _SYS_TIMER;
26 |
27 | static {
28 |
29 | _RUNTIME_ID = UUID.randomUUID().toString();
30 | LibMetrics.instance().setValue(_RUNTIME_ID, "system", "runtimeId");
31 |
32 | _RUNTIME_TIMESTART = System.currentTimeMillis();
33 | LibMetrics.instance().setValue(_RUNTIME_TIMESTART, "system", "time", "start");
34 |
35 | try {
36 | _RUNTIME_VERSION = Integer.parseInt(LibExtrasStreams.readStream(//
37 | ServerRuntime.class.getResourceAsStream("/app.version")));
38 | LibMetrics.instance().setValue(_RUNTIME_VERSION, "system", "version");
39 | } catch (Exception e) {
40 | throw new RuntimeException(e);
41 | }
42 |
43 | try {
44 | _RUNTIME_HOST = InetAddress.getLocalHost().getCanonicalHostName();
45 | LibMetrics.instance().setValue(_RUNTIME_HOST, "system", "host");
46 | } catch (UnknownHostException e) {
47 | throw new RuntimeException(e);
48 | }
49 |
50 | _SYS_TIMER = new Timer();
51 | }
52 |
53 | /**
54 | * Returns the current runtime ID of the server.
55 | *
56 | * @return the current runtime ID of the server
57 | */
58 | public static final String getRuntimeID() {
59 | return _RUNTIME_ID;
60 | }
61 |
62 | /**
63 | * Returns the time, in milliseconds, the server was started.
64 | *
65 | * @return the time the server was started
66 | */
67 | public static final long getTimeStart() {
68 | return _RUNTIME_TIMESTART;
69 | }
70 |
71 | /**
72 | * Returns the amount of time, in milliseconds, the server has been running.
73 | *
74 | * @return the amount of time the server has been running
75 | */
76 | public static final long getTimeRunning() {
77 | return System.currentTimeMillis() - getTimeStart();
78 | }
79 |
80 | /**
81 | * Returns the build version of the server.
82 | *
83 | * @return the build version of the server
84 | */
85 | public static final int getVersion() {
86 | return _RUNTIME_VERSION;
87 | }
88 |
89 | /**
90 | * Returns the system host name.
91 | *
92 | * @return the system host name
93 | */
94 | public static final String getHostname() {
95 | return _RUNTIME_HOST;
96 | }
97 |
98 | /**
99 | * Returns an instance of the system timer.
100 | *
101 | * @return an instance of the system timer
102 | */
103 | public static final Timer getSystemTimer() {
104 | return _SYS_TIMER;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/resources/ext/js/limits.js:
--------------------------------------------------------------------------------
1 | //
2 | // BarcodeAPI.org, 2017-2025
3 | // limits.js // limits.html
4 | //
5 |
6 | window.addEventListener("load", init);
7 |
8 | function init() {
9 |
10 | // Load limiter info
11 | fetch('/limiter/')
12 | .then(response => {
13 | return response.json();
14 | })
15 | .then(data => {
16 | if (!data.enforce) {
17 | document.getElementsByClassName("notice-enforced")[0].style.display = 'block';
18 | document.getElementsByClassName("notice-abusers")[0].style.display = 'none';
19 | }
20 |
21 | document.getElementById("token_count").innerHTML = //
22 | ((data.tokenCount == -1) ? "Unlimited" : data.tokenCount.toFixed(2));
23 |
24 | document.getElementById("token_limit").innerHTML = //
25 | ((data.tokenLimit == -1) ? "Unlimited" : data.tokenLimit);
26 |
27 | // Log tracking event
28 | var setupMillis = ((new Date()) - timeStart);
29 | trackingEvent("AppEvents", "AppLoad", "Limits", setupMillis);
30 | });
31 |
32 | // Load plans info
33 | fetch('/plans/')
34 | .then(response => {
35 | return response.json();
36 | })
37 | .then(data => {
38 |
39 | // Add free plan details
40 | document.querySelector(".free-details").innerHTML = data.free.description;
41 | document.querySelector(".free-support").innerHTML = data.free.support;
42 |
43 | // Check if has paid plans
44 | if (data.paid.length > 0) {
45 |
46 | // Show free lmit in info message
47 | document.querySelector(".free-limit").innerHTML = data.free.limit.toLocaleString();
48 |
49 | // Show information about subscriber plans
50 | document.querySelector(".plan-overview").style.display = "block";
51 |
52 | // Loop and add each paid plan
53 | for (var x in data.paid) {
54 | addPlan(data.paid[x]);
55 | }
56 | }
57 |
58 | // Delete the plan template
59 | delTemplate();
60 | });
61 | }
62 |
63 | function addPlan(plan) {
64 |
65 | var info = document.getElementById("plan-template").cloneNode(true);
66 |
67 | info.setAttribute("id", "plan-X");
68 |
69 | info.querySelector(".plan-name").innerHTML = plan.name;
70 |
71 | var planPrices = document.createElement("span");
72 |
73 | for (option in plan.price) {
74 |
75 | if (option > 0) {
76 | var spacer = document.createElement("span");
77 | spacer.innerHTML = " || ";
78 | planPrices.appendChild(spacer);
79 | }
80 |
81 | var price = plan.price[option];
82 | var priceInfo = document.createElement("a");
83 | priceInfo.innerHTML = price.name;
84 |
85 | if (price.link) {
86 | priceInfo.href = price.link;
87 | }
88 |
89 | planPrices.appendChild(priceInfo);
90 | }
91 |
92 | info.querySelector(".plan-prices").appendChild(planPrices);
93 |
94 | info.querySelector(".plan-details").innerHTML = plan.description;
95 | info.querySelector(".plan-support").innerHTML = plan.support;
96 |
97 | document.getElementById("user-plans").append(info);
98 | }
99 |
100 | function delTemplate() {
101 | document.getElementById("user-plans")//
102 | .removeChild(document.getElementById("plan-template"));
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/BarcodeCanvasProvider.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen;
2 |
3 | import java.awt.Color;
4 | import java.awt.Graphics2D;
5 | import java.awt.geom.Rectangle2D;
6 | import java.awt.image.BufferedImage;
7 | import java.io.ByteArrayOutputStream;
8 | import java.io.IOException;
9 |
10 | import org.krysalis.barcode4j.BarcodeDimension;
11 | import org.krysalis.barcode4j.TextAlignment;
12 | import org.krysalis.barcode4j.output.AbstractCanvasProvider;
13 | import org.krysalis.barcode4j.output.bitmap.BitmapBuilder;
14 | import org.krysalis.barcode4j.output.bitmap.BitmapEncoder;
15 | import org.krysalis.barcode4j.output.bitmap.BitmapEncoderRegistry;
16 | import org.krysalis.barcode4j.output.java2d.Java2DCanvasProvider;
17 |
18 | /**
19 | * BarcodeCanvasProvider.java
20 | *
21 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
22 | */
23 | public class BarcodeCanvasProvider extends AbstractCanvasProvider {
24 |
25 | private static final String _MIME = "image/x-png";
26 |
27 | private static final BitmapEncoder _ENCODER = //
28 | BitmapEncoderRegistry.getInstance(_MIME);
29 |
30 | private int dpi;
31 | private BufferedImage image;
32 | private Graphics2D g2d;
33 | private Java2DCanvasProvider delegate;
34 |
35 | private final Color colorBG;
36 | private final Color colorFG;
37 |
38 | public BarcodeCanvasProvider(int dpi, String bg, String fg) {
39 | super(0);
40 |
41 | this.dpi = dpi;
42 | this.colorBG = Color.decode("0x" + bg);
43 | this.colorFG = Color.decode("0x" + fg);
44 | }
45 |
46 | /**
47 | * Encode and render the image.
48 | *
49 | * @return the image bytes
50 | * @throws IOException generation failure
51 | */
52 | public byte[] finish() throws IOException {
53 |
54 | ByteArrayOutputStream out = //
55 | new ByteArrayOutputStream();
56 |
57 | synchronized (image) {
58 |
59 | image.flush();
60 | _ENCODER.encode(image, out, _MIME, dpi);
61 | }
62 |
63 | return out.toByteArray();
64 | }
65 |
66 | /** {@inheritDoc} */
67 | public void establishDimensions(BarcodeDimension dim) {
68 | super.establishDimensions(dim);
69 | boolean twoTone = ((colorBG.equals(Color.white)) && (colorFG.equals(Color.black)));
70 | int format = ((twoTone) ? BufferedImage.TYPE_BYTE_BINARY : BufferedImage.TYPE_INT_RGB);
71 | this.image = BitmapBuilder.prepareImage(dim, getOrientation(), dpi, format);
72 | this.g2d = BitmapBuilder.prepareGraphics2D(this.image, dim, 0, false);
73 | this.delegate = new Java2DCanvasProvider(g2d, 0);
74 | this.delegate.establishDimensions(dim);
75 | this.g2d.setColor(colorBG);
76 | this.g2d.fill(new Rectangle2D.Double(0, 0, image.getWidth(), image.getHeight()));
77 | }
78 |
79 | /** {@inheritDoc} */
80 | public void deviceFillRect(double x, double y, double w, double h) {
81 | this.g2d.setColor(colorFG);
82 | this.g2d.fill(new Rectangle2D.Double(x, y, w, h));
83 | }
84 |
85 | /** {@inheritDoc} */
86 | public void deviceText(String text, double x1, double x2, double y1, String fontName, double fontSize,
87 | TextAlignment textAlign) {
88 | this.g2d.setColor(colorFG);
89 | this.g2d.setPaint(colorFG);
90 | this.delegate.deviceText(text, x1, x2, y1, fontName, fontSize, textAlign);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/resources/type.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | BarcodeAPI.org - $TYPE$
10 |
11 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
40 |
41 |
52 |
53 |
54 |
55 |
56 |
-
57 |
58 |
59 | Cost
60 |
61 | Basic:
-
62 |
63 | Custom:
-
64 |
65 |
66 | Target:
-
67 |
68 |
69 | Format:
-
70 |
71 |
72 | Decode Supported:
-
73 |
74 | Checksums Enforced:
-
75 |
76 | Non-Printing Characters:
- ?
78 |
79 |
80 |
-
81 |
82 |
83 | Read more on
Wikipedia .
84 |
85 |
86 | Parameters
87 |
88 |
-
89 |
90 |
-
91 |
92 |
93 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/gen/types/AprilTagGenerator.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.gen.types;
2 |
3 | import java.awt.image.BufferedImage;
4 | import java.io.ByteArrayOutputStream;
5 | import java.io.IOException;
6 | import java.util.HashMap;
7 |
8 | import javax.imageio.ImageIO;
9 |
10 | import org.barcodeapi.server.core.CodeType;
11 | import org.barcodeapi.server.core.GenerationException;
12 | import org.barcodeapi.server.core.GenerationException.ExceptionType;
13 | import org.barcodeapi.server.gen.BarcodeRequest;
14 | import org.barcodeapi.server.gen.CodeGenerator;
15 | import org.json.JSONObject;
16 |
17 | import com.google.zxing.WriterException;
18 | import com.mclarkdev.tools.libapriltag.TagFamily;
19 | import com.mclarkdev.tools.libapriltag.families.Tag16h5;
20 | import com.mclarkdev.tools.libapriltag.families.Tag25h9;
21 | import com.mclarkdev.tools.libapriltag.families.Tag36h10;
22 | import com.mclarkdev.tools.libapriltag.families.Tag36h11;
23 | import com.mclarkdev.tools.libapriltag.families.Tag36h9;
24 | import com.mclarkdev.tools.libapriltag.families.TagCircle21h7;
25 | import com.mclarkdev.tools.libapriltag.families.TagCircle49h12;
26 | import com.mclarkdev.tools.libapriltag.families.TagCustom48h12;
27 | import com.mclarkdev.tools.libapriltag.families.TagStandard41h12;
28 | import com.mclarkdev.tools.libapriltag.families.TagStandard52h13;
29 |
30 | /**
31 | * AztecGenerator.java
32 | *
33 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
34 | */
35 | public class AprilTagGenerator extends CodeGenerator {
36 |
37 | private final HashMap tagFamilies;
38 |
39 | public AprilTagGenerator(CodeType codeType) {
40 | super(codeType);
41 |
42 | tagFamilies = new HashMap<>();
43 |
44 | // Basic tag families
45 | tagFamilies.put("tag16h5", new Tag16h5());
46 | tagFamilies.put("tag25h9", new Tag25h9());
47 | tagFamilies.put("tag36h9", new Tag36h9());
48 | tagFamilies.put("tag36h10", new Tag36h10());
49 | tagFamilies.put("tag36h11", new Tag36h11());
50 |
51 | // Extended tag families
52 | tagFamilies.put("tagCircle21h7", new TagCircle21h7());
53 | tagFamilies.put("tagCircle49h12", new TagCircle49h12());
54 | tagFamilies.put("tagCustom48h12", new TagCustom48h12());
55 | tagFamilies.put("tagStandard41h12", new TagStandard41h12());
56 | tagFamilies.put("tagStandard52h13", new TagStandard52h13());
57 | }
58 |
59 | @Override
60 | public byte[] onRender(BarcodeRequest request) throws WriterException, IOException, GenerationException {
61 |
62 | String[] parts = request.getData().split(":");
63 |
64 | String family = parts[0];
65 | int id = Integer.parseInt(parts[1]);
66 |
67 | JSONObject options = request.getOptions();
68 |
69 | HashMap defaults = //
70 | request.getType().getDefaults();
71 |
72 | TagFamily tagFamily = tagFamilies.get(family);
73 |
74 | if (tagFamily == null) {
75 | throw new GenerationException(ExceptionType.INVALID, //
76 | new Throwable("Unsupported tag type."));
77 | }
78 |
79 | int scale = options.optInt("scale", //
80 | (Integer) defaults.getOrDefault("scale", 8));
81 |
82 | BufferedImage img = tagFamily.getLayout().renderToImage(tagFamily.getCodes()[id], scale);
83 |
84 | try {
85 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
86 | ImageIO.write(img, "png", baos);
87 | baos.close();
88 | return baos.toByteArray();
89 | } catch (Exception e) {
90 | return null;
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/gen/types/TestEan8.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.gen.types;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.barcodeapi.server.core.GenerationException.ExceptionType;
5 | import org.eclipse.jetty.http.HttpStatus;
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | public class TestEan8 extends ServerTestBase {
10 |
11 | @Test
12 | public void testEan8_8Nums() {
13 |
14 | apiGet("8/12345670");
15 |
16 | Assert.assertEquals("Response Code", //
17 | HttpStatus.OK_200, getResponseCode());
18 |
19 | Assert.assertEquals("Code Type", //
20 | "EAN8", getHeader("X-Barcode-Type"));
21 |
22 | Assert.assertEquals("Code Data", //
23 | "12345670", getHeader("X-Barcode-Content"));
24 | }
25 |
26 | @Test
27 | public void testEan8_7Nums() {
28 |
29 | apiGet("8/1234567");
30 |
31 | Assert.assertEquals("Response Code", //
32 | HttpStatus.OK_200, getResponseCode());
33 |
34 | Assert.assertEquals("Code Type", //
35 | "EAN8", getHeader("X-Barcode-Type"));
36 |
37 | // Checksum is filled in
38 | Assert.assertEquals("Code Data", //
39 | "12345670", getHeader("X-Barcode-Content"));
40 | }
41 |
42 | @Test
43 | public void testEan8_8NumsInvalidChecksum() throws Exception {
44 |
45 | apiGet("8/12345678");
46 |
47 | Assert.assertEquals("Response Code", //
48 | ExceptionType.CHECKSUM.getStatusCode(), getResponseCode());
49 |
50 | Assert.assertEquals("Error Message", //
51 | "Invalid checksum: expected 0", getHeader("X-Error-Message"));
52 | }
53 |
54 | @Test
55 | public void testEan8_TooShort() throws Exception {
56 |
57 | apiGet("8/123456");
58 |
59 | Assert.assertEquals("Response Code", //
60 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
61 |
62 | Assert.assertEquals("Response Message", //
63 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
64 | }
65 |
66 | @Test
67 | public void testEan8_TooLong() throws Exception {
68 |
69 | apiGet("8/123456789");
70 |
71 | Assert.assertEquals("Response Code", //
72 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
73 |
74 | Assert.assertEquals("Response Message", //
75 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
76 | }
77 |
78 | @Test
79 | public void testEan8_WithLetters() throws Exception {
80 |
81 | apiGet("8/ABCDEFGH");
82 |
83 | Assert.assertEquals("Response Code", //
84 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
85 |
86 | Assert.assertEquals("Response Message", //
87 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
88 | }
89 |
90 | @Test
91 | public void testEan8_WithSymbols() throws Exception {
92 |
93 | apiGet("8/!@");
94 |
95 | Assert.assertEquals("Response Code", //
96 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
97 |
98 | Assert.assertEquals("Response Message", //
99 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
100 | }
101 |
102 | @Test
103 | public void testEan8_WithUnicode() throws Exception {
104 |
105 | apiGet("8/Ω");
106 |
107 | Assert.assertEquals("Response Code", //
108 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
109 |
110 | Assert.assertEquals("Response Message", //
111 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/gen/types/TestUPCE.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.gen.types;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.barcodeapi.server.core.GenerationException.ExceptionType;
5 | import org.eclipse.jetty.http.HttpStatus;
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | public class TestUPCE extends ServerTestBase {
10 |
11 | @Test
12 | public void testUPCE_8Nums() {
13 |
14 | apiGet("e/01234565");
15 |
16 | Assert.assertEquals("Response Code", //
17 | HttpStatus.OK_200, getResponseCode());
18 |
19 | Assert.assertEquals("Code Type", //
20 | "UPC_E", getHeader("X-Barcode-Type"));
21 |
22 | Assert.assertEquals("Code Data", //
23 | "01234565", getHeader("X-Barcode-Content"));
24 | }
25 |
26 | @Test
27 | public void testUPCE_7Nums() {
28 |
29 | apiGet("e/0123456");
30 |
31 | Assert.assertEquals("Response Code", //
32 | HttpStatus.OK_200, getResponseCode());
33 |
34 | Assert.assertEquals("Code Type", //
35 | "UPC_E", getHeader("X-Barcode-Type"));
36 |
37 | // Checksum is filled in
38 | Assert.assertEquals("Code Data", //
39 | "01234565", getHeader("X-Barcode-Content"));
40 | }
41 |
42 | @Test
43 | public void testUPCE_8NumsInvalidChecksum() throws Exception {
44 |
45 | apiGet("e/01234567");
46 |
47 | Assert.assertEquals("Response Code", //
48 | ExceptionType.CHECKSUM.getStatusCode(), getResponseCode());
49 |
50 | Assert.assertEquals("Error Message", //
51 | "Invalid checksum: expected 5", getHeader("X-Error-Message"));
52 | }
53 |
54 | @Test
55 | public void testUPCE_TooShort() throws Exception {
56 |
57 | apiGet("e/012345");
58 |
59 | Assert.assertEquals("Response Code", //
60 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
61 |
62 | Assert.assertEquals("Response Message", //
63 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
64 | }
65 |
66 | @Test
67 | public void testUPCE_TooLong() throws Exception {
68 |
69 | apiGet("e/012345678");
70 |
71 | Assert.assertEquals("Response Code", //
72 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
73 |
74 | Assert.assertEquals("Response Message", //
75 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
76 | }
77 |
78 | @Test
79 | public void testUPCE_WithLetters() throws Exception {
80 |
81 | apiGet("e/ABCDEFGH");
82 |
83 | Assert.assertEquals("Response Code", //
84 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
85 |
86 | Assert.assertEquals("Response Message", //
87 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
88 | }
89 |
90 | @Test
91 | public void testUPCE_WithSymbols() throws Exception {
92 |
93 | apiGet("e/!@");
94 |
95 | Assert.assertEquals("Response Code", //
96 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
97 |
98 | Assert.assertEquals("Response Message", //
99 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
100 | }
101 |
102 | @Test
103 | public void testUPCE_WithUnicode() throws Exception {
104 |
105 | apiGet("e/Ω");
106 |
107 | Assert.assertEquals("Response Code", //
108 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
109 |
110 | Assert.assertEquals("Response Message", //
111 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/gen/types/TestUPCA.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.gen.types;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.barcodeapi.server.core.GenerationException.ExceptionType;
5 | import org.eclipse.jetty.http.HttpStatus;
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | public class TestUPCA extends ServerTestBase {
10 |
11 | @Test
12 | public void testUPCA_12Nums() {
13 |
14 | apiGet("a/123456789012");
15 |
16 | Assert.assertEquals("Response Code", //
17 | HttpStatus.OK_200, getResponseCode());
18 |
19 | Assert.assertEquals("Code Type", //
20 | "UPC_A", getHeader("X-Barcode-Type"));
21 |
22 | Assert.assertEquals("Code Data", //
23 | "123456789012", getHeader("X-Barcode-Content"));
24 | }
25 |
26 | @Test
27 | public void testUPCA_11Nums() {
28 |
29 | apiGet("a/12345678901");
30 |
31 | Assert.assertEquals("Response Code", //
32 | HttpStatus.OK_200, getResponseCode());
33 |
34 | Assert.assertEquals("Code Type", //
35 | "UPC_A", getHeader("X-Barcode-Type"));
36 |
37 | // Checksum is filled in
38 | Assert.assertEquals("Code Data", //
39 | "123456789012", getHeader("X-Barcode-Content"));
40 | }
41 |
42 | @Test
43 | public void testUPCA_8NumsInvalidChecksum() throws Exception {
44 |
45 | apiGet("a/123456789013");
46 |
47 | Assert.assertEquals("Response Code", //
48 | ExceptionType.CHECKSUM.getStatusCode(), getResponseCode());
49 |
50 | Assert.assertEquals("Error Message", //
51 | "Invalid checksum: expected 2", getHeader("X-Error-Message"));
52 | }
53 |
54 | @Test
55 | public void testUPCA_TooShort() throws Exception {
56 |
57 | apiGet("a/1234567890");
58 |
59 | Assert.assertEquals("Response Code", //
60 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
61 |
62 | Assert.assertEquals("Response Message", //
63 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
64 | }
65 |
66 | @Test
67 | public void testUPCA_TooLong() throws Exception {
68 |
69 | apiGet("a/1234567890123");
70 |
71 | Assert.assertEquals("Response Code", //
72 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
73 |
74 | Assert.assertEquals("Response Message", //
75 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
76 | }
77 |
78 | @Test
79 | public void testUPCA_WithLetters() throws Exception {
80 |
81 | apiGet("a/ABCDEFGH");
82 |
83 | Assert.assertEquals("Response Code", //
84 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
85 |
86 | Assert.assertEquals("Response Message", //
87 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
88 | }
89 |
90 | @Test
91 | public void testUPCA_WithSymbols() throws Exception {
92 |
93 | apiGet("a/!@");
94 |
95 | Assert.assertEquals("Response Code", //
96 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
97 |
98 | Assert.assertEquals("Response Message", //
99 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
100 | }
101 |
102 | @Test
103 | public void testUPCA_WithUnicode() throws Exception {
104 |
105 | apiGet("a/Ω");
106 |
107 | Assert.assertEquals("Response Code", //
108 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
109 |
110 | Assert.assertEquals("Response Message", //
111 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/test/java/org/barcodeapi/test/gen/types/TestEan13.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.test.gen.types;
2 |
3 | import org.barcodeapi.server.ServerTestBase;
4 | import org.barcodeapi.server.core.GenerationException.ExceptionType;
5 | import org.eclipse.jetty.http.HttpStatus;
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | public class TestEan13 extends ServerTestBase {
10 |
11 | @Test
12 | public void testEan13_13Nums() {
13 |
14 | apiGet("13/1234567890128");
15 |
16 | Assert.assertEquals("Response Code", //
17 | HttpStatus.OK_200, getResponseCode());
18 |
19 | Assert.assertEquals("Code Type", //
20 | "EAN13", getHeader("X-Barcode-Type"));
21 |
22 | Assert.assertEquals("Code Data", //
23 | "1234567890128", getHeader("X-Barcode-Content"));
24 | }
25 |
26 | @Test
27 | public void testEan13_12Nums() {
28 |
29 | apiGet("13/123456789012");
30 |
31 | Assert.assertEquals("Response Code", //
32 | HttpStatus.OK_200, getResponseCode());
33 |
34 | Assert.assertEquals("Code Type", //
35 | "EAN13", getHeader("X-Barcode-Type"));
36 |
37 | // Checksum is filled in
38 | Assert.assertEquals("Code Data", //
39 | "1234567890128", getHeader("X-Barcode-Content"));
40 | }
41 |
42 | @Test
43 | public void testEan13_13NumsInvalidChecksum() throws Exception {
44 |
45 | apiGet("13/1234567890123");
46 |
47 | Assert.assertEquals("Response Code", //
48 | ExceptionType.CHECKSUM.getStatusCode(), getResponseCode());
49 |
50 | Assert.assertEquals("Error Message", //
51 | "Invalid checksum: expected 8", getHeader("X-Error-Message"));
52 | }
53 |
54 | @Test
55 | public void testEan13_TooShort() throws Exception {
56 |
57 | apiGet("13/12345678901");
58 |
59 | Assert.assertEquals("Response Code", //
60 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
61 |
62 | Assert.assertEquals("Response Message", //
63 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
64 | }
65 |
66 | @Test
67 | public void testEan13_TooLong() throws Exception {
68 |
69 | apiGet("13/12345678901234");
70 |
71 | Assert.assertEquals("Response Code", //
72 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
73 |
74 | Assert.assertEquals("Response Message", //
75 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
76 | }
77 |
78 | @Test
79 | public void testEan13_WithLetters() throws Exception {
80 |
81 | apiGet("13/123456789O123");
82 |
83 | Assert.assertEquals("Response Code", //
84 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
85 |
86 | Assert.assertEquals("Response Message", //
87 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
88 | }
89 |
90 | @Test
91 | public void testEan13_WithSymbols() throws Exception {
92 |
93 | apiGet("13/!@");
94 |
95 | Assert.assertEquals("Response Code", //
96 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
97 |
98 | Assert.assertEquals("Response Message", //
99 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
100 | }
101 |
102 | @Test
103 | public void testEan13_WithUnicode() throws Exception {
104 |
105 | apiGet("13/Ω");
106 |
107 | Assert.assertEquals("Response Code", //
108 | ExceptionType.INVALID.getStatusCode(), getResponseCode());
109 |
110 | Assert.assertEquals("Response Message", //
111 | "Invalid data for selected code type.", getHeader("X-Error-Message"));
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/cache/CachedSession.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.cache;
2 |
3 | import java.util.Map;
4 | import java.util.UUID;
5 | import java.util.concurrent.ConcurrentHashMap;
6 |
7 | import javax.servlet.http.Cookie;
8 |
9 | import org.json.JSONArray;
10 | import org.json.JSONObject;
11 |
12 | /**
13 | * CachedSession.java
14 | *
15 | * A user session object allowing for the detailed tracking of individual user
16 | * usage activities across all handlers.
17 | *
18 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
19 | */
20 | public class CachedSession extends CachedObject {
21 |
22 | private static final long serialVersionUID = 20241222L;
23 |
24 | private final String key;
25 |
26 | private final Cookie cookie;
27 |
28 | private final ConcurrentHashMap sessionIPs;
29 | private final ConcurrentHashMap sessionRequests;
30 |
31 | public CachedSession() {
32 | super("session");
33 |
34 | // Generate new random UUID
35 | this.key = UUID.randomUUID().toString();
36 |
37 | // Create user cookie
38 | this.cookie = new Cookie("session", this.key);
39 | this.cookie.setMaxAge((int) (getStandardTimeout() / 1000));
40 | this.cookie.setPath("/");
41 |
42 | // Memory map for ip and request history
43 | this.sessionIPs = new ConcurrentHashMap();
44 | this.sessionRequests = new ConcurrentHashMap();
45 | }
46 |
47 | /**
48 | * Returns the key for the session.
49 | *
50 | * @return the key for the session
51 | */
52 | public String getKey() {
53 |
54 | return key;
55 | }
56 |
57 | /**
58 | * Returns the browser cookie for the session.
59 | *
60 | * @return the browser cookie for the session
61 | */
62 | public Cookie getCookie() {
63 |
64 | return cookie;
65 | }
66 |
67 | /**
68 | * Called when the user session is loaded when navigating across handlers.
69 | * Tracks a users usage across the site, viewable through the /session/ handler.
70 | *
71 | * @param data
72 | */
73 | public void hit(String ip, String data) {
74 | this.touch();
75 |
76 | // Count for IP
77 | int countIP = sessionIPs.containsKey(ip) ? sessionIPs.get(ip) : 0;
78 | sessionIPs.put(ip, (countIP + 1));
79 |
80 | // Count for request
81 | int countReq = sessionRequests.containsKey(data) ? sessionRequests.get(data) : 0;
82 | sessionRequests.put(data, (countReq + 1));
83 | }
84 |
85 | /**
86 | * Returns the user session as a JSON object.
87 | *
88 | * @return the user session in JSON format
89 | */
90 | public JSONObject asJSON() {
91 |
92 | JSONArray addresses = new JSONArray();
93 | for (Map.Entry entry : sessionIPs.entrySet()) {
94 |
95 | addresses.put(new JSONObject() //
96 | .put("ip", entry.getKey())//
97 | .put("hits", entry.getValue()));
98 | }
99 |
100 | JSONArray requests = new JSONArray();
101 | for (Map.Entry entry : sessionRequests.entrySet()) {
102 |
103 | requests.put(new JSONObject()//
104 | .put("text", entry.getKey())//
105 | .put("hits", entry.getValue()));
106 | }
107 |
108 | return (new JSONObject()//
109 | .put("key", getKey())//
110 | .put("created", getTimeCreated())//
111 | .put("expires", getTimeExpires())//
112 | .put("last", getTimeLastTouched())//
113 | .put("count", getAccessCount())//
114 | .put("addresses", addresses)//
115 | .put("requests", requests));
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/main/java/org/barcodeapi/server/cache/CachedBarcode.java:
--------------------------------------------------------------------------------
1 | package org.barcodeapi.server.cache;
2 |
3 | import java.util.Base64;
4 |
5 | import org.barcodeapi.core.utils.CodeUtils;
6 | import org.barcodeapi.server.core.CodeType;
7 | import org.json.JSONObject;
8 |
9 | /**
10 | * CachedBarcode.java
11 | *
12 | * @author Matthew R. Clark (BarcodeAPI.org, 2017-2024)
13 | */
14 | public class CachedBarcode extends CachedObject {
15 |
16 | private static final long serialVersionUID = 20241222L;
17 |
18 | private final CodeType type;
19 | private final byte[] data;
20 |
21 | private final String strRaw;
22 | private final String strNice;
23 | private final String strEncoded;
24 |
25 | public CachedBarcode(CodeType type, String raw, byte[] data) {
26 | super("barcode");
27 |
28 | // Fail if null data
29 | if (type == null || data == null) {
30 | throw new IllegalArgumentException();
31 | }
32 |
33 | this.type = type;
34 | this.data = data;
35 |
36 | this.strRaw = raw;
37 | this.strNice = CodeUtils.stripIllegal(raw);
38 | this.strEncoded = CodeUtils.encodeURL(raw);
39 | }
40 |
41 | /**
42 | * Returns the name of the barcode type.
43 | *
44 | * @return the name of the barcode type
45 | */
46 | public CodeType getBarcodeType() {
47 |
48 | return type;
49 | }
50 |
51 | /**
52 | * Returns the barcode image data.
53 | *
54 | * @return the barcode image data
55 | */
56 | public byte[] getBarcodeData() {
57 |
58 | this.touch();
59 | return data;
60 | }
61 |
62 | /**
63 | * Returns the raw data encoded in the barcode.
64 | *
65 | * @return the raw barcode text string
66 | */
67 | public String getBarcodeStringRaw() {
68 |
69 | return strRaw;
70 | }
71 |
72 | /**
73 | * Returns a _nice_ string representing the data encoded in the barcode.
74 | *
75 | * @return a _nice_ barcode text string
76 | */
77 | public String getBarcodeStringNice() {
78 |
79 | return strNice;
80 | }
81 |
82 | /**
83 | * Returns an encoded string representing the data encoded in the barcode.
84 | *
85 | * @return an encoded barcode text string
86 | */
87 | public String getBarcodeStringEncoded() {
88 |
89 | return strEncoded;
90 | }
91 |
92 | /**
93 | * returns the number of bytes in the data array.
94 | *
95 | * @return number of data bytesS
96 | */
97 | public int getBarcodeDataSize() {
98 |
99 | return data.length;
100 | }
101 |
102 | /**
103 | * Returns the barcode data encoded as Base64.
104 | *
105 | * @return barcode data in Base64 format
106 | */
107 | public String encodeBase64() {
108 |
109 | return Base64.getEncoder()//
110 | .encodeToString(getBarcodeData());
111 | }
112 |
113 | /**
114 | * Returns the barcode data encoded as Base64, wrapped in a JSON object.
115 | *
116 | * @return barcode data in JSON format
117 | */
118 | public String encodeJSON() {
119 |
120 | return (new JSONObject()//
121 | .put("text", getBarcodeStringRaw())//
122 | .put("type", getBarcodeType().getName())//
123 | .put("encoded", getBarcodeStringEncoded())//
124 | .put("base64", encodeBase64())//
125 | ).toString();
126 | }
127 |
128 | /**
129 | * Returns an HTML page with the barcode embedded as Base-64.
130 | *
131 | * @return barcode in an HTML format page
132 | */
133 | public String encodeHTML() {
134 | return String.format(//
135 | " ", encodeBase64());
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/resources/ext/js/decode.js:
--------------------------------------------------------------------------------
1 | //
2 | // BarcodeAPI.org, 2017-2025
3 | // decode.js // decode.html
4 | //
5 |
6 | // 2MB max upload file size
7 | const MAX_SIZE = 2 * 1024 * 1024;
8 |
9 | // Supported file types for decoding
10 | const VALID_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/bmp'];
11 |
12 | function init() {
13 | const fileSelect = document.getElementById("decode-image-select");
14 | fileSelect.addEventListener('change', handleFileSelection);
15 |
16 | const decodeButton = document.getElementById("decode-image-button");
17 | decodeButton.addEventListener('click', submitForDecode);
18 |
19 | const clearButton = document.getElementById("decode-preview-clear");
20 | clearButton.addEventListener("click", clearPreview);
21 |
22 | // Log tracking event
23 | var setupMillis = ((new Date()) - timeStart);
24 | trackingEvent("AppEvents", "AppLoad", "Decode", setupMillis);
25 | }
26 |
27 | function handleFileSelection() {
28 | const file = document.getElementById("decode-image-select").files[0];
29 |
30 | if (!isFileSupported(file)) {
31 | clearPreview(); // Clear the preview if the file is unsupported
32 | return;
33 | }
34 |
35 | previewImage(file);
36 | toggleSubmitButton(!!this.value.length);
37 | }
38 |
39 | function isFileSupported(file) {
40 | if (!file) {
41 | console.warn("No file selected.");
42 | return false;
43 | }
44 |
45 | // Check file type
46 | if (!VALID_IMAGE_TYPES.includes(file.type)) {
47 | console.error("Unsupported file type.");
48 | return false;
49 | }
50 |
51 | // Check file size
52 | if (file.size > MAX_SIZE) {
53 | console.error("Image exceeds maximum size.");
54 | return false;
55 | }
56 |
57 | return true;
58 | }
59 |
60 | function previewImage(file) {
61 | const img = document.getElementById('decode-image-preview');
62 | img.src = ""; // Clear previous preview
63 |
64 | const reader = new FileReader();
65 | reader.onload = (e) => {
66 | img.src = e.target.result;
67 | img.style.display = 'block';
68 | };
69 | reader.readAsDataURL(file);
70 |
71 | // Log tracking event
72 | trackingEvent("Decode", "Preview");
73 | }
74 |
75 | function clearPreview() {
76 | const img = document.getElementById('decode-image-preview');
77 | img.src = "";
78 | img.style.display = 'none';
79 | document.getElementById("decode-image-select").value = "";
80 | toggleSubmitButton(false);
81 | }
82 |
83 | function toggleSubmitButton(enabled) {
84 | const submitButton = document.getElementById("decode-image-button");
85 | submitButton.disabled = !enabled;
86 | }
87 |
88 | function submitForDecode(e) {
89 | console.log("Submit for decoding:", e);
90 | const fileInput = document.getElementById("decode-image-select");
91 | const file = fileInput.files[0];
92 |
93 | if (!file) {
94 | console.error("No file selected for decoding.");
95 | return;
96 | }
97 |
98 | const formData = new FormData();
99 | formData.append('image', file);
100 |
101 | fetch('/decode/', {
102 | method: 'POST',
103 | body: formData,
104 | }).then(response => {
105 |
106 | if (!response.ok) {
107 | throw new Error(`Error: ${response.statusText}`);
108 | }
109 | return response.json();
110 | }).then(data => {
111 |
112 | console.log("Decoded data:", data);
113 | }).catch(error => {
114 |
115 | console.error("Error during file upload:", error);
116 | });
117 |
118 | // Log tracking event
119 | trackingEvent("Decode", "Upload");
120 | }
121 |
--------------------------------------------------------------------------------
/resources/limits.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 | BarcodeAPI.org - Rate Limits
10 |
11 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
43 |
Rate Limits
44 |
45 |
Token Limit :
/day 
❔
47 |
48 |
Tokens Remaining :
49 |
50 |
51 | Note : Rate limits are not currently enforced.
52 |
53 |
54 |
55 |
56 |
57 |
58 | Unlock the full potential of BarcodeAPI.org with our API pricing plans!
59 |
60 |
61 | Our token per day
62 | FREE plan should be sufficient for most users.
63 |
64 |
65 |
66 |
67 |
68 |
Free Tier
69 |
70 | ALWAYS FREE
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | For more information about tokens, check out the
API Docs
94 |
95 |
96 | Additional user session details can be found at
Session Info
98 |
99 |
100 |
101 |
Note : Abusers of the API will be IP banned. (
More )
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------