├── core
├── src
│ ├── main
│ │ └── java
│ │ │ ├── lombok.config
│ │ │ └── org
│ │ │ ├── piwik
│ │ │ └── java
│ │ │ │ └── tracking
│ │ │ │ ├── package-info.java
│ │ │ │ ├── PiwikLocale.java
│ │ │ │ ├── CustomVariable.java
│ │ │ │ ├── PiwikRequest.java
│ │ │ │ ├── EcommerceItem.java
│ │ │ │ ├── PiwikDate.java
│ │ │ │ └── PiwikTracker.java
│ │ │ └── matomo
│ │ │ └── java
│ │ │ └── tracking
│ │ │ ├── SenderProvider.java
│ │ │ ├── SenderFactory.java
│ │ │ ├── servlet
│ │ │ ├── CookieWrapper.java
│ │ │ └── HttpServletRequestWrapper.java
│ │ │ ├── parameters
│ │ │ ├── package-info.java
│ │ │ ├── Hex.java
│ │ │ ├── EcommerceItem.java
│ │ │ ├── EcommerceItems.java
│ │ │ ├── RandomValue.java
│ │ │ ├── UniqueId.java
│ │ │ ├── DeviceResolution.java
│ │ │ ├── CustomVariable.java
│ │ │ ├── AcceptLanguage.java
│ │ │ ├── Country.java
│ │ │ └── VisitorId.java
│ │ │ ├── InvalidUrlException.java
│ │ │ ├── DaemonThreadFactory.java
│ │ │ ├── MatomoException.java
│ │ │ ├── package-info.java
│ │ │ ├── TrustingX509TrustManager.java
│ │ │ ├── TrackingParameter.java
│ │ │ ├── Sender.java
│ │ │ ├── CustomVariable.java
│ │ │ ├── ProxyAuthenticator.java
│ │ │ ├── ActionType.java
│ │ │ ├── EcommerceItem.java
│ │ │ ├── MatomoLocale.java
│ │ │ ├── BulkRequest.java
│ │ │ ├── ServiceLoaderSenderFactory.java
│ │ │ ├── ExecutorServiceCloser.java
│ │ │ ├── AuthToken.java
│ │ │ ├── RequestValidator.java
│ │ │ ├── MatomoRequestBuilder.java
│ │ │ ├── TrackingParameterMethod.java
│ │ │ └── MatomoDate.java
│ └── test
│ │ └── java
│ │ └── org
│ │ ├── matomo
│ │ └── java
│ │ │ └── tracking
│ │ │ ├── TestThrowable.java
│ │ │ ├── TestSenderFactory.java
│ │ │ ├── InvalidUrlExceptionTest.java
│ │ │ ├── parameters
│ │ │ ├── UniqueIdTest.java
│ │ │ ├── EcommerceItemsTest.java
│ │ │ ├── HexTest.java
│ │ │ ├── AcceptLanguageTest.java
│ │ │ ├── DeviceResolutionTest.java
│ │ │ ├── CustomVariableTest.java
│ │ │ ├── CountryTest.java
│ │ │ └── VisitorIdTest.java
│ │ │ ├── MatomoLocaleTest.java
│ │ │ ├── MatomoExceptionTest.java
│ │ │ ├── PiwikLocaleTest.java
│ │ │ ├── DaemonThreadFactoryTest.java
│ │ │ ├── ServiceLoaderSenderFactoryTest.java
│ │ │ ├── TrustingX509TrustManagerTest.java
│ │ │ ├── BulkRequestTest.java
│ │ │ ├── CustomVariableTest.java
│ │ │ ├── PiwikDateTest.java
│ │ │ ├── ExecutorServiceCloserTest.java
│ │ │ ├── ProxyAuthenticatorTest.java
│ │ │ ├── EcommerceItemTest.java
│ │ │ ├── AuthTokenTest.java
│ │ │ ├── TestSender.java
│ │ │ ├── TrackingParameterMethodTest.java
│ │ │ ├── RequestValidatorTest.java
│ │ │ ├── MatomoRequestBuilderTest.java
│ │ │ └── MatomoRequestTest.java
│ │ └── piwik
│ │ └── java
│ │ └── tracking
│ │ ├── CustomVariableTest.java
│ │ ├── EcommerceItemTest.java
│ │ └── PiwikTrackerIT.java
└── pom.xml
├── java11
├── src
│ └── main
│ │ ├── resources
│ │ └── META-INF
│ │ │ └── services
│ │ │ └── org.matomo.java.tracking.SenderProvider
│ │ └── java
│ │ └── org
│ │ └── matomo
│ │ └── java
│ │ └── tracking
│ │ └── Java11SenderProvider.java
└── pom.xml
├── java8
├── src
│ ├── main
│ │ ├── resources
│ │ │ └── META-INF
│ │ │ │ └── services
│ │ │ │ └── org.matomo.java.tracking.SenderProvider
│ │ └── java
│ │ │ └── org
│ │ │ └── matomo
│ │ │ └── java
│ │ │ └── tracking
│ │ │ ├── TrustingHostnameVerifier.java
│ │ │ └── Java8SenderProvider.java
│ └── test
│ │ └── java
│ │ └── org
│ │ └── matomo
│ │ └── java
│ │ └── tracking
│ │ └── TrustingHostnameVerifierTest.java
└── pom.xml
├── test
├── src
│ └── main
│ │ ├── resources
│ │ ├── simplelogger.properties
│ │ └── web
│ │ │ └── track.html
│ │ └── java
│ │ └── org
│ │ └── matomo
│ │ └── java
│ │ └── tracking
│ │ └── test
│ │ ├── SendExample.java
│ │ ├── ServletMatomoRequestExample.java
│ │ ├── BulkExample.java
│ │ ├── MatomoServletTester.java
│ │ ├── ConsumerExample.java
│ │ └── EcommerceExample.java
└── pom.xml
├── spring
├── src
│ ├── main
│ │ ├── resources
│ │ │ └── META-INF
│ │ │ │ └── spring
│ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
│ │ └── java
│ │ │ └── org
│ │ │ └── matomo
│ │ │ └── java
│ │ │ └── tracking
│ │ │ └── spring
│ │ │ ├── package-info.java
│ │ │ ├── TrackerConfigurationBuilderCustomizer.java
│ │ │ ├── StandardTrackerConfigurationBuilderCustomizer.java
│ │ │ ├── MatomoTrackerProperties.java
│ │ │ └── MatomoTrackerAutoConfiguration.java
│ └── test
│ │ └── java
│ │ └── org
│ │ └── matomo
│ │ └── java
│ │ └── tracking
│ │ └── spring
│ │ ├── MatomoTrackerAutoConfigurationIT.java
│ │ └── StandardTrackerConfigurationBuilderCustomizerIT.java
└── pom.xml
├── .gitignore
├── .github
├── dependabot.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── pull_request_template.md
├── workflows
│ ├── gh-pages.yml
│ ├── build.yml
│ ├── codeql-analysis.yml
│ └── release.yml
└── release-drafter.yml
├── servlet-jakarta
├── src
│ ├── test
│ │ └── java
│ │ │ └── org
│ │ │ └── matomo
│ │ │ └── java
│ │ │ └── tracking
│ │ │ ├── TestSenderFactory.java
│ │ │ ├── servlet
│ │ │ └── JakartaHttpServletWrapperTest.java
│ │ │ ├── TestSender.java
│ │ │ └── MatomoTrackerFilterIT.java
│ └── main
│ │ └── java
│ │ └── org
│ │ └── matomo
│ │ └── java
│ │ └── tracking
│ │ └── servlet
│ │ ├── MatomoTrackerFilter.java
│ │ └── JakartaHttpServletWrapper.java
└── pom.xml
├── servlet-javax
├── src
│ ├── test
│ │ └── java
│ │ │ └── org
│ │ │ └── matomo
│ │ │ └── java
│ │ │ └── tracking
│ │ │ ├── TestSenderFactory.java
│ │ │ ├── servlet
│ │ │ └── JavaxHttpServletWrapperTest.java
│ │ │ ├── TestSender.java
│ │ │ └── MatomoTrackerFilterIT.java
│ └── main
│ │ └── java
│ │ └── org
│ │ └── matomo
│ │ └── java
│ │ └── tracking
│ │ └── servlet
│ │ ├── MatomoTrackerFilter.java
│ │ └── JavaxHttpServletWrapper.java
└── pom.xml
├── SECURITY.md
├── docker-compose.yml
├── LICENSE
├── CONTRIBUTING.md
└── CODE_OF_CONDUCT.md
/core/src/main/java/lombok.config:
--------------------------------------------------------------------------------
1 | lombok.addLombokGeneratedAnnotation = true
2 |
--------------------------------------------------------------------------------
/java11/src/main/resources/META-INF/services/org.matomo.java.tracking.SenderProvider:
--------------------------------------------------------------------------------
1 | org.matomo.java.tracking.Java11SenderProvider
--------------------------------------------------------------------------------
/java8/src/main/resources/META-INF/services/org.matomo.java.tracking.SenderProvider:
--------------------------------------------------------------------------------
1 | org.matomo.java.tracking.Java8SenderProvider
--------------------------------------------------------------------------------
/test/src/main/resources/simplelogger.properties:
--------------------------------------------------------------------------------
1 | org.slf4j.simpleLogger.defaultLogLevel=debug
2 | org.slf4j.simpleLogger.log.org.eclipse.jetty=info
--------------------------------------------------------------------------------
/spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
--------------------------------------------------------------------------------
1 | org.matomo.java.tracking.spring.MatomoTrackerAutoConfiguration
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *\.DS_Store
3 | *#*#
4 | *.swp
5 | info
6 | **/target/
7 | hs_err_pid*
8 | *.iml
9 | .idea
10 | .classpath
11 | .project
12 | test/logs/*
13 | !test/logs/.gitkeep
--------------------------------------------------------------------------------
/core/src/main/java/org/piwik/java/tracking/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Piwik Java Tracking API. Renamed to {@link org.matomo.java.tracking} in 3.0.0.
3 | */
4 |
5 | package org.piwik.java.tracking;
6 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/SenderProvider.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | interface SenderProvider {
4 |
5 | Sender provideSender(TrackerConfiguration trackerConfiguration, QueryCreator queryCreator);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/TestThrowable.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | class TestThrowable extends Throwable {
4 |
5 | TestThrowable() {
6 | super("message", null, false, false);
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/SenderFactory.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | /**
4 | * A factory for {@link Sender} instances.
5 | */
6 | public interface SenderFactory {
7 |
8 | Sender createSender(TrackerConfiguration trackerConfiguration, QueryCreator queryCreator);
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/servlet/CookieWrapper.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.servlet;
2 |
3 | import lombok.Value;
4 |
5 | /**
6 | * Wrapper for the cookie name and value.
7 | */
8 | @Value
9 | public class CookieWrapper {
10 |
11 | String name;
12 |
13 | String value;
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: maven
4 | directory: /
5 | schedule:
6 | interval: weekly
7 | assignees:
8 | - dheid
9 | - package-ecosystem: github-actions
10 | directory: /
11 | schedule:
12 | interval: weekly
13 | assignees:
14 | - dheid
15 |
--------------------------------------------------------------------------------
/test/src/main/resources/web/track.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Matomo Java Tracker Servlet Test
7 |
8 |
9 | Thank you! Your request was sent to Matomo at http://localhost:8080.
10 |
11 |
--------------------------------------------------------------------------------
/java8/src/test/java/org/matomo/java/tracking/TrustingHostnameVerifierTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | class TrustingHostnameVerifierTest {
8 |
9 | @Test
10 | void verifyAlwaysReturnsTrue() {
11 |
12 | boolean verified = new TrustingHostnameVerifier().verify(null, null);
13 |
14 | assertThat(verified).isTrue();
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/spring/src/main/java/org/matomo/java/tracking/spring/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides Spring specific classes to integrate Matomo tracking into Spring applications.
3 | *
4 | * See {@link org.matomo.java.tracking.spring.MatomoTrackerProperties} for the available configuration properties.
5 | *
6 | *
See {@link org.matomo.java.tracking.spring.MatomoTrackerAutoConfiguration} for the available configuration
7 | * options.
8 | */
9 |
10 | package org.matomo.java.tracking.spring;
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: dheid
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Code snippets**
14 | Code to reproduce the behavior
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Additional context**
20 | Add any other context about the problem here.
21 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | - [ ] Ensure that the pull request title represents the desired changelog entry
4 | - [ ] Please describe what you did
5 | - [ ] Link to relevant issues in GitHub
6 | - [ ] Link to relevant pull requests, esp. upstream and downstream changes
7 | - [ ] Ensure you have provided tests - that demonstrates feature works or fixes the issue
8 |
9 |
12 |
--------------------------------------------------------------------------------
/java8/src/main/java/org/matomo/java/tracking/TrustingHostnameVerifier.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import edu.umd.cs.findbugs.annotations.Nullable;
4 | import javax.net.ssl.HostnameVerifier;
5 | import javax.net.ssl.SSLSession;
6 |
7 | class TrustingHostnameVerifier implements HostnameVerifier {
8 |
9 | @Override
10 | public boolean verify(
11 | @Nullable
12 | String hostname,
13 | @Nullable
14 | SSLSession session
15 | ) {
16 | return true;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/parameters/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Contains types for Matomo Tracking Parameters according to
3 | * the Matomo Tracking HTTP API.
4 | *
5 | *
The types help you to use the correct format for the tracking parameters. The package was introduced in Matomo
6 | * Java Tracker version 3 to let the tracker be more self-explanatory and better maintainable.
7 | */
8 |
9 | package org.matomo.java.tracking.parameters;
10 |
--------------------------------------------------------------------------------
/core/src/test/java/org/piwik/java/tracking/CustomVariableTest.java:
--------------------------------------------------------------------------------
1 | package org.piwik.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | class CustomVariableTest {
8 |
9 | @Test
10 | void createsCustomVariable() {
11 | CustomVariable customVariable = new CustomVariable("key", "value");
12 |
13 | assertThat(customVariable.getKey()).isEqualTo("key");
14 | assertThat(customVariable.getValue()).isEqualTo("value");
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/TestSenderFactory.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import lombok.Getter;
4 |
5 | class TestSenderFactory implements SenderFactory {
6 |
7 | @Getter
8 | private TestSender testSender;
9 |
10 | @Override
11 | public Sender createSender(TrackerConfiguration trackerConfiguration, QueryCreator queryCreator) {
12 | TestSender testSender = new TestSender(trackerConfiguration, queryCreator);
13 | this.testSender = testSender;
14 | return testSender;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/InvalidUrlException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking;
9 |
10 | /**
11 | * Thrown when an invalid URL is passed to the tracker.
12 | */
13 | public class InvalidUrlException extends RuntimeException {
14 |
15 | InvalidUrlException(Throwable cause) {
16 | super(cause);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/servlet-jakarta/src/test/java/org/matomo/java/tracking/TestSenderFactory.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import lombok.Getter;
4 |
5 | class TestSenderFactory implements SenderFactory {
6 |
7 | @Getter
8 | private TestSender testSender;
9 |
10 | @Override
11 | public Sender createSender(TrackerConfiguration trackerConfiguration, QueryCreator queryCreator) {
12 | TestSender testSender = new TestSender(trackerConfiguration, queryCreator);
13 | this.testSender = testSender;
14 | return testSender;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/servlet-javax/src/test/java/org/matomo/java/tracking/TestSenderFactory.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import lombok.Getter;
4 |
5 | class TestSenderFactory implements SenderFactory {
6 |
7 | @Getter
8 | private TestSender testSender;
9 |
10 | @Override
11 | public Sender createSender(TrackerConfiguration trackerConfiguration, QueryCreator queryCreator) {
12 | TestSender testSender = new TestSender(trackerConfiguration, queryCreator);
13 | this.testSender = testSender;
14 | return testSender;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/InvalidUrlExceptionTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | class InvalidUrlExceptionTest {
8 |
9 | @Test
10 | void createsInvalidUrlException() {
11 | InvalidUrlException invalidUrlException = new InvalidUrlException(new Throwable());
12 |
13 | assertThat(invalidUrlException).isNotNull();
14 | assertThat(invalidUrlException.getCause()).isNotNull();
15 |
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/DaemonThreadFactory.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import java.util.concurrent.ThreadFactory;
5 | import java.util.concurrent.atomic.AtomicInteger;
6 |
7 | class DaemonThreadFactory implements ThreadFactory {
8 |
9 | private final AtomicInteger count = new AtomicInteger();
10 |
11 | @Override
12 | public Thread newThread(@NonNull Runnable r) {
13 | Thread thread = new Thread(null, r, String.format("MatomoJavaTracker-%d", count.getAndIncrement()));
14 | thread.setDaemon(true);
15 | return thread;
16 | }
17 | }
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | The following versions of this library are
6 | currently being supported with security updates.
7 |
8 | | Version | Supported |
9 | |---------|------------------------|
10 | | >3 | :white_check_mark: yes |
11 | | <=2 | ✖️ no |
12 |
13 | ## Reporting a Vulnerability
14 |
15 | If you found a security vulerability please don't hesitate to send me a message,
16 | open a new [discussion](https://github.com/matomo-org/matomo-java-tracker/discussions) or
17 | open a new [issue](https://github.com/matomo-org/matomo-java-tracker/issues).
18 |
--------------------------------------------------------------------------------
/core/src/test/java/org/piwik/java/tracking/EcommerceItemTest.java:
--------------------------------------------------------------------------------
1 | package org.piwik.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | class EcommerceItemTest {
8 |
9 | @Test
10 | void createsEcItem() {
11 | EcommerceItem item = new EcommerceItem("sku", "name", "category", 1.0, 1);
12 |
13 | assertThat(item.getSku()).isEqualTo("sku");
14 | assertThat(item.getName()).isEqualTo("name");
15 | assertThat(item.getCategory()).isEqualTo("category");
16 | assertThat(item.getPrice()).isEqualTo(1.0);
17 | assertThat(item.getQuantity()).isEqualTo(1);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 | services:
3 | database:
4 | image: mariadb:10.11.5-jammy
5 | command: --max-allowed-packet=64MB
6 | environment:
7 | - MYSQL_ROOT_PASSWORD=matomo
8 | - MYSQL_PASSWORD=matomo
9 | - MYSQL_DATABASE=matomo
10 | - MYSQL_USER=matomo
11 | matomo:
12 | image: matomo:4.15.1-apache
13 | environment:
14 | - MATOMO_DATABASE_HOST=database
15 | - MATOMO_DATABASE_ADAPTER=mysql
16 | - MATOMO_DATABASE_TABLES_PREFIX=matomo_
17 | - MATOMO_DATABASE_USERNAME=matomo
18 | - MATOMO_DATABASE_PASSWORD=matomo
19 | - MATOMO_DATABASE_DBNAME=matomo
20 | ports:
21 | - '8080:80'
22 |
--------------------------------------------------------------------------------
/java8/src/main/java/org/matomo/java/tracking/Java8SenderProvider.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import java.util.concurrent.Executors;
4 |
5 | /**
6 | * Provides a {@link Sender} implementation based on Java 8.
7 | */
8 | public class Java8SenderProvider implements SenderProvider {
9 |
10 | @Override
11 | public Sender provideSender(
12 | TrackerConfiguration trackerConfiguration, QueryCreator queryCreator
13 | ) {
14 | return new Java8Sender(
15 | trackerConfiguration,
16 | queryCreator,
17 | Executors.newFixedThreadPool(trackerConfiguration.getThreadPoolSize(), new DaemonThreadFactory())
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/parameters/Hex.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import lombok.NonNull;
11 |
12 | final class Hex {
13 |
14 | private Hex() {
15 | // utility class
16 | }
17 |
18 | static String fromBytes(@NonNull byte[] bytes) {
19 | StringBuilder result = new StringBuilder(bytes.length * 2);
20 | for (byte b : bytes) {
21 | result.append(String.format("%02x", b));
22 | }
23 | return result.toString();
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: "[REQUEST]"
5 | labels: enhancement
6 | assignees: dheid
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/parameters/UniqueIdTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.parameters;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | class UniqueIdTest {
8 |
9 | @Test
10 | void createsRandomUniqueId() {
11 |
12 | UniqueId uniqueId = UniqueId.random();
13 |
14 | assertThat(uniqueId.toString()).matches("[0-9a-zA-Z]{6}");
15 |
16 | }
17 |
18 | @Test
19 | void createsSameUniqueIds() {
20 |
21 | UniqueId uniqueId1 = UniqueId.fromValue(868686868L);
22 | UniqueId uniqueId2 = UniqueId.fromValue(868686868);
23 |
24 | assertThat(uniqueId1).hasToString(uniqueId2.toString());
25 |
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/MatomoLocaleTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
5 |
6 | import java.util.Locale;
7 | import org.junit.jupiter.api.Test;
8 |
9 | class MatomoLocaleTest {
10 |
11 | @Test
12 | void createsMatomoLocaleFromLocale() {
13 | MatomoLocale locale = new MatomoLocale(Locale.US);
14 | assertThat(locale.toString()).isEqualTo("us");
15 | }
16 |
17 | @Test
18 | void failsIfLocaleIsNull() {
19 | assertThatThrownBy(() -> new MatomoLocale(null))
20 | .isInstanceOf(NullPointerException.class)
21 | .hasMessage("Locale must not be null");
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/MatomoException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking;
9 |
10 | /**
11 | * Thrown when an error occurs while communicating with the Matomo server or when the request is invalid.
12 | */
13 | public class MatomoException extends RuntimeException {
14 |
15 | private static final long serialVersionUID = 4592083764365938934L;
16 |
17 | MatomoException(String message) {
18 | super(message);
19 | }
20 |
21 | MatomoException(String message, Throwable cause) {
22 | super(message, cause);
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * This package contains classes that allow you to specify a {@link org.matomo.java.tracking.MatomoTracker}
3 | * with the corresponding {@link org.matomo.java.tracking.TrackerConfiguration}. You can then send a
4 | * {@link org.matomo.java.tracking.MatomoRequest} as a single HTTP GET request or multiple requests as a bulk HTTP POST
5 | * request synchronously or asynchronously. If an exception occurs, {@link org.matomo.java.tracking.MatomoException}
6 | * will be thrown.
7 | *
8 | *
For more information about the Matomo Tracking HTTP API, see the Matomo Tracking HTTP API.
9 | */
10 |
11 | package org.matomo.java.tracking;
12 |
13 |
14 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/TrustingX509TrustManager.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import edu.umd.cs.findbugs.annotations.Nullable;
4 | import java.security.cert.X509Certificate;
5 | import javax.net.ssl.X509TrustManager;
6 |
7 | class TrustingX509TrustManager implements X509TrustManager {
8 |
9 | @Override
10 | @Nullable
11 | public X509Certificate[] getAcceptedIssuers() {
12 | return null;
13 | }
14 |
15 | @Override
16 | public void checkClientTrusted(
17 | @Nullable X509Certificate[] chain, @Nullable String authType
18 | ) {
19 | // no operation
20 | }
21 |
22 | @Override
23 | public void checkServerTrusted(
24 | @Nullable X509Certificate[] chain, @Nullable String authType
25 | ) {
26 | // no operation
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/MatomoExceptionTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | class MatomoExceptionTest {
8 |
9 | @Test
10 | void createsMatomoExceptionWithMessage() {
11 | MatomoException matomoException = new MatomoException("message");
12 |
13 | assertEquals("message", matomoException.getMessage());
14 | }
15 |
16 | @Test
17 | void createsMatomoExceptionWithMessageAndCause() {
18 | Throwable cause = new Throwable();
19 | MatomoException matomoException = new MatomoException("message", cause);
20 |
21 | assertEquals("message", matomoException.getMessage());
22 | assertEquals(cause, matomoException.getCause());
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/PiwikLocaleTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import java.util.Locale;
6 | import org.junit.jupiter.api.Test;
7 | import org.piwik.java.tracking.PiwikLocale;
8 |
9 | class PiwikLocaleTest {
10 |
11 | private final PiwikLocale locale = new PiwikLocale(Locale.US);
12 |
13 | /**
14 | * Test of setLocale method, of class PiwikLocale.
15 | */
16 | @Test
17 | void testLocale() {
18 | locale.setLocale(Locale.GERMANY);
19 | assertThat(locale.getLocale()).isEqualTo(Locale.GERMAN);
20 | }
21 |
22 | /**
23 | * Test of toString method, of class PiwikLocale.
24 | */
25 | @Test
26 | void testToString() {
27 | assertThat(locale).hasToString("us");
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/DaemonThreadFactoryTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | class DaemonThreadFactoryTest {
8 |
9 | private final DaemonThreadFactory daemonThreadFactory = new DaemonThreadFactory();
10 |
11 | @Test
12 | void createsNewThreadAsDaemonThread() {
13 | Thread thread = daemonThreadFactory.newThread(() -> {
14 | // do nothing
15 | });
16 | assertThat(thread.isDaemon()).isTrue();
17 | }
18 |
19 | @Test
20 | void createsNewThreadWithMatomoJavaTrackerName() {
21 | Thread thread = daemonThreadFactory.newThread(() -> {
22 | // do nothing
23 | });
24 | assertThat(thread.getName()).startsWith("MatomoJavaTracker-");
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/TrackingParameter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking;
9 |
10 | import java.lang.annotation.ElementType;
11 | import java.lang.annotation.Retention;
12 | import java.lang.annotation.RetentionPolicy;
13 | import java.lang.annotation.Target;
14 |
15 | @Target(ElementType.FIELD)
16 | @Retention(RetentionPolicy.RUNTIME)
17 | @interface TrackingParameter {
18 |
19 | String name();
20 |
21 | String regex() default "";
22 |
23 | double min() default Double.MIN_VALUE;
24 |
25 | double max() default Double.MAX_VALUE;
26 |
27 | int maxLength() default Integer.MAX_VALUE;
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/Sender.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import edu.umd.cs.findbugs.annotations.Nullable;
5 | import java.util.Collection;
6 | import java.util.concurrent.CompletableFuture;
7 |
8 | interface Sender extends AutoCloseable {
9 | @NonNull
10 | CompletableFuture sendSingleAsync(
11 | @NonNull MatomoRequest request
12 | );
13 |
14 | void sendSingle(
15 | @NonNull MatomoRequest request
16 | );
17 |
18 | void sendBulk(
19 | @NonNull Iterable extends MatomoRequest> requests, @Nullable String overrideAuthToken
20 | );
21 |
22 | @NonNull
23 | CompletableFuture sendBulkAsync(
24 | @NonNull Collection extends MatomoRequest> requests, @Nullable String overrideAuthToken
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: Deploy GitHub Pages
2 | on:
3 | push:
4 | branches: ["main"]
5 | workflow_dispatch:
6 | permissions:
7 | contents: read
8 | pages: write
9 | id-token: write
10 | concurrency:
11 | group: "pages"
12 | cancel-in-progress: false
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v4
18 | - uses: actions/configure-pages@v5
19 | - uses: actions/jekyll-build-pages@v1
20 | with:
21 | source: ./
22 | destination: ./_site
23 | - uses: actions/upload-pages-artifact@v3
24 | deploy:
25 | environment:
26 | name: github-pages
27 | url: ${{ steps.deployment.outputs.page_url }}
28 | runs-on: ubuntu-latest
29 | needs: build
30 | steps:
31 | - id: deployment
32 | uses: actions/deploy-pages@v4
33 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Java CI with Maven
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | permissions:
13 | pull-requests: write
14 | checks: write
15 | contents: read
16 | steps:
17 | - uses: actions/checkout@v4
18 | - uses: actions/setup-java@v4
19 | with:
20 | java-version: '17'
21 | distribution: 'temurin'
22 | cache: maven
23 | - run: mvn -B verify
24 | - uses: madrapps/jacoco-report@v1.7.1
25 | with:
26 | paths: ${{ github.workspace }}/target/site/jacoco/jacoco.xml
27 | token: ${{ secrets.GITHUB_TOKEN }}
28 | min-coverage-overall: 80
29 | min-coverage-changed-files: 80
30 | - uses: scacap/action-surefire-report@v1.9.0
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/ServiceLoaderSenderFactoryTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
4 |
5 | import java.net.URI;
6 | import org.junit.jupiter.api.Test;
7 |
8 | class ServiceLoaderSenderFactoryTest {
9 |
10 | @Test
11 | void failsIfNoImplementationFound() {
12 | ServiceLoaderSenderFactory serviceLoaderSenderFactory = new ServiceLoaderSenderFactory();
13 |
14 | TrackerConfiguration trackerConfiguration =
15 | TrackerConfiguration.builder().apiEndpoint(URI.create("http://localhost/matomo.php")).build();
16 |
17 | assertThatThrownBy(() -> serviceLoaderSenderFactory.createSender(trackerConfiguration,
18 | new QueryCreator(trackerConfiguration)
19 | ))
20 | .isInstanceOf(MatomoException.class)
21 | .hasMessage("No SenderProvider found");
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/TrustingX509TrustManagerTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import java.security.cert.X509Certificate;
6 | import org.junit.jupiter.api.Test;
7 |
8 | class TrustingX509TrustManagerTest {
9 |
10 | private final TrustingX509TrustManager trustingX509TrustManager = new TrustingX509TrustManager();
11 |
12 | @Test
13 | void acceptedIssuersIsAlwaysNull() {
14 | X509Certificate[] acceptedIssuers = trustingX509TrustManager.getAcceptedIssuers();
15 | assertThat(acceptedIssuers).isNull();
16 | }
17 |
18 | @Test
19 | void checkClientTrustedDoesNothing() {
20 | trustingX509TrustManager.checkClientTrusted(null, null);
21 | }
22 |
23 | @Test
24 | void checkServerTrustedDoesNothing() {
25 | trustingX509TrustManager.checkServerTrusted(null, null);
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 | schedule:
9 | - cron: '27 11 * * 5'
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze
14 | runs-on: ubuntu-latest
15 | permissions:
16 | actions: read
17 | contents: read
18 | security-events: write
19 |
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | language: [ 'java' ]
24 |
25 | steps:
26 | - uses: actions/checkout@v4
27 | - uses: actions/setup-java@v4
28 | with:
29 | java-version: '17'
30 | distribution: 'temurin'
31 | cache: maven
32 | - uses: github/codeql-action/init@v3
33 | with:
34 | languages: ${{ matrix.language }}
35 | - uses: github/codeql-action/autobuild@v3
36 | - uses: github/codeql-action/analyze@v3
37 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/CustomVariable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking;
9 |
10 | import lombok.NonNull;
11 |
12 | /**
13 | * A user defined custom variable.
14 | *
15 | * @author brettcsorba
16 | * @deprecated Use {@link org.matomo.java.tracking.parameters.EcommerceItem} instead.
17 | */
18 | @Deprecated
19 | public class CustomVariable extends org.matomo.java.tracking.parameters.CustomVariable {
20 |
21 | /**
22 | * Instantiates a new custom variable.
23 | *
24 | * @param key the key of the custom variable (required)
25 | * @param value the value of the custom variable (required)
26 | */
27 | public CustomVariable(@NonNull String key, @NonNull String value) {
28 | super(key, value);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/src/main/java/org/piwik/java/tracking/PiwikLocale.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.piwik.java.tracking;
9 |
10 | import java.util.Locale;
11 | import org.matomo.java.tracking.parameters.Country;
12 |
13 | /**
14 | * A locale object that can be used to send visitor country to Matomo. This class is deprecated and will be removed in
15 | * the future.
16 | *
17 | * @author brettcsorba
18 | * @deprecated Use {@link Country} instead.
19 | */
20 | @Deprecated
21 | public class PiwikLocale extends Country {
22 |
23 | /**
24 | * Creates a new Piwik locale object with the specified locale.
25 | *
26 | * @param locale the locale to use
27 | * @deprecated Use {@link Country} instead.
28 | */
29 | @Deprecated
30 | public PiwikLocale(Locale locale) {
31 | super(locale);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/ProxyAuthenticator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking;
9 |
10 | import edu.umd.cs.findbugs.annotations.Nullable;
11 | import java.net.Authenticator;
12 | import java.net.PasswordAuthentication;
13 | import lombok.NonNull;
14 | import lombok.RequiredArgsConstructor;
15 |
16 | @RequiredArgsConstructor
17 | class ProxyAuthenticator extends Authenticator {
18 |
19 | @NonNull
20 | private final String user;
21 |
22 | @NonNull
23 | private final String password;
24 |
25 | @Nullable
26 | @Override
27 | protected PasswordAuthentication getPasswordAuthentication() {
28 | if (getRequestorType() == RequestorType.PROXY) {
29 | return new PasswordAuthentication(user, password.toCharArray());
30 | }
31 | return null;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/parameters/EcommerceItem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import lombok.AllArgsConstructor;
11 | import lombok.Builder;
12 | import lombok.Getter;
13 | import lombok.Setter;
14 |
15 | /**
16 | * Represents an item in an ecommerce order.
17 | */
18 | @Builder
19 | @AllArgsConstructor
20 | @Getter
21 | @Setter
22 | public class EcommerceItem {
23 |
24 | private String sku;
25 |
26 | @Builder.Default
27 | private String name = "";
28 |
29 | @Builder.Default
30 | private String category = "";
31 |
32 | @Builder.Default
33 | private Double price = 0.0;
34 |
35 | @Builder.Default
36 | private Integer quantity = 0;
37 |
38 | public String toString() {
39 | return String.format("[\"%s\",\"%s\",\"%s\",%s,%d]", sku, name, category, price, quantity);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/ActionType.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import java.util.function.BiConsumer;
4 | import lombok.NonNull;
5 | import lombok.RequiredArgsConstructor;
6 |
7 | /**
8 | * The type of action performed (download or outlink).
9 | */
10 | @RequiredArgsConstructor
11 | public enum ActionType {
12 | DOWNLOAD(MatomoRequest.MatomoRequestBuilder::downloadUrl),
13 | LINK(MatomoRequest.MatomoRequestBuilder::outlinkUrl);
14 |
15 | @NonNull
16 | private final BiConsumer consumer;
17 |
18 | /**
19 | * Applies the action URL to the given builder.
20 | *
21 | * @param builder The builder to apply the action URL to.
22 | * @param actionUrl The action URL to apply.
23 | *
24 | * @return The builder with the action URL applied.
25 | */
26 | public MatomoRequest.MatomoRequestBuilder applyUrl(
27 | @NonNull MatomoRequest.MatomoRequestBuilder builder,
28 | @NonNull String actionUrl
29 | ) {
30 | consumer.accept(builder, actionUrl);
31 | return builder;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/core/src/main/java/org/piwik/java/tracking/CustomVariable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.piwik.java.tracking;
9 |
10 | /**
11 | * A user defined custom variable.
12 | *
13 | * Renamed to {@link org.matomo.java.tracking.parameters.CustomVariable} in 3.0.0.
14 | *
15 | * @author brettcsorba
16 | * @deprecated Use {@link org.matomo.java.tracking.parameters.CustomVariable} instead.
17 | */
18 | @Deprecated
19 | public class CustomVariable extends org.matomo.java.tracking.parameters.CustomVariable {
20 |
21 | /**
22 | * Instantiates a new custom variable.
23 | *
24 | * @param key the key of the custom variable (required)
25 | * @param value the value of the custom variable (required)
26 | * @deprecated Use {@link org.matomo.java.tracking.parameters.CustomVariable} instead.
27 | */
28 | @Deprecated
29 | public CustomVariable(String key, String value) {
30 | super(key, value);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/EcommerceItem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking;
9 |
10 |
11 | /**
12 | * A user defined custom variable.
13 | *
14 | * @author brettcsorba
15 | * @deprecated Use {@link org.matomo.java.tracking.parameters.EcommerceItem} instead.
16 | */
17 | @Deprecated
18 | public class EcommerceItem extends org.matomo.java.tracking.parameters.EcommerceItem {
19 |
20 |
21 | /**
22 | * Instantiates a new ecommerce item.
23 | *
24 | * @param sku the sku (Stock Keeping Unit) of the item
25 | * @param name the name of the item
26 | * @param category the category of the item
27 | * @param price the price of the item
28 | * @param quantity the quantity of the item
29 | */
30 | public EcommerceItem(
31 | String sku, String name, String category, Double price, Integer quantity
32 | ) {
33 | super(sku, name, category, price, quantity);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/parameters/EcommerceItems.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | import java.util.stream.Collectors;
13 | import lombok.AllArgsConstructor;
14 | import lombok.Builder;
15 | import lombok.EqualsAndHashCode;
16 | import lombok.Getter;
17 | import lombok.NoArgsConstructor;
18 | import lombok.Setter;
19 | import lombok.Singular;
20 | import lombok.experimental.Delegate;
21 |
22 | /**
23 | * Multiple things that you can buy online.
24 | */
25 | @Builder
26 | @AllArgsConstructor
27 | @NoArgsConstructor
28 | @EqualsAndHashCode
29 | @Getter
30 | @Setter
31 | public class EcommerceItems {
32 |
33 | @Delegate
34 | @Singular
35 | private List items = new ArrayList<>();
36 |
37 | public String toString() {
38 | return items.stream().map(String::valueOf).collect(Collectors.joining(",", "[", "]"));
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/BulkRequestTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static java.util.Collections.emptyList;
4 | import static java.util.Collections.singleton;
5 | import static org.assertj.core.api.Assertions.assertThat;
6 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
7 |
8 | import org.junit.jupiter.api.Test;
9 |
10 | class BulkRequestTest {
11 |
12 | @Test
13 | void formatsQueriesAsJson() {
14 | BulkRequest bulkRequest = BulkRequest.builder()
15 | .queries(singleton("idsite=1&rec=1&action_name=TestBulkRequest"))
16 | .authToken("token")
17 | .build();
18 |
19 | byte[] bytes = bulkRequest.toBytes();
20 |
21 | assertThat(new String(bytes)).isEqualTo("{\"requests\":[\"?idsite=1&rec=1&action_name=TestBulkRequest\"],\"token_auth\":\"token\"}");
22 | }
23 |
24 | @Test
25 | void failsIfQueriesAreEmpty() {
26 |
27 | BulkRequest bulkRequest = BulkRequest.builder().queries(emptyList()).build();
28 |
29 | assertThatThrownBy(bulkRequest::toBytes)
30 | .isInstanceOf(IllegalArgumentException.class)
31 | .hasMessage("Queries must not be empty");
32 |
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/MatomoLocale.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking;
9 |
10 | import static java.util.Objects.requireNonNull;
11 |
12 | import edu.umd.cs.findbugs.annotations.NonNull;
13 | import java.util.Locale;
14 | import lombok.Getter;
15 | import lombok.Setter;
16 | import org.matomo.java.tracking.parameters.Country;
17 |
18 | /**
19 | * Object representing a locale required by some Matomo query parameters.
20 | *
21 | * @author brettcsorba
22 | * @deprecated Use {@link Country} instead
23 | */
24 | @Setter
25 | @Getter
26 | @Deprecated
27 | public class MatomoLocale extends Country {
28 |
29 | /**
30 | * Constructs a new MatomoLocale.
31 | *
32 | * @param locale The locale to get the country code from
33 | * @deprecated Please use {@link Country}
34 | */
35 | @Deprecated
36 | public MatomoLocale(
37 | @NonNull
38 | Locale locale
39 | ) {
40 | super(requireNonNull(locale, "Locale must not be null"));
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/core/src/main/java/org/piwik/java/tracking/PiwikRequest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.piwik.java.tracking;
9 |
10 | import static java.util.Objects.requireNonNull;
11 |
12 | import java.net.URL;
13 | import org.matomo.java.tracking.MatomoRequest;
14 |
15 | /**
16 | * A request object that can be used to send requests to Matomo. This class is deprecated and will be removed in the
17 | * future.
18 | *
19 | * @author brettcsorba
20 | * @deprecated Use {@link MatomoRequest} instead.
21 | */
22 | @Deprecated
23 | public class PiwikRequest extends MatomoRequest {
24 |
25 | /**
26 | * Creates a new request object with the specified site ID and action URL.
27 | *
28 | * @param siteId the site ID
29 | * @param actionUrl the action URL. Must not be null.
30 | * @deprecated Use {@link MatomoRequest} instead.
31 | */
32 | @Deprecated
33 | public PiwikRequest(int siteId, URL actionUrl) {
34 | super(siteId, requireNonNull(actionUrl, "Action URL must not be null").toString());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/CustomVariableTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
5 |
6 | import org.junit.jupiter.api.Test;
7 |
8 | class CustomVariableTest {
9 |
10 | @Test
11 | void createsCustomVariable() {
12 | CustomVariable customVariable = new CustomVariable("key", "value");
13 |
14 | assertThat(customVariable.getKey()).isEqualTo("key");
15 | assertThat(customVariable.getValue()).isEqualTo("value");
16 | }
17 |
18 | @Test
19 | void failsOnNullKey() {
20 | assertThatThrownBy(() -> new CustomVariable(
21 | null,
22 | "value"
23 | )).isInstanceOf(NullPointerException.class);
24 | }
25 |
26 | @Test
27 | void failsOnNullValue() {
28 | assertThatThrownBy(() -> new CustomVariable(
29 | "key",
30 | null
31 | )).isInstanceOf(NullPointerException.class);
32 | }
33 |
34 | @Test
35 | void failsOnNullKeyAndValue() {
36 | assertThatThrownBy(() -> new CustomVariable(
37 | null,
38 | null
39 | )).isInstanceOf(NullPointerException.class);
40 | }
41 |
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/core/src/main/java/org/piwik/java/tracking/EcommerceItem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.piwik.java.tracking;
9 |
10 | /**
11 | * Describes an item in an ecommerce transaction.
12 | *
13 | * @author brettcsorba
14 | * @deprecated Use {@link org.matomo.java.tracking.parameters.EcommerceItem} instead.
15 | */
16 | @Deprecated
17 | public class EcommerceItem extends org.matomo.java.tracking.parameters.EcommerceItem {
18 |
19 | /**
20 | * Creates a new ecommerce item.
21 | *
22 | * @param sku the sku (Stock Keeping Unit) of the item.
23 | * @param name the name of the item.
24 | * @param category the category of the item.
25 | * @param price the price of the item.
26 | * @param quantity the quantity of the item.
27 | * @deprecated Use {@link org.matomo.java.tracking.parameters.EcommerceItem} instead.
28 | */
29 | @Deprecated
30 | public EcommerceItem(String sku, String name, String category, Double price, Integer quantity) {
31 | super(sku, name, category, price, quantity);
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/test/java/org/piwik/java/tracking/PiwikTrackerIT.java:
--------------------------------------------------------------------------------
1 | package org.piwik.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | class PiwikTrackerIT {
8 |
9 | private PiwikTracker piwikTracker;
10 |
11 | @Test
12 | void createsNewPiwikTrackerInstanceWithHostUrl() {
13 |
14 | piwikTracker = new PiwikTracker("http://localhost:8080");
15 |
16 | assertThat(piwikTracker).isNotNull();
17 |
18 | }
19 |
20 | @Test
21 | void createsNewPiwikTrackerInstanceWithHostUrlAndTimeout() {
22 |
23 | piwikTracker = new PiwikTracker("http://localhost:8080", 1000);
24 |
25 | assertThat(piwikTracker).isNotNull();
26 |
27 | }
28 |
29 | @Test
30 | void createsNewPiwikTrackerInstanceWithHostUrlAndProxySettings() {
31 |
32 | piwikTracker = new PiwikTracker("http://localhost:8080", "localhost", 8080);
33 |
34 | assertThat(piwikTracker).isNotNull();
35 |
36 | }
37 |
38 | @Test
39 | void createsNewPiwikTrackerInstanceWithHostUrlAndProxySettingsAndTimeout() {
40 |
41 | piwikTracker = new PiwikTracker("http://localhost:8080", "localhost", 8080, 1000);
42 |
43 | assertThat(piwikTracker).isNotNull();
44 |
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/PiwikDateTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import java.util.TimeZone;
6 | import org.junit.jupiter.api.Test;
7 | import org.piwik.java.tracking.PiwikDate;
8 |
9 |
10 | class PiwikDateTest {
11 |
12 | /**
13 | * Test of constructor, of class PiwikDate.
14 | */
15 | @Test
16 | void testConstructor0() {
17 | PiwikDate date = new PiwikDate();
18 | assertThat(date).isNotNull();
19 | }
20 |
21 | @Test
22 | void testConstructor1() {
23 | PiwikDate date = new PiwikDate(1433186085092L);
24 | assertThat(date).isNotNull();
25 | assertThat(date.getTime()).isEqualTo(1433186085092L);
26 | }
27 |
28 | @Test
29 | void testConstructor2() {
30 | PiwikDate date = new PiwikDate(1467437553000L);
31 | assertThat(date.getTime()).isEqualTo(1467437553000L);
32 | }
33 |
34 | /**
35 | * Test of setTimeZone method, of class PiwikDate.
36 | */
37 | @Test
38 | void testSetTimeZone() {
39 | PiwikDate date = new PiwikDate(1433186085092L);
40 | date.setTimeZone(TimeZone.getTimeZone("America/New_York"));
41 | assertThat(date.getTime()).isEqualTo(1433186085092L);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/parameters/EcommerceItemsTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.parameters;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 |
8 | class EcommerceItemsTest {
9 |
10 | @Test
11 | void formatsJson() {
12 | EcommerceItems ecommerceItems = EcommerceItems
13 | .builder()
14 | .item(EcommerceItem
15 | .builder()
16 | .sku("XYZ12345")
17 | .name("Matomo - The big book about web analytics")
18 | .category("Education & Teaching")
19 | .price(23.1)
20 | .quantity(2)
21 | .build())
22 | .item(EcommerceItem
23 | .builder()
24 | .sku("B0C2WV3MRJ")
25 | .name("Matomo for data visualization")
26 | .category("Education & Teaching")
27 | .price(15.1)
28 | .quantity(1)
29 | .build())
30 | .build();
31 | assertThat(ecommerceItems).hasToString("[[\"XYZ12345\",\"Matomo - The big book about web analytics\",\"Education & Teaching\",23.1,2],[\"B0C2WV3MRJ\",\"Matomo for data visualization\",\"Education & Teaching\",15.1,1]]");
32 | }
33 |
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/BulkRequest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import edu.umd.cs.findbugs.annotations.Nullable;
4 | import java.nio.charset.StandardCharsets;
5 | import java.util.Collection;
6 | import java.util.Iterator;
7 | import lombok.Builder;
8 | import lombok.NonNull;
9 | import lombok.Value;
10 |
11 | @Builder
12 | @Value
13 | class BulkRequest {
14 |
15 | @NonNull
16 | Collection queries;
17 |
18 | @Nullable
19 | String authToken;
20 |
21 | byte[] toBytes(
22 |
23 | ) {
24 | if (queries.isEmpty()) {
25 | throw new IllegalArgumentException("Queries must not be empty");
26 | }
27 | StringBuilder payload = new StringBuilder("{\"requests\":[");
28 | Iterator iterator = queries.iterator();
29 | while (iterator.hasNext()) {
30 | String query = iterator.next();
31 | payload.append("\"?").append(query).append('"');
32 | if (iterator.hasNext()) {
33 | payload.append(',');
34 | }
35 | }
36 | payload.append(']');
37 | if (authToken != null) {
38 | payload.append(",\"token_auth\":\"").append(authToken).append('"');
39 | }
40 | return payload.append('}').toString().getBytes(StandardCharsets.UTF_8);
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/parameters/HexTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.parameters;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
5 |
6 | import java.util.stream.Stream;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.params.ParameterizedTest;
9 | import org.junit.jupiter.params.provider.Arguments;
10 | import org.junit.jupiter.params.provider.MethodSource;
11 |
12 | class HexTest {
13 |
14 |
15 |
16 | @Test
17 | void failsIfBytesAreNull() {
18 | assertThatThrownBy(() -> Hex.fromBytes(null))
19 | .isInstanceOf(NullPointerException.class)
20 | .hasMessage("bytes is marked non-null but is null");
21 | }
22 |
23 | private static Stream testBytes() {
24 | return Stream.of(
25 | Arguments.of(new byte[] {0x00, 0x01, 0x02, 0x03}, "00010203"),
26 | Arguments.of(new byte[] {(byte) 0xFF, (byte) 0xFE, (byte) 0xFD, (byte) 0xFC}, "fffefdfc"),
27 | Arguments.of(new byte[0], "")
28 | );
29 | }
30 |
31 | @ParameterizedTest
32 | @MethodSource("testBytes")
33 | void convertsBytesIntoHex(byte[] bytes, String expected) {
34 | assertThat(Hex.fromBytes(bytes)).isEqualTo(expected);
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/ServiceLoaderSenderFactory.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static java.util.stream.Collectors.toMap;
4 |
5 | import java.util.Map;
6 | import java.util.ServiceLoader;
7 | import java.util.function.Function;
8 | import java.util.stream.StreamSupport;
9 |
10 | class ServiceLoaderSenderFactory implements SenderFactory {
11 |
12 | @Override
13 | public Sender createSender(TrackerConfiguration trackerConfiguration, QueryCreator queryCreator) {
14 | ServiceLoader serviceLoader = ServiceLoader.load(SenderProvider.class);
15 | Map senderProviders = StreamSupport
16 | .stream(serviceLoader.spliterator(), false)
17 | .collect(toMap(senderProvider -> senderProvider.getClass().getName(), Function.identity()));
18 | SenderProvider senderProvider = senderProviders.get("org.matomo.java.tracking.Java11SenderProvider");
19 | if (senderProvider == null) {
20 | senderProvider = senderProviders.get("org.matomo.java.tracking.Java8SenderProvider");
21 | }
22 | if (senderProvider == null) {
23 | throw new MatomoException("No SenderProvider found");
24 | }
25 | return senderProvider.provideSender(trackerConfiguration, new QueryCreator(trackerConfiguration));
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/spring/src/main/java/org/matomo/java/tracking/spring/TrackerConfigurationBuilderCustomizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.spring;
9 |
10 | import org.matomo.java.tracking.TrackerConfiguration;
11 | import org.springframework.lang.NonNull;
12 |
13 | /**
14 | * Allows to customize the {@link TrackerConfiguration.TrackerConfigurationBuilder} with additional properties.
15 | *
16 | * Implementations of this interface are detected automatically by the {@link MatomoTrackerAutoConfiguration}.
17 | *
18 | * @see MatomoTrackerAutoConfiguration
19 | * @see TrackerConfiguration
20 | * @see TrackerConfiguration.TrackerConfigurationBuilder
21 | */
22 | @FunctionalInterface
23 | public interface TrackerConfigurationBuilderCustomizer {
24 |
25 | /**
26 | * Customize the {@link TrackerConfiguration.TrackerConfigurationBuilder}.
27 | *
28 | * @param builder the {@link TrackerConfiguration.TrackerConfigurationBuilder} instance (never {@code null})
29 | * @see TrackerConfiguration#builder()
30 | * @see MatomoTrackerProperties
31 | */
32 | void customize(@NonNull TrackerConfiguration.TrackerConfigurationBuilder builder);
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/test/src/main/java/org/matomo/java/tracking/test/SendExample.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.test;
2 |
3 | import java.net.URI;
4 | import org.matomo.java.tracking.MatomoRequests;
5 | import org.matomo.java.tracking.MatomoTracker;
6 | import org.matomo.java.tracking.TrackerConfiguration;
7 | import org.matomo.java.tracking.parameters.VisitorId;
8 |
9 | /**
10 | * Example for sending a request.
11 | */
12 | public class SendExample {
13 |
14 | /**
15 | * Example for sending a request.
16 | *
17 | * @param args ignored
18 | */
19 | public static void main(String[] args) {
20 |
21 | TrackerConfiguration configuration = TrackerConfiguration
22 | .builder()
23 | .apiEndpoint(URI.create("https://www.yourdomain.com/matomo.php"))
24 | .defaultSiteId(1)
25 | .defaultAuthToken("ee6e3dd9ed1b61f5328cf5978b5a8c71")
26 | .logFailedTracking(true)
27 | .build();
28 |
29 | try (MatomoTracker tracker = new MatomoTracker(configuration)) {
30 | tracker.sendRequestAsync(MatomoRequests
31 | .event("Training", "Workout completed", "Bench press", 60.0)
32 | .visitorId(VisitorId.fromString("customer@mail.com"))
33 | .build()
34 | );
35 | } catch (Exception e) {
36 | throw new RuntimeException("Could not close tracker", e);
37 | }
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/ExecutorServiceCloserTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import java.util.concurrent.ExecutorService;
6 | import java.util.concurrent.Executors;
7 | import org.junit.jupiter.api.Test;
8 |
9 | class ExecutorServiceCloserTest {
10 |
11 | @Test
12 | void shutsDownExecutorService() {
13 |
14 | ExecutorService executorService = Executors.newFixedThreadPool(2, new DaemonThreadFactory());
15 |
16 | ExecutorServiceCloser.close(executorService);
17 |
18 | assertThat(executorService.isTerminated()).isTrue();
19 | assertThat(executorService.isShutdown()).isTrue();
20 |
21 | }
22 |
23 | @Test
24 | void shutsDownExecutorServiceImmediately() throws Exception {
25 |
26 | ExecutorService executorService = Executors.newSingleThreadExecutor();
27 | executorService.submit(() -> {
28 | try {
29 | Thread.sleep(10000L);
30 | } catch (InterruptedException e) {
31 | throw new RuntimeException(e);
32 | }
33 | });
34 |
35 | Thread thread = new Thread(() -> {
36 | ExecutorServiceCloser.close(executorService);
37 | });
38 | thread.start();
39 | Thread.sleep(1000L);
40 | thread.interrupt();
41 |
42 | assertThat(executorService.isShutdown()).isTrue();
43 |
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/ExecutorServiceCloser.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import java.util.concurrent.ExecutorService;
4 | import java.util.concurrent.TimeUnit;
5 | import lombok.NonNull;
6 |
7 | /**
8 | * Helps to close an executor service.
9 | */
10 | public class ExecutorServiceCloser {
11 |
12 | /**
13 | * Closes the given executor service.
14 | *
15 | *
This will check whether the executor service is already terminated, and if not, it
16 | * initiates a shutdown and waits a minute. If the minute expires, the executor service
17 | * is shutdown immediately.
18 | *
19 | * @param executorService The executor service to close
20 | */
21 | public static void close(@NonNull ExecutorService executorService) {
22 | boolean terminated = executorService.isTerminated();
23 | if (!terminated) {
24 | executorService.shutdown();
25 | boolean interrupted = false;
26 | while (!terminated) {
27 | try {
28 | terminated = executorService.awaitTermination(1L, TimeUnit.MINUTES);
29 | } catch (InterruptedException e) {
30 | if (!interrupted) {
31 | executorService.shutdownNow();
32 | interrupted = true;
33 | }
34 | }
35 | }
36 | if (interrupted) {
37 | Thread.currentThread().interrupt();
38 | }
39 | }
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release with Maven
2 | on:
3 | workflow_dispatch:
4 | jobs:
5 | publish:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v4
9 | - uses: actions/setup-java@v4
10 | with:
11 | java-version: '17'
12 | distribution: 'temurin'
13 | cache: maven
14 | server-id: ossrh
15 | server-username: OSSRH_USERNAME
16 | server-password: OSSRH_PASSWORD
17 | gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
18 | gpg-passphrase: GPG_PASSPHRASE
19 | - run: |
20 | git config user.email "matomo-java-tracker@daniel-heid.de"
21 | git config user.name "Matomo Java Tracker"
22 | - id: version
23 | run: |
24 | VERSION=$( mvn -B help:evaluate -Dexpression=project.version -q -DforceStdout )
25 | echo "::set-output name=version::${VERSION%-SNAPSHOT}"
26 | - run: mvn -B release:prepare release:perform
27 | env:
28 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
29 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
30 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
31 | - uses: release-drafter/release-drafter@v6
32 | with:
33 | version: ${{ steps.version.outputs.version }}
34 | publish: true
35 | env:
36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/AuthToken.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking;
9 |
10 | import edu.umd.cs.findbugs.annotations.Nullable;
11 |
12 | final class AuthToken {
13 |
14 | private AuthToken() {
15 | // utility
16 | }
17 |
18 | @Nullable
19 | static String determineAuthToken(
20 | @Nullable
21 | String overrideAuthToken,
22 | @Nullable
23 | Iterable extends MatomoRequest> requests,
24 | @Nullable
25 | TrackerConfiguration trackerConfiguration
26 | ) {
27 | if (isNotBlank(overrideAuthToken)) {
28 | return overrideAuthToken;
29 | }
30 | if (requests != null) {
31 | for (MatomoRequest request : requests) {
32 | if (request != null && isNotBlank(request.getAuthToken())) {
33 | return request.getAuthToken();
34 | }
35 | }
36 | }
37 | if (trackerConfiguration != null && isNotBlank(trackerConfiguration.getDefaultAuthToken())) {
38 | return trackerConfiguration.getDefaultAuthToken();
39 | }
40 | return null;
41 | }
42 |
43 | private static boolean isNotBlank(
44 | @Nullable
45 | String str
46 | ) {
47 | return str != null && !str.isEmpty() && !str.trim().isEmpty();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/parameters/AcceptLanguageTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import static java.util.Collections.singletonList;
11 | import static org.assertj.core.api.Assertions.assertThat;
12 |
13 | import org.junit.jupiter.api.Test;
14 | import org.junit.jupiter.params.ParameterizedTest;
15 | import org.junit.jupiter.params.provider.NullAndEmptySource;
16 |
17 | class AcceptLanguageTest {
18 |
19 | @Test
20 | void fromHeader() {
21 |
22 | AcceptLanguage acceptLanguage =
23 | AcceptLanguage.fromHeader("de,de-DE;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
24 |
25 | assertThat(acceptLanguage).hasToString(
26 | "de,de-de;q=0.9,de-dd;q=0.9,en;q=0.8,en-gb;q=0.7,en-us;q=0.6");
27 |
28 | }
29 |
30 | @ParameterizedTest
31 | @NullAndEmptySource
32 | void fromHeaderToleratesNull(String header) {
33 |
34 | AcceptLanguage acceptLanguage = AcceptLanguage.fromHeader(header);
35 |
36 | assertThat(acceptLanguage).isNull();
37 |
38 | }
39 |
40 | @Test
41 | void failsOnNullLanguageRange() {
42 | assertThat(AcceptLanguage
43 | .builder()
44 | .languageRanges(singletonList(null))
45 | .build()).hasToString("");
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/servlet-javax/src/main/java/org/matomo/java/tracking/servlet/MatomoTrackerFilter.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.servlet;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import java.io.IOException;
5 | import javax.servlet.FilterChain;
6 | import javax.servlet.ServletException;
7 | import javax.servlet.http.HttpFilter;
8 | import javax.servlet.http.HttpServletRequest;
9 | import javax.servlet.http.HttpServletResponse;
10 | import lombok.RequiredArgsConstructor;
11 | import lombok.extern.slf4j.Slf4j;
12 | import org.matomo.java.tracking.MatomoRequest;
13 | import org.matomo.java.tracking.MatomoTracker;
14 |
15 | /**
16 | * This filter can be used to automatically send a request to the Matomo server for every request
17 | * that is received by the servlet container.
18 | */
19 | @RequiredArgsConstructor
20 | @Slf4j
21 | public class MatomoTrackerFilter extends HttpFilter {
22 |
23 | private final MatomoTracker tracker;
24 |
25 | @Override
26 | protected void doFilter(@NonNull HttpServletRequest req, @NonNull HttpServletResponse res,
27 | @NonNull FilterChain chain)
28 | throws IOException, ServletException {
29 | MatomoRequest matomoRequest = ServletMatomoRequest
30 | .fromServletRequest(JavaxHttpServletWrapper.fromHttpServletRequest(req)).build();
31 | log.debug("Sending request {}", matomoRequest);
32 | tracker.sendRequestAsync(matomoRequest);
33 | super.doFilter(req, res, chain);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/servlet-jakarta/src/test/java/org/matomo/java/tracking/servlet/JakartaHttpServletWrapperTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.servlet;
2 |
3 | import static java.util.Collections.singletonMap;
4 | import static org.assertj.core.api.Assertions.assertThat;
5 |
6 | import jakarta.servlet.http.Cookie;
7 | import java.util.List;
8 | import org.junit.jupiter.api.Test;
9 |
10 | class JakartaHttpServletWrapperTest {
11 |
12 | @Test
13 | void wrapsHttpServletRequest() {
14 |
15 | MockHttpServletRequest servlet = new MockHttpServletRequest();
16 | servlet.setRequestURL(new StringBuffer("http://localhost"));
17 | servlet.setRemoteUser("remote-user");
18 | servlet.setHeaders(singletonMap("Accept-Language", "en-US,en;q=0.9,de;q=0.8"));
19 | servlet.setCookies(List.of(new Cookie("foo", "bar")));
20 |
21 | HttpServletRequestWrapper httpServletRequestWrapper =
22 | JakartaHttpServletWrapper.fromHttpServletRequest(servlet);
23 |
24 | assertThat(httpServletRequestWrapper.getRequestURL()).hasToString("http://localhost");
25 | assertThat(httpServletRequestWrapper.getRemoteUser()).hasToString("remote-user");
26 | assertThat(httpServletRequestWrapper.getHeaders())
27 | .containsEntry("accept-language", "en-US,en;q=0.9,de;q=0.8");
28 | assertThat(httpServletRequestWrapper.getCookies())
29 | .hasSize(1)
30 | .satisfiesExactly(cookieWrapper -> {
31 | assertThat(cookieWrapper.getName()).isEqualTo("foo");
32 | assertThat(cookieWrapper.getValue()).isEqualTo("bar");
33 | });
34 | }
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, General Electric Company
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 | * Neither the name of the copyright holder nor the
12 | names of its contributors may be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/servlet-javax/src/test/java/org/matomo/java/tracking/servlet/JavaxHttpServletWrapperTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.servlet;
2 |
3 | import static java.util.Collections.singleton;
4 | import static java.util.Collections.singletonMap;
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | import java.util.List;
8 | import javax.servlet.http.Cookie;
9 | import org.junit.jupiter.api.Test;
10 |
11 | class JavaxHttpServletWrapperTest {
12 |
13 | @Test
14 | void wrapsHttpServletRequest() {
15 |
16 | MockHttpServletRequest servlet = new MockHttpServletRequest();
17 | servlet.setRequestURL(new StringBuffer("http://localhost"));
18 | servlet.setRemoteUser("remote-user");
19 | servlet.setHeaders(singletonMap("Accept-Language", "en-US,en;q=0.9,de;q=0.8"));
20 | servlet.setCookies(singleton(new Cookie("foo", "bar")));
21 |
22 | HttpServletRequestWrapper httpServletRequestWrapper =
23 | JavaxHttpServletWrapper.fromHttpServletRequest(servlet);
24 |
25 | assertThat(httpServletRequestWrapper.getRequestURL()).hasToString("http://localhost");
26 | assertThat(httpServletRequestWrapper.getRemoteUser()).hasToString("remote-user");
27 | assertThat(httpServletRequestWrapper.getHeaders())
28 | .containsEntry("accept-language", "en-US,en;q=0.9,de;q=0.8");
29 | assertThat(httpServletRequestWrapper.getCookies())
30 | .hasSize(1)
31 | .satisfiesExactly(cookieWrapper -> {
32 | assertThat(cookieWrapper.getName()).isEqualTo("foo");
33 | assertThat(cookieWrapper.getValue()).isEqualTo("bar");
34 | });
35 | }
36 |
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/test/src/main/java/org/matomo/java/tracking/test/ServletMatomoRequestExample.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.test;
2 |
3 | import jakarta.servlet.http.HttpServletRequest;
4 | import org.matomo.java.tracking.MatomoRequest;
5 | import org.matomo.java.tracking.MatomoRequests;
6 | import org.matomo.java.tracking.MatomoTracker;
7 | import org.matomo.java.tracking.parameters.VisitorId;
8 | import org.matomo.java.tracking.servlet.JakartaHttpServletWrapper;
9 | import org.matomo.java.tracking.servlet.ServletMatomoRequest;
10 |
11 | /**
12 | * This is an example of how to use the ServletMatomoRequest class.
13 | */
14 | public class ServletMatomoRequestExample {
15 |
16 | private final MatomoTracker tracker;
17 |
18 | public ServletMatomoRequestExample(MatomoTracker tracker) {
19 | this.tracker = tracker;
20 | }
21 |
22 | /**
23 | * Example for sending a request from a servlet request.
24 | *
25 | * @param request the servlet request
26 | */
27 | public void someControllerMethod(HttpServletRequest request) {
28 | MatomoRequest matomoRequest = ServletMatomoRequest
29 | .addServletRequestHeaders(
30 | MatomoRequests.contentImpression(
31 | "Latest Product Announced",
32 | "Main Blog Text",
33 | "https://www.yourdomain.com/blog/2018/10/01/new-product-launches"
34 | ),
35 | JakartaHttpServletWrapper.fromHttpServletRequest(request)
36 | ).visitorId(VisitorId.fromString("customer@mail.com"))
37 | // ...
38 | .build();
39 | tracker.sendRequestAsync(matomoRequest);
40 | // ...
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/servlet-jakarta/src/main/java/org/matomo/java/tracking/servlet/MatomoTrackerFilter.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.servlet;
2 |
3 | import jakarta.servlet.FilterChain;
4 | import jakarta.servlet.ServletException;
5 | import jakarta.servlet.http.HttpFilter;
6 | import jakarta.servlet.http.HttpServletRequest;
7 | import jakarta.servlet.http.HttpServletResponse;
8 | import java.io.IOException;
9 | import lombok.RequiredArgsConstructor;
10 | import lombok.extern.slf4j.Slf4j;
11 | import org.matomo.java.tracking.MatomoRequest;
12 | import org.matomo.java.tracking.MatomoTracker;
13 |
14 | /**
15 | * This filter can be used to automatically send a request to the Matomo server for every request
16 | * that is received by the servlet container.
17 | */
18 | @RequiredArgsConstructor
19 | @Slf4j
20 | public class MatomoTrackerFilter extends HttpFilter {
21 |
22 | private final MatomoTracker tracker;
23 |
24 | @Override
25 | protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
26 | throws IOException, ServletException {
27 | MatomoRequest matomoRequest = ServletMatomoRequest
28 | .fromServletRequest(JakartaHttpServletWrapper.fromHttpServletRequest(req)).build();
29 | log.debug("Sending request {}", matomoRequest);
30 | tracker.sendRequestAsync(matomoRequest);
31 | super.doFilter(req, res, chain);
32 | }
33 |
34 | @Override
35 | public void destroy() {
36 | if (tracker != null) {
37 | try {
38 | tracker.close();
39 | } catch (Exception e) {
40 | throw new RuntimeException("Could not close tracker", e);
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/parameters/DeviceResolutionTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
12 |
13 | import org.junit.jupiter.api.Test;
14 |
15 | class DeviceResolutionTest {
16 |
17 | @Test
18 | void formatsDeviceResolution() {
19 |
20 | DeviceResolution deviceResolution = DeviceResolution.builder().width(1280).height(1080).build();
21 |
22 | assertThat(deviceResolution).hasToString("1280x1080");
23 |
24 | }
25 |
26 | @Test
27 | void returnsNullOnNull() {
28 |
29 | DeviceResolution deviceResolution = DeviceResolution.fromString(null);
30 |
31 | assertThat(deviceResolution).isNull();
32 |
33 | }
34 |
35 | @Test
36 | void failsOnWrongDimensionSize() {
37 | assertThatThrownBy(() -> DeviceResolution.fromString("1920x1080x720"))
38 | .isInstanceOf(IllegalArgumentException.class)
39 | .hasMessage("Wrong dimension size");
40 | }
41 |
42 | @Test
43 | void failsIfDeviceResolutionIsTooShort() {
44 | assertThatThrownBy(() -> DeviceResolution.fromString("1"))
45 | .isInstanceOf(IllegalArgumentException.class)
46 | .hasMessage("Wrong device resolution size");
47 | }
48 |
49 | @Test
50 | void returnsNullIfDeviceResolutionIsEmpty() {
51 | assertThat(DeviceResolution.fromString("")).isNull();
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/parameters/RandomValue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import java.security.SecureRandom;
11 | import java.util.Random;
12 |
13 | /**
14 | * A random value to avoid the tracking request being cached by the browser or a proxy.
15 | */
16 | public class RandomValue {
17 |
18 | private static final Random RANDOM = new SecureRandom();
19 |
20 | private final byte[] representation = new byte[10];
21 |
22 | private String override;
23 |
24 | /**
25 | * Static factory to generate a random value.
26 | *
27 | * @return A randomly generated value
28 | */
29 | public static RandomValue random() {
30 | RandomValue randomValue = new RandomValue();
31 | RANDOM.nextBytes(randomValue.representation);
32 | return randomValue;
33 | }
34 |
35 | /**
36 | * Static factory to generate a random value from a given string. The string will be used as is and not hashed.
37 | *
38 | * @param override The string to use as random value
39 | * @return A random value from the given string
40 | */
41 | public static RandomValue fromString(String override) {
42 | RandomValue randomValue = new RandomValue();
43 | randomValue.override = override;
44 | return randomValue;
45 | }
46 |
47 | @Override
48 | public String toString() {
49 | if (override != null) {
50 | return override;
51 | }
52 | return Hex.fromBytes(representation);
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/core/src/main/java/org/piwik/java/tracking/PiwikDate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.piwik.java.tracking;
9 |
10 | import java.time.Instant;
11 | import java.time.ZonedDateTime;
12 | import java.util.TimeZone;
13 | import org.matomo.java.tracking.MatomoDate;
14 |
15 | /**
16 | * A date object that can be used to send dates to Matomo. This class is deprecated and will be removed in a future.
17 | *
18 | * @author brettcsorba
19 | * @deprecated Please use {@link Instant}
20 | */
21 | @Deprecated
22 | public class PiwikDate extends MatomoDate {
23 |
24 | /**
25 | * Creates a new date object with the current time.
26 | *
27 | * @deprecated Use {@link Instant} instead.
28 | */
29 | @Deprecated
30 | public PiwikDate() {
31 | }
32 |
33 | /**
34 | * Creates a new date object with the specified time. The time is specified in milliseconds since the epoch.
35 | *
36 | * @param epochMilli The time in milliseconds since the epoch
37 | * @deprecated Use {@link Instant} instead.
38 | */
39 | @Deprecated
40 | public PiwikDate(long epochMilli) {
41 | super(epochMilli);
42 | }
43 |
44 | /**
45 | * Sets the time zone for this date object. This is used to convert the date to UTC before sending it to Matomo.
46 | *
47 | * @param zone the time zone to use
48 | * @deprecated Use {@link ZonedDateTime#toInstant()} instead.
49 | */
50 | @Deprecated
51 | public void setTimeZone(TimeZone zone) {
52 | setTimeZone(zone.toZoneId());
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/test/src/main/java/org/matomo/java/tracking/test/BulkExample.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.test;
2 |
3 | import java.net.URI;
4 | import org.matomo.java.tracking.MatomoRequests;
5 | import org.matomo.java.tracking.MatomoTracker;
6 | import org.matomo.java.tracking.TrackerConfiguration;
7 | import org.matomo.java.tracking.parameters.VisitorId;
8 |
9 | /**
10 | * Example for sending multiple requests in one bulk request.
11 | */
12 | public class BulkExample {
13 |
14 | /**
15 | * Example for sending multiple requests in one bulk request.
16 | *
17 | * @param args ignored
18 | */
19 | public static void main(String[] args) {
20 |
21 | TrackerConfiguration configuration = TrackerConfiguration
22 | .builder()
23 | .apiEndpoint(URI.create("https://www.yourdomain.com/matomo.php"))
24 | .defaultSiteId(1)
25 | .defaultAuthToken("ee6e3dd9ed1b61f5328cf5978b5a8c71")
26 | .logFailedTracking(true)
27 | .build();
28 |
29 | try (MatomoTracker tracker = new MatomoTracker(configuration)) {
30 | VisitorId visitorId = VisitorId.fromString("customer@mail.com");
31 | tracker.sendBulkRequestAsync(
32 | MatomoRequests.siteSearch("Running shoes", "Running", 120L)
33 | .visitorId(visitorId).build(),
34 | MatomoRequests.pageView("VelocityStride ProX Running Shoes")
35 | .visitorId(visitorId).build(),
36 | MatomoRequests.ecommerceOrder("QXZ-789LMP", 100.0, 124.0, 19.0, 10.0, 5.0)
37 | .visitorId(visitorId)
38 | .build()
39 | );
40 | } catch (Exception e) {
41 | throw new RuntimeException("Could not close tracker", e);
42 | }
43 |
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/test/src/main/java/org/matomo/java/tracking/test/MatomoServletTester.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.test;
2 |
3 | import java.net.URI;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.eclipse.jetty.ee10.servlet.DefaultServlet;
6 | import org.eclipse.jetty.ee10.servlet.FilterHolder;
7 | import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
8 | import org.eclipse.jetty.ee10.servlet.ServletHolder;
9 | import org.eclipse.jetty.server.Server;
10 | import org.matomo.java.tracking.MatomoTracker;
11 | import org.matomo.java.tracking.TrackerConfiguration;
12 | import org.matomo.java.tracking.servlet.MatomoTrackerFilter;
13 |
14 | @Slf4j
15 | class MatomoServletTester {
16 | public static void main(String[] args) throws Exception {
17 |
18 | ServletHolder servletHolder = new ServletHolder("default", new DefaultServlet());
19 | servletHolder.setInitParameter(
20 | "resourceBase",
21 | MatomoServletTester.class.getClassLoader().getResource("web").toExternalForm()
22 | );
23 |
24 | ServletContextHandler context = new ServletContextHandler();
25 | context.setContextPath("/");
26 | context.addServlet(servletHolder, "/");
27 | context.addFilter(new FilterHolder(new MatomoTrackerFilter(new MatomoTracker(
28 | TrackerConfiguration
29 | .builder()
30 | .apiEndpoint(URI.create("http://localhost:8080/matomo.php"))
31 | .defaultSiteId(1)
32 | .defaultAuthToken("ee6e3dd9ed1b61f5328cf5978b5a8c71")
33 | .logFailedTracking(true)
34 | .build()))), "/*", null);
35 |
36 | Server server = new Server(8090);
37 | server.setHandler(context);
38 | server.start();
39 | server.join();
40 |
41 | }
42 | }
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/parameters/UniqueId.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import java.security.SecureRandom;
11 | import java.util.Random;
12 | import java.util.stream.Collectors;
13 | import java.util.stream.IntStream;
14 | import lombok.AccessLevel;
15 | import lombok.RequiredArgsConstructor;
16 |
17 | /**
18 | * A six character unique ID consisting of the characters [0-9a-Z].
19 | */
20 | @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
21 | public final class UniqueId {
22 |
23 | private static final String CHARS =
24 | "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
25 |
26 | private static final Random RANDOM = new SecureRandom();
27 |
28 | private final long value;
29 |
30 | /**
31 | * Static factory to generate a random unique id.
32 | *
33 | * @return A randomly generated unique id
34 | */
35 | public static UniqueId random() {
36 | return fromValue(RANDOM.nextLong());
37 | }
38 |
39 | /**
40 | * Creates a unique id from a number.
41 | *
42 | * @param value A number to create this unique id from
43 | * @return The unique id for the given value
44 | */
45 | public static UniqueId fromValue(long value) {
46 | return new UniqueId(value);
47 | }
48 |
49 | @Override
50 | public String toString() {
51 | return IntStream
52 | .range(0, 6)
53 | .map(i -> (int) (value >> i * 8))
54 | .mapToObj(codePoint -> String.valueOf(CHARS.charAt(Math.abs(codePoint % CHARS.length()))))
55 | .collect(Collectors.joining());
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | org.piwik.java.tracking
6 | matomo-java-tracker-parent
7 | 3.4.1-SNAPSHOT
8 | ../pom.xml
9 |
10 |
11 | matomo-java-tracker-core
12 | jar
13 |
14 | Matomo Java Tracker Core
15 |
16 |
17 |
18 | com.github.spotbugs
19 | spotbugs-annotations
20 |
21 |
22 | org.slf4j
23 | slf4j-api
24 |
25 |
26 | org.projectlombok
27 | lombok
28 | provided
29 |
30 |
31 | org.junit.jupiter
32 | junit-jupiter
33 | test
34 |
35 |
36 | org.assertj
37 | assertj-core
38 | test
39 |
40 |
41 | org.slf4j
42 | slf4j-simple
43 | test
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/test/src/main/java/org/matomo/java/tracking/test/ConsumerExample.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.test;
2 |
3 | import java.net.URI;
4 | import org.matomo.java.tracking.MatomoRequest;
5 | import org.matomo.java.tracking.MatomoRequests;
6 | import org.matomo.java.tracking.MatomoTracker;
7 | import org.matomo.java.tracking.TrackerConfiguration;
8 | import org.matomo.java.tracking.parameters.VisitorId;
9 |
10 | /**
11 | * Example for sending a request and performing an action when the request was sent successfully.
12 | */
13 | public class ConsumerExample {
14 |
15 | /**
16 | * Example for sending a request and performing an action when the request was sent successfully.
17 | *
18 | * @param args ignored
19 | */
20 | public static void main(String[] args) {
21 |
22 | TrackerConfiguration configuration = TrackerConfiguration
23 | .builder()
24 | .apiEndpoint(URI.create("https://www.yourdomain.com/matomo.php"))
25 | .defaultSiteId(1)
26 | .defaultAuthToken("ee6e3dd9ed1b61f5328cf5978b5a8c71")
27 | .logFailedTracking(true)
28 | .build();
29 |
30 | try (MatomoTracker tracker = new MatomoTracker(configuration)) {
31 | MatomoRequest request = MatomoRequests
32 | .event("Training", "Workout completed", "Bench press", 60.0)
33 | .visitorId(VisitorId.fromString("customer@mail.com"))
34 | .build();
35 |
36 | tracker.sendRequestAsync(request)
37 | .thenAccept(req -> System.out.printf("Sent request %s%n", req))
38 | .exceptionally(throwable -> {
39 | System.err.printf("Failed to send request: %s%n", throwable.getMessage());
40 | return null;
41 | });
42 | } catch (Exception e) {
43 | throw new RuntimeException("Could not close tracker", e);
44 | }
45 |
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name-template: 'v$RESOLVED_VERSION'
2 | tag-template: 'v$RESOLVED_VERSION'
3 |
4 | categories:
5 | - title: 💥 Breaking changes
6 | labels:
7 | - breaking
8 | - title: 🚨 Removed
9 | labels:
10 | - removed
11 | - title: 🎉 Major features and improvements
12 | labels:
13 | - major-enhancement
14 | - major-rfe
15 | - title: 🐛 Major bug fixes
16 | labels:
17 | - major-bug
18 | - title: ⚠️ Deprecated
19 | labels:
20 | - deprecated
21 | - title: 🚀 New features and improvements
22 | labels:
23 | - enhancement
24 | - feature
25 | - rfe
26 | - title: 🐛 Bug fixes
27 | labels:
28 | - bug
29 | - fix
30 | - bugfix
31 | - regression
32 | - regression-fix
33 | - title: 🌐 Localization and translation
34 | labels:
35 | - localization
36 | - title: 👷 Changes for developers
37 | labels:
38 | - developer
39 | - title: 📝 Documentation updates
40 | labels:
41 | - documentation
42 | - title: 👻 Maintenance
43 | labels:
44 | - chore
45 | - internal
46 | - maintenance
47 | - title: 🚦 Tests
48 | labels:
49 | - test
50 | - tests
51 | - title: ✍ Other changes
52 | - title: 📦 Dependency updates
53 | labels:
54 | - dependencies
55 | collapse-after: 15
56 |
57 | exclude-labels:
58 | - reverted
59 | - no-changelog
60 | - skip-changelog
61 | - invalid
62 |
63 | template: |
64 | ## Changes
65 |
66 | $CHANGES
67 |
68 | autolabeler:
69 | - label: 'documentation'
70 | files:
71 | - '*.md'
72 | branch:
73 | - '/docs{0,1}\/.+/'
74 | - label: 'bug'
75 | branch:
76 | - '/fix\/.+/'
77 | title:
78 | - '/fix/i'
79 | - label: 'enhancement'
80 | branch:
81 | - '/feature\/.+/'
82 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/ProxyAuthenticatorTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import java.net.Authenticator;
6 | import java.net.Authenticator.RequestorType;
7 | import java.net.InetAddress;
8 | import java.net.PasswordAuthentication;
9 | import java.net.URL;
10 | import org.junit.jupiter.api.Test;
11 |
12 | class ProxyAuthenticatorTest {
13 |
14 | private PasswordAuthentication passwordAuthentication;
15 |
16 | @Test
17 | void createsPasswordAuthentication() throws Exception {
18 |
19 | ProxyAuthenticator proxyAuthenticator = new ProxyAuthenticator("user", "password");
20 | Authenticator.setDefault(proxyAuthenticator);
21 | givenPasswordAuthentication(RequestorType.PROXY);
22 |
23 | assertThat(passwordAuthentication.getUserName()).isEqualTo("user");
24 | assertThat(passwordAuthentication.getPassword()).contains(
25 | 'p',
26 | 'a',
27 | 's',
28 | 's',
29 | 'w',
30 | 'o',
31 | 'r',
32 | 'd'
33 | );
34 |
35 | }
36 |
37 | private void givenPasswordAuthentication(RequestorType proxy) throws Exception {
38 | passwordAuthentication = Authenticator.requestPasswordAuthentication("host",
39 | InetAddress.getLocalHost(),
40 | 8080,
41 | "http",
42 | "prompt",
43 | "https",
44 | new URL("https://www.daniel-heid.de"),
45 | proxy
46 | );
47 | }
48 |
49 | @Test
50 | void returnsNullIfNoPasswordAuthentication() throws Exception {
51 |
52 | ProxyAuthenticator proxyAuthenticator = new ProxyAuthenticator("user", "password");
53 | Authenticator.setDefault(proxyAuthenticator);
54 | givenPasswordAuthentication(RequestorType.SERVER);
55 |
56 | assertThat(passwordAuthentication).isNull();
57 |
58 | }
59 |
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/parameters/DeviceResolution.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import edu.umd.cs.findbugs.annotations.Nullable;
11 | import lombok.Builder;
12 | import lombok.RequiredArgsConstructor;
13 |
14 | /**
15 | * The resolution (width and height) of the user's output device (monitor / phone).
16 | */
17 | @Builder
18 | @RequiredArgsConstructor
19 | public class DeviceResolution {
20 |
21 | private final int width;
22 |
23 | private final int height;
24 |
25 | /**
26 | * Creates a device resolution from a string representation.
27 | *
28 | *
The string must be in the format "widthxheight", e.g. "1920x1080".
29 | *
30 | * @param deviceResolution The string representation of the device resolution, e.g. "1920x1080"
31 | * @return The device resolution representation
32 | */
33 | @Nullable
34 | public static DeviceResolution fromString(
35 | @Nullable
36 | String deviceResolution
37 | ) {
38 | if (deviceResolution == null || deviceResolution.trim().isEmpty()) {
39 | return null;
40 | }
41 | if (deviceResolution.length() < 3) {
42 | throw new IllegalArgumentException("Wrong device resolution size");
43 | }
44 | String[] dimensions = deviceResolution.split("x");
45 | if (dimensions.length != 2) {
46 | throw new IllegalArgumentException("Wrong dimension size");
47 | }
48 | return builder()
49 | .width(Integer.parseInt(dimensions[0]))
50 | .height(Integer.parseInt(dimensions[1]))
51 | .build();
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return String.format("%dx%d", width, height);
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/RequestValidator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking;
9 |
10 |
11 | import edu.umd.cs.findbugs.annotations.Nullable;
12 | import java.time.Instant;
13 | import java.time.temporal.ChronoUnit;
14 | import lombok.NonNull;
15 |
16 | final class RequestValidator {
17 |
18 | private RequestValidator() {
19 | // utility
20 | }
21 |
22 | static void validate(
23 | @NonNull
24 | MatomoRequest request,
25 | @Nullable
26 | CharSequence authToken
27 | ) {
28 |
29 | if (request.getSearchResultsCount() != null && request.getSearchQuery() == null) {
30 | throw new MatomoException("Search query must be set if search results count is set");
31 | }
32 | if (authToken == null) {
33 | if (request.getVisitorLongitude() != null || request.getVisitorLatitude() != null
34 | || request.getVisitorRegion() != null || request.getVisitorCity() != null
35 | || request.getVisitorCountry() != null || request.getVisitorIp() != null) {
36 | throw new MatomoException(
37 | "Auth token must be present if visitor longitude, latitude, region, city, country or IP are set");
38 | }
39 | if (request.getRequestTimestamp() != null && request
40 | .getRequestTimestamp()
41 | .isBefore(Instant.now().minus(4, ChronoUnit.HOURS))) {
42 | throw new MatomoException(
43 | "Auth token must be present if request timestamp is more than four hours ago");
44 | }
45 | } else {
46 | if (authToken.length() != 32) {
47 | throw new IllegalArgumentException("Auth token must be exactly 32 characters long");
48 | }
49 | }
50 | }
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/servlet-javax/src/test/java/org/matomo/java/tracking/TestSender.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import edu.umd.cs.findbugs.annotations.Nullable;
5 | import java.util.ArrayList;
6 | import java.util.Collection;
7 | import java.util.concurrent.CompletableFuture;
8 | import lombok.Getter;
9 | import lombok.RequiredArgsConstructor;
10 |
11 | /**
12 | * A {@link Sender} implementation that does not send anything but stores the requests.
13 | *
14 | *
This class is intended for testing purposes only. It does not send anything to the Matomo server. Instead, it
15 | * stores the requests and queries in collections that can be accessed via {@link #getRequests()}.
16 | */
17 | @RequiredArgsConstructor
18 | @Getter
19 | class TestSender implements Sender {
20 |
21 | private final Collection requests = new ArrayList<>();
22 |
23 | private final TrackerConfiguration trackerConfiguration;
24 |
25 | private final QueryCreator queryCreator;
26 |
27 | @NonNull
28 | @Override
29 | public CompletableFuture sendSingleAsync(@NonNull MatomoRequest request) {
30 | requests.add(request);
31 | return CompletableFuture.completedFuture(request);
32 | }
33 |
34 | @Override
35 | public void sendSingle(@NonNull MatomoRequest request) {
36 | throw new UnsupportedOperationException();
37 | }
38 |
39 | @Override
40 | public void sendBulk(
41 | @NonNull Iterable extends MatomoRequest> requests, @Nullable String overrideAuthToken
42 | ) {
43 | throw new UnsupportedOperationException();
44 | }
45 |
46 | @NonNull
47 | @Override
48 | public CompletableFuture sendBulkAsync(
49 | @NonNull Collection extends MatomoRequest> requests, @Nullable String overrideAuthToken
50 | ) {
51 | throw new UnsupportedOperationException();
52 | }
53 |
54 | @Override
55 | public void close() {
56 | // do nothing
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/servlet-jakarta/src/test/java/org/matomo/java/tracking/TestSender.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import edu.umd.cs.findbugs.annotations.Nullable;
5 | import java.util.ArrayList;
6 | import java.util.Collection;
7 | import java.util.concurrent.CompletableFuture;
8 | import lombok.Getter;
9 | import lombok.RequiredArgsConstructor;
10 |
11 | /**
12 | * A {@link Sender} implementation that does not send anything but stores the requests.
13 | *
14 | * This class is intended for testing purposes only. It does not send anything to the Matomo server. Instead, it
15 | * stores the requests and queries in collections that can be accessed via {@link #getRequests()}.
16 | */
17 | @RequiredArgsConstructor
18 | @Getter
19 | class TestSender implements Sender {
20 |
21 | private final Collection requests = new ArrayList<>();
22 |
23 | private final TrackerConfiguration trackerConfiguration;
24 |
25 | private final QueryCreator queryCreator;
26 |
27 | @NonNull
28 | @Override
29 | public CompletableFuture sendSingleAsync(@NonNull MatomoRequest request) {
30 | requests.add(request);
31 | return CompletableFuture.completedFuture(request);
32 | }
33 |
34 | @Override
35 | public void sendSingle(@NonNull MatomoRequest request) {
36 | throw new UnsupportedOperationException();
37 | }
38 |
39 | @Override
40 | public void sendBulk(
41 | @NonNull Iterable extends MatomoRequest> requests, @Nullable String overrideAuthToken
42 | ) {
43 | throw new UnsupportedOperationException();
44 | }
45 |
46 | @NonNull
47 | @Override
48 | public CompletableFuture sendBulkAsync(
49 | @NonNull Collection extends MatomoRequest> requests, @Nullable String overrideAuthToken
50 | ) {
51 | throw new UnsupportedOperationException();
52 | }
53 |
54 | @Override
55 | public void close() {
56 | // Do nothing
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/parameters/CustomVariable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import lombok.AllArgsConstructor;
11 | import lombok.EqualsAndHashCode;
12 | import lombok.Getter;
13 | import lombok.NonNull;
14 | import lombok.Setter;
15 | import lombok.ToString;
16 |
17 | /**
18 | * A key-value pair that represents custom information. See
19 | * How do I use Custom Variables?
20 | *
21 | * If you are not already using Custom Variables to measure your custom data, Matomo recommends to use the
22 | * Custom Dimensions feature instead.
23 | * There are many advantages of Custom Dimensions over Custom
24 | * variables. Custom variables will be deprecated in the future.
25 | *
26 | * @deprecated Should not be used according to the Matomo FAQ: How do I use Custom Variables?
27 | */
28 | @Getter
29 | @Setter
30 | @AllArgsConstructor
31 | @ToString
32 | @EqualsAndHashCode(exclude = "index")
33 | @Deprecated
34 | public class CustomVariable {
35 |
36 | private int index;
37 |
38 | @NonNull
39 | private String key;
40 |
41 | @NonNull
42 | private String value;
43 |
44 | /**
45 | * Instantiates a new custom variable.
46 | *
47 | * @param key the key of the custom variable (required)
48 | * @param value the value of the custom variable (required)
49 | */
50 | public CustomVariable(@NonNull String key, @NonNull String value) {
51 | this.key = key;
52 | this.value = value;
53 | }
54 | }
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/MatomoRequestBuilder.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import edu.umd.cs.findbugs.annotations.Nullable;
4 | import java.util.Map;
5 | import org.matomo.java.tracking.parameters.AcceptLanguage;
6 |
7 | /**
8 | * The former MatomoRequestBuilder class has been moved to MatomoRequest.MatomoRequestBuilder.
9 | * This class is only here for backwards compatibility.
10 | *
11 | * @deprecated Use {@link MatomoRequest.MatomoRequestBuilder} instead.
12 | */
13 | @Deprecated
14 | public class MatomoRequestBuilder extends MatomoRequest.MatomoRequestBuilder {
15 |
16 |
17 | /**
18 | * Sets the tracking parameter for the accept languages of a user. Only here for backwards
19 | * compatibility.
20 | *
21 | * @param headerAcceptLanguage The accept language header of a user. Must be in the format
22 | * specified in RFC 2616.
23 | * @return This builder
24 | * @deprecated Use {@link MatomoRequest.MatomoRequestBuilder#headerAcceptLanguage(AcceptLanguage)}
25 | * in combination with {@link AcceptLanguage#fromHeader(String)} instead.
26 | */
27 | @Deprecated
28 | public MatomoRequestBuilder headerAcceptLanguage(@Nullable String headerAcceptLanguage) {
29 | headerAcceptLanguage(AcceptLanguage.fromHeader(headerAcceptLanguage));
30 | return this;
31 | }
32 |
33 | /**
34 | * Sets the custom tracking parameters to the given parameters.
35 | *
36 | *
This converts the given map to a map of collections. Only included for backwards
37 | * compatibility.
38 | *
39 | * @param parameters The custom tracking parameters to set
40 | * @return This builder
41 | * @deprecated Use {@link MatomoRequest.MatomoRequestBuilder#additionalParameters(Map)}}
42 | */
43 | @Deprecated
44 | public MatomoRequestBuilder customTrackingParameters(@Nullable Map parameters) {
45 | additionalParameters(parameters);
46 | return this;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/servlet-javax/src/main/java/org/matomo/java/tracking/servlet/JavaxHttpServletWrapper.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.servlet;
2 |
3 | import java.util.LinkedHashMap;
4 | import java.util.List;
5 | import java.util.Locale;
6 | import java.util.Map;
7 | import java.util.stream.Collectors;
8 | import java.util.stream.Stream;
9 | import javax.servlet.http.HttpServletRequest;
10 | import lombok.NonNull;
11 |
12 | /**
13 | * Converts a javax {@link HttpServletRequest} to a {@link HttpServletRequestWrapper}.
14 | */
15 | public final class JavaxHttpServletWrapper {
16 |
17 | private JavaxHttpServletWrapper() {
18 | // utility
19 | }
20 |
21 | /**
22 | * Takes a javax {@link HttpServletRequest} and converts it to a
23 | * {@link HttpServletRequestWrapper}.
24 | *
25 | * @param request the request to convert to a wrapper object (must not be {@code null}).
26 | * @return the wrapper object (never {@code null}).
27 | */
28 | @edu.umd.cs.findbugs.annotations.NonNull
29 | public static HttpServletRequestWrapper fromHttpServletRequest(@NonNull HttpServletRequest request) {
30 | Map headers = new LinkedHashMap<>();
31 | request.getHeaderNames()
32 | .asIterator()
33 | .forEachRemaining(name -> headers.put(name.toLowerCase(Locale.ROOT), request.getHeader(name)));
34 | List cookies = null;
35 | if (request.getCookies() != null) {
36 | cookies = Stream.of(request.getCookies())
37 | .map(cookie -> new CookieWrapper(cookie.getName(), cookie.getValue()))
38 | .collect(Collectors.toList());
39 | }
40 | return HttpServletRequestWrapper
41 | .builder()
42 | .requestURL(request.getRequestURL())
43 | .remoteAddr(request.getRemoteAddr())
44 | .remoteUser(request.getRemoteUser())
45 | .headers(headers)
46 | .cookies(cookies == null ? null : cookies.toArray(new CookieWrapper[0]))
47 | .build();
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/servlet-jakarta/src/main/java/org/matomo/java/tracking/servlet/JakartaHttpServletWrapper.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.servlet;
2 |
3 | import jakarta.servlet.http.HttpServletRequest;
4 | import java.util.LinkedHashMap;
5 | import java.util.List;
6 | import java.util.Locale;
7 | import java.util.Map;
8 | import java.util.stream.Collectors;
9 | import java.util.stream.Stream;
10 | import lombok.NonNull;
11 |
12 | /**
13 | * Converts a Jakarta {@link HttpServletRequest} to a {@link HttpServletRequestWrapper}.
14 | */
15 | public final class JakartaHttpServletWrapper {
16 |
17 | private JakartaHttpServletWrapper() {
18 | // utility
19 | }
20 |
21 | /**
22 | * Takes a Jakarta {@link HttpServletRequest} and converts it to a
23 | * {@link HttpServletRequestWrapper}.
24 | *
25 | * @param request the request to convert to a wrapper object (must not be {@code null}).
26 | * @return the wrapper object (never {@code null}).
27 | */
28 | @edu.umd.cs.findbugs.annotations.NonNull
29 | public static HttpServletRequestWrapper fromHttpServletRequest(@NonNull HttpServletRequest request) {
30 | Map headers = new LinkedHashMap<>();
31 | request.getHeaderNames()
32 | .asIterator()
33 | .forEachRemaining(name -> headers.put(name.toLowerCase(Locale.ROOT), request.getHeader(name)));
34 | List cookies = null;
35 | if (request.getCookies() != null) {
36 | cookies = Stream.of(request.getCookies())
37 | .map(cookie -> new CookieWrapper(cookie.getName(), cookie.getValue()))
38 | .collect(Collectors.toList());
39 | }
40 | return HttpServletRequestWrapper
41 | .builder()
42 | .requestURL(request.getRequestURL())
43 | .remoteAddr(request.getRemoteAddr())
44 | .remoteUser(request.getRemoteUser())
45 | .headers(headers)
46 | .cookies(cookies == null ? null : cookies.toArray(new CookieWrapper[0]))
47 | .build();
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/EcommerceItemTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | class EcommerceItemTest {
8 |
9 | private EcommerceItem ecommerceItem = new EcommerceItem(null, null, null, null, null);
10 |
11 | /**
12 | * Test of constructor, of class EcommerceItem.
13 | */
14 | @Test
15 | void testConstructor() {
16 | EcommerceItem ecommerceItem = new EcommerceItem("sku", "name", "category", 2.0, 2);
17 | assertThat(ecommerceItem.getSku()).isEqualTo("sku");
18 | assertThat(ecommerceItem.getName()).isEqualTo("name");
19 | assertThat(ecommerceItem.getCategory()).isEqualTo("category");
20 | assertThat(ecommerceItem.getPrice()).isEqualTo(2.0);
21 | assertThat(ecommerceItem.getQuantity()).isEqualTo(2);
22 | }
23 |
24 | /**
25 | * Test of getSku method, of class EcommerceItem.
26 | */
27 | @Test
28 | void testGetSku() {
29 | ecommerceItem.setSku("sku");
30 | assertThat(ecommerceItem.getSku()).isEqualTo("sku");
31 | }
32 |
33 | /**
34 | * Test of getName method, of class EcommerceItem.
35 | */
36 | @Test
37 | void testGetName() {
38 | ecommerceItem.setName("name");
39 | assertThat(ecommerceItem.getName()).isEqualTo("name");
40 | }
41 |
42 | /**
43 | * Test of getCategory method, of class EcommerceItem.
44 | */
45 | @Test
46 | void testGetCategory() {
47 | ecommerceItem.setCategory("category");
48 | assertThat(ecommerceItem.getCategory()).isEqualTo("category");
49 | }
50 |
51 | /**
52 | * Test of getPrice method, of class EcommerceItem.
53 | */
54 | @Test
55 | void testGetPrice() {
56 | ecommerceItem.setPrice(2.0);
57 | assertThat(ecommerceItem.getPrice()).isEqualTo(2.0);
58 | }
59 |
60 | /**
61 | * Test of getQuantity method, of class EcommerceItem.
62 | */
63 | @Test
64 | void testGetQuantity() {
65 | ecommerceItem.setQuantity(2);
66 | assertThat(ecommerceItem.getQuantity()).isEqualTo(2);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/TrackingParameterMethod.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking;
9 |
10 | import java.lang.reflect.Method;
11 | import java.util.regex.Pattern;
12 | import lombok.Builder;
13 | import lombok.NonNull;
14 | import lombok.Value;
15 |
16 | @Builder
17 | @Value
18 | class TrackingParameterMethod {
19 |
20 | String parameterName;
21 |
22 | Method method;
23 |
24 | Pattern pattern;
25 |
26 | double min;
27 |
28 | double max;
29 |
30 | int maxLength;
31 |
32 | void validateParameterValue(@NonNull Object parameterValue) {
33 | if (pattern != null && parameterValue instanceof CharSequence && !pattern
34 | .matcher((CharSequence) parameterValue)
35 | .matches()) {
36 | throw new MatomoException(String.format("Invalid value for %s. Must match regex %s",
37 | parameterName,
38 | pattern
39 | ));
40 | }
41 | if (maxLength != 0 && parameterValue.toString().length() > maxLength) {
42 | throw new MatomoException(String.format("Invalid value for %s. Must be less or equal than %d characters",
43 | parameterName,
44 | maxLength
45 | ));
46 | }
47 | if (parameterValue instanceof Number) {
48 | Number number = (Number) parameterValue;
49 | if (number.doubleValue() < min) {
50 | throw new MatomoException(String.format(
51 | "Invalid value for %s. Must be greater or equal than %s",
52 | parameterName,
53 | min % 1 == 0 ? Long.toString((long) min) : min
54 | ));
55 |
56 | }
57 | if (number.doubleValue() > max) {
58 | throw new MatomoException(String.format(
59 | "Invalid value for %s. Must be less or equal than %s",
60 | parameterName,
61 | max % 1 == 0 ? Long.toString((long) max) : max
62 | ));
63 | }
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/servlet/HttpServletRequestWrapper.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.servlet;
2 |
3 | import edu.umd.cs.findbugs.annotations.Nullable;
4 | import java.util.Collections;
5 | import java.util.Enumeration;
6 | import java.util.Locale;
7 | import java.util.Map;
8 | import lombok.Builder;
9 | import lombok.NonNull;
10 | import lombok.Value;
11 |
12 | /**
13 | * Wraps a HttpServletRequest to be compatible with both the Jakarta and the Java EE API.
14 | */
15 | @Builder
16 | @Value
17 | public class HttpServletRequestWrapper {
18 |
19 | @Nullable
20 | StringBuffer requestURL;
21 |
22 | @Nullable
23 | String remoteAddr;
24 |
25 | @Nullable
26 | String remoteUser;
27 |
28 | @Nullable
29 | Map headers;
30 |
31 | @Nullable
32 | CookieWrapper[] cookies;
33 |
34 | /**
35 | * Returns an enumeration of all the header names this request contains. If the request has no
36 | * headers, this method returns an empty enumeration.
37 | *
38 | * @return an enumeration of all the header names sent with this request
39 | */
40 | public Enumeration getHeaderNames() {
41 | return headers == null ? Collections.emptyEnumeration() :
42 | Collections.enumeration(headers.keySet());
43 | }
44 |
45 | /**
46 | * Returns the value of the specified request header as a String. If the request did not include a
47 | * header of the specified name, this method returns null. If there are multiple headers with the
48 | * same name, this method returns the last header in the request. The header name is case
49 | * insensitive. You can use this method with any request header.
50 | *
51 | * @param name a String specifying the header name (case insensitive) - must not be {@code null}.
52 | * @return a String containing the value of the requested header, or null if the request does not
53 | * have a header of that name
54 | */
55 | @Nullable
56 | public String getHeader(@NonNull String name) {
57 | return headers == null ? null : headers.get(name.toLowerCase(Locale.ROOT));
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/test/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | org.piwik.java.tracking
6 | matomo-java-tracker-parent
7 | 3.4.1-SNAPSHOT
8 | ../pom.xml
9 |
10 |
11 | matomo-java-tracker-test
12 |
13 | Matomo Java Tracker Test
14 | Test application for Matomo Java Tracker
15 |
16 |
17 | 11
18 | 11
19 |
20 |
21 |
22 |
23 | org.piwik.java.tracking
24 | matomo-java-tracker-java11
25 | ${project.version}
26 |
27 |
28 | org.piwik.java.tracking
29 | matomo-java-tracker-servlet-jakarta
30 | ${project.version}
31 |
32 |
33 | com.github.javafaker
34 | javafaker
35 | 1.0.2
36 |
37 |
38 | org.projectlombok
39 | lombok
40 | provided
41 |
42 |
43 | org.slf4j
44 | slf4j-simple
45 |
46 |
47 | org.eclipse.jetty.ee10
48 | jetty-ee10-servlet
49 | 12.0.16
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/MatomoDate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking;
9 |
10 | import java.time.Instant;
11 | import java.time.ZoneId;
12 | import java.time.ZoneOffset;
13 | import java.time.ZonedDateTime;
14 | import lombok.Getter;
15 |
16 | /**
17 | * A datetime object that will return the datetime in the format {@code yyyy-MM-dd hh:mm:ss}.
18 | *
19 | * @author brettcsorba
20 | * @deprecated Please use {@link Instant}
21 | */
22 | @Deprecated
23 | @Getter
24 | public class MatomoDate {
25 |
26 | private ZonedDateTime zonedDateTime;
27 |
28 | /**
29 | * Allocates a Date object and initializes it so that it represents the time
30 | * at which it was allocated, measured to the nearest millisecond.
31 | */
32 | @Deprecated
33 | public MatomoDate() {
34 | zonedDateTime = ZonedDateTime.now(ZoneOffset.UTC);
35 | }
36 |
37 | /**
38 | * Allocates a Date object and initializes it to represent the specified number
39 | * of milliseconds since the standard base time known as "the epoch", namely
40 | * January 1, 1970, 00:00:00 GMT.
41 | *
42 | * @param epochMilli the milliseconds since January 1, 1970, 00:00:00 GMT.
43 | */
44 | @Deprecated
45 | public MatomoDate(long epochMilli) {
46 | zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneOffset.UTC);
47 | }
48 |
49 | /**
50 | * Sets the time zone of the String that will be returned by {@link #toString()}.
51 | * Defaults to UTC.
52 | *
53 | * @param zone the TimeZone to set
54 | */
55 | public void setTimeZone(ZoneId zone) {
56 | zonedDateTime = zonedDateTime.withZoneSameInstant(zone);
57 | }
58 |
59 | /**
60 | * Converts this datetime to the number of milliseconds from the epoch
61 | * of 1970-01-01T00:00:00Z.
62 | *
63 | * @return the number of milliseconds since the epoch of 1970-01-01T00:00:00Z
64 | * @throws ArithmeticException if numeric overflow occurs
65 | */
66 | public long getTime() {
67 | return zonedDateTime.toInstant().toEpochMilli();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/test/src/main/java/org/matomo/java/tracking/test/EcommerceExample.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.test;
2 |
3 | import java.net.URI;
4 | import org.matomo.java.tracking.MatomoRequests;
5 | import org.matomo.java.tracking.MatomoTracker;
6 | import org.matomo.java.tracking.TrackerConfiguration;
7 | import org.matomo.java.tracking.parameters.EcommerceItem;
8 | import org.matomo.java.tracking.parameters.EcommerceItems;
9 | import org.matomo.java.tracking.parameters.VisitorId;
10 |
11 | /**
12 | * Example for sending an ecommerce request.
13 | */
14 | public class EcommerceExample {
15 |
16 | /**
17 | * Example for sending an ecommerce request.
18 | *
19 | * @param args ignored
20 | */
21 | public static void main(String[] args) {
22 |
23 | TrackerConfiguration configuration = TrackerConfiguration
24 | .builder()
25 | .apiEndpoint(URI.create("https://www.yourdomain.com/matomo.php"))
26 | .defaultSiteId(1)
27 | .defaultAuthToken("ee6e3dd9ed1b61f5328cf5978b5a8c71")
28 | .logFailedTracking(true)
29 | .build();
30 |
31 | try (MatomoTracker tracker = new MatomoTracker(configuration)) {
32 | tracker.sendBulkRequestAsync(MatomoRequests
33 | .ecommerceCartUpdate(50.0)
34 | .ecommerceItems(EcommerceItems
35 | .builder()
36 | .item(EcommerceItem
37 | .builder()
38 | .sku("XYZ12345")
39 | .name("Matomo - The big book about web analytics")
40 | .category("Education & Teaching")
41 | .price(23.1)
42 | .quantity(2)
43 | .build())
44 | .item(EcommerceItem
45 | .builder()
46 | .sku("B0C2WV3MRJ")
47 | .name("Matomo for data visualization")
48 | .category("Education & Teaching")
49 | .price(15.0)
50 | .quantity(1)
51 | .build())
52 | .build())
53 | .visitorId(VisitorId.fromString("customer@mail.com"))
54 | .build()
55 | );
56 | } catch (Exception e) {
57 | throw new RuntimeException("Could not close tracker", e);
58 | }
59 |
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/parameters/CustomVariableTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 | import static org.assertj.core.api.Assertions.fail;
12 |
13 | import org.junit.jupiter.api.BeforeEach;
14 | import org.junit.jupiter.api.Test;
15 |
16 | class CustomVariableTest {
17 |
18 | private CustomVariable customVariable;
19 |
20 | @BeforeEach
21 | void setUp() {
22 | customVariable = new CustomVariable("key", "value");
23 | }
24 |
25 | @Test
26 | void testConstructorNullKey() {
27 | try {
28 | new CustomVariable(null, null);
29 | fail("Exception should have been throw.");
30 | } catch (NullPointerException e) {
31 | assertThat(e.getLocalizedMessage()).isEqualTo("key is marked non-null but is null");
32 | }
33 | }
34 |
35 | @Test
36 | void testConstructorNullValue() {
37 | try {
38 | new CustomVariable("key", null);
39 | fail("Exception should have been throw.");
40 | } catch (NullPointerException e) {
41 | assertThat(e.getLocalizedMessage()).isEqualTo("value is marked non-null but is null");
42 | }
43 | }
44 |
45 | @Test
46 | void testGetKey() {
47 | assertThat(customVariable.getKey()).isEqualTo("key");
48 | }
49 |
50 | @Test
51 | void testGetValue() {
52 | assertThat(customVariable.getValue()).isEqualTo("value");
53 | }
54 |
55 | @Test
56 | void equalsCustomVariable() {
57 | CustomVariable variableA = new CustomVariable("a", "b");
58 | CustomVariable variableB = new CustomVariable("a", "b");
59 | assertThat(variableA).isEqualTo(variableB);
60 | assertThat(variableA.hashCode()).isEqualTo(variableB.hashCode());
61 | CustomVariable c = new CustomVariable("a", "c");
62 | assertThat(variableA).isNotEqualTo(c);
63 | assertThat(variableA.hashCode()).isNotEqualTo(c.hashCode());
64 | CustomVariable d = new CustomVariable("d", "b");
65 | assertThat(variableA).isNotEqualTo(d);
66 | assertThat(variableA.hashCode()).isNotEqualTo(d.hashCode());
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/spring/src/main/java/org/matomo/java/tracking/spring/StandardTrackerConfigurationBuilderCustomizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.spring;
9 |
10 | import java.net.URI;
11 | import org.matomo.java.tracking.TrackerConfiguration;
12 | import org.springframework.boot.context.properties.PropertyMapper;
13 | import org.springframework.core.Ordered;
14 | import org.springframework.lang.NonNull;
15 |
16 | class StandardTrackerConfigurationBuilderCustomizer implements TrackerConfigurationBuilderCustomizer, Ordered {
17 |
18 | private final MatomoTrackerProperties properties;
19 |
20 | StandardTrackerConfigurationBuilderCustomizer(MatomoTrackerProperties properties) {
21 | this.properties = properties;
22 | }
23 |
24 | @Override
25 | public int getOrder() {
26 | return 0;
27 | }
28 |
29 | @Override
30 | public void customize(@NonNull TrackerConfiguration.TrackerConfigurationBuilder builder) {
31 | PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
32 | map.from(properties::getApiEndpoint).as(URI::create).to(builder::apiEndpoint);
33 | map.from(properties::getDefaultSiteId).to(builder::defaultSiteId);
34 | map.from(properties::getDefaultAuthToken).to(builder::defaultAuthToken);
35 | map.from(properties::getEnabled).to(builder::enabled);
36 | map.from(properties::getConnectTimeout).to(builder::connectTimeout);
37 | map.from(properties::getSocketTimeout).to(builder::socketTimeout);
38 | map.from(properties::getProxyHost).to(builder::proxyHost);
39 | map.from(properties::getProxyPort).to(builder::proxyPort);
40 | map.from(properties::getProxyUsername).to(builder::proxyUsername);
41 | map.from(properties::getProxyPassword).to(builder::proxyPassword);
42 | map.from(properties::getUserAgent).to(builder::userAgent);
43 | map.from(properties::getLogFailedTracking).to(builder::logFailedTracking);
44 | map.from(properties::getDisableSslCertValidation).to(builder::disableSslCertValidation);
45 | map.from(properties::getDisableSslHostVerification).to(builder::disableSslHostVerification);
46 | map.from(properties::getThreadPoolSize).to(builder::threadPoolSize);
47 | }
48 |
49 |
50 | }
--------------------------------------------------------------------------------
/servlet-jakarta/src/test/java/org/matomo/java/tracking/MatomoTrackerFilterIT.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import java.net.URI;
6 | import java.net.http.HttpClient;
7 | import java.net.http.HttpRequest;
8 | import java.net.http.HttpResponse;
9 | import org.eclipse.jetty.ee10.servlet.FilterHolder;
10 | import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
11 | import org.eclipse.jetty.server.Server;
12 | import org.junit.jupiter.api.Test;
13 | import org.matomo.java.tracking.servlet.MatomoTrackerFilter;
14 |
15 | class MatomoTrackerFilterIT {
16 |
17 | @Test
18 | void sendsAnAsyncRequestOnFilter() throws Exception {
19 |
20 |
21 | TestSenderFactory senderFactory = new TestSenderFactory();
22 |
23 | MatomoTracker tracker = new MatomoTracker(
24 | TrackerConfiguration
25 | .builder()
26 | .apiEndpoint(URI.create("http://localhost:8080/matomo.php"))
27 | .defaultSiteId(1)
28 | .defaultAuthToken("ee6e3dd9ed1b61f5328cf5978b5a8c71")
29 | .logFailedTracking(true)
30 | .build());
31 | tracker.setSenderFactory(senderFactory);
32 |
33 | ServletContextHandler context = new ServletContextHandler();
34 | context.setContextPath("/");
35 | context.addFilter(new FilterHolder(new MatomoTrackerFilter(tracker)), "/*", null);
36 | Server server = new Server(0);
37 | server.setHandler(context);
38 |
39 | server.start();
40 | URI uri = server.getURI();
41 | HttpClient.newHttpClient().send(
42 | HttpRequest.newBuilder()
43 | .header("Accept-Language", "en-US,en;q=0.9,de;q=0.8")
44 | .uri(uri)
45 | .build(),
46 | HttpResponse.BodyHandlers.discarding()
47 | );
48 | server.stop();
49 |
50 | TestSender testSender = senderFactory.getTestSender();
51 | assertThat(testSender.getRequests()).hasSize(1).satisfiesExactly(matomoRequest -> {
52 | assertThat(matomoRequest.getActionUrl()).isEqualTo(uri.toString());
53 | assertThat(matomoRequest.getVisitorId()).isNotNull();
54 | assertThat(matomoRequest.getVisitorIp()).isNotNull();
55 | assertThat(matomoRequest.getHeaders()).containsEntry(
56 | "accept-language",
57 | "en-US,en;q=0.9,de;q=0.8"
58 | );
59 | });
60 |
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/servlet-javax/src/test/java/org/matomo/java/tracking/MatomoTrackerFilterIT.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import java.net.URI;
6 | import java.net.http.HttpClient;
7 | import java.net.http.HttpRequest;
8 | import java.net.http.HttpResponse;
9 | import org.eclipse.jetty.server.Server;
10 | import org.eclipse.jetty.servlet.FilterHolder;
11 | import org.eclipse.jetty.servlet.ServletContextHandler;
12 | import org.junit.jupiter.api.Test;
13 | import org.matomo.java.tracking.servlet.MatomoTrackerFilter;
14 |
15 | class MatomoTrackerFilterIT {
16 |
17 | @Test
18 | void sendsAnAsyncRequestOnFilter() throws Exception {
19 |
20 |
21 | TestSenderFactory senderFactory = new TestSenderFactory();
22 |
23 | MatomoTracker tracker = new MatomoTracker(
24 | TrackerConfiguration
25 | .builder()
26 | .apiEndpoint(URI.create("http://localhost:8080/matomo.php"))
27 | .defaultSiteId(1)
28 | .defaultAuthToken("ee6e3dd9ed1b61f5328cf5978b5a8c71")
29 | .logFailedTracking(true)
30 | .build());
31 | tracker.setSenderFactory(senderFactory);
32 |
33 | ServletContextHandler context = new ServletContextHandler();
34 | context.setContextPath("/");
35 | context.addFilter(new FilterHolder(new MatomoTrackerFilter(tracker)), "/*", null);
36 | Server server = new Server(0);
37 | server.setHandler(context);
38 |
39 | server.start();
40 | URI uri = server.getURI();
41 | HttpClient.newHttpClient().send(
42 | HttpRequest.newBuilder()
43 | .header("Accept-Language", "en-US,en;q=0.9,de;q=0.8")
44 | .uri(uri)
45 | .build(),
46 | HttpResponse.BodyHandlers.discarding()
47 | );
48 | server.stop();
49 |
50 | TestSender testSender = senderFactory.getTestSender();
51 | assertThat(testSender.getRequests()).hasSize(1).satisfiesExactly(matomoRequest -> {
52 | assertThat(matomoRequest.getActionUrl()).isEqualTo(uri.toString());
53 | assertThat(matomoRequest.getVisitorId()).isNotNull();
54 | assertThat(matomoRequest.getVisitorIp()).isNotNull();
55 | assertThat(matomoRequest.getHeaders()).containsEntry(
56 | "accept-language",
57 | "en-US,en;q=0.9,de;q=0.8"
58 | );
59 | });
60 |
61 | tracker.close();
62 |
63 | }
64 |
65 | }
--------------------------------------------------------------------------------
/java8/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | org.piwik.java.tracking
6 | matomo-java-tracker-parent
7 | 3.4.1-SNAPSHOT
8 | ../pom.xml
9 |
10 |
11 | matomo-java-tracker
12 | 3.4.1-SNAPSHOT
13 | jar
14 |
15 | Matomo Java Tracker Java 8
16 | Official Java implementation of the Matomo Tracking HTTP API for Java 8.
17 |
18 |
19 |
20 | org.piwik.java.tracking
21 | matomo-java-tracker-core
22 | ${project.version}
23 |
24 |
25 | org.slf4j
26 | slf4j-api
27 |
28 |
29 | com.github.spotbugs
30 | spotbugs-annotations
31 |
32 |
33 | org.projectlombok
34 | lombok
35 | provided
36 |
37 |
38 | org.junit.jupiter
39 | junit-jupiter
40 | test
41 |
42 |
43 | org.assertj
44 | assertj-core
45 | test
46 |
47 |
48 | com.github.tomakehurst
49 | wiremock-standalone
50 | 2.27.2
51 | test
52 |
53 |
54 | org.slf4j
55 | slf4j-simple
56 | test
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/parameters/AcceptLanguage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import edu.umd.cs.findbugs.annotations.NonNull;
11 | import edu.umd.cs.findbugs.annotations.Nullable;
12 | import java.util.List;
13 | import java.util.Locale.LanguageRange;
14 | import java.util.Objects;
15 | import java.util.stream.Collectors;
16 | import lombok.Builder;
17 | import lombok.Singular;
18 | import lombok.Value;
19 |
20 | /**
21 | * Describes the content for the Accept-Language header field that can be overridden by a custom parameter. The format
22 | * is specified in the corresponding RFC 4647 Matching of Language Tags
23 | *
24 | * Example: "en-US,en;q=0.8,de;q=0.6"
25 | */
26 | @Builder
27 | @Value
28 | public class AcceptLanguage {
29 |
30 | @Singular
31 | List languageRanges;
32 |
33 | /**
34 | * Creates the Accept-Language definition for a given header.
35 | *
36 | * Please see {@link LanguageRange#parse(String)} for more information. Example: "en-US,en;q=0.8,de;q=0.6"
37 | *
38 | * @param header A header that can be null
39 | * @return The parsed header (probably reformatted). null if the header is null.
40 | * @see LanguageRange#parse(String)
41 | */
42 | @Nullable
43 | public static AcceptLanguage fromHeader(
44 | @Nullable
45 | String header
46 | ) {
47 | if (header == null || header.trim().isEmpty()) {
48 | return null;
49 | }
50 | return new AcceptLanguage(LanguageRange.parse(header));
51 | }
52 |
53 | /**
54 | * Returns the Accept Language header value.
55 | *
56 | * @return The header value, e.g. "en-US,en;q=0.8,de;q=0.6"
57 | */
58 | @NonNull
59 | public String toString() {
60 | return languageRanges
61 | .stream()
62 | .filter(Objects::nonNull)
63 | .map(AcceptLanguage::format)
64 | .collect(Collectors.joining(","));
65 | }
66 |
67 | private static String format(
68 | @NonNull
69 | LanguageRange languageRange
70 | ) {
71 | return languageRange.getWeight() == LanguageRange.MAX_WEIGHT ? languageRange.getRange() :
72 | String.format("%s;q=%s", languageRange.getRange(), languageRange.getWeight());
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/AuthTokenTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static java.util.Collections.singleton;
4 | import static org.assertj.core.api.Assertions.assertThat;
5 |
6 | import java.net.URI;
7 | import org.junit.jupiter.api.Test;
8 |
9 | class AuthTokenTest {
10 |
11 | @Test
12 | void determineAuthTokenReturnsAuthTokenFromRequest() {
13 |
14 | MatomoRequest request =
15 | MatomoRequests
16 | .event("Inbox", "Open", null, null)
17 | .authToken("bdeca231a312ab12cde124131bedfa23").build();
18 |
19 | String authToken = AuthToken.determineAuthToken(null, singleton(request), null);
20 |
21 | assertThat(authToken).isEqualTo("bdeca231a312ab12cde124131bedfa23");
22 |
23 | }
24 |
25 | @Test
26 | void determineAuthTokenReturnsAuthTokenFromTrackerConfiguration() {
27 |
28 | TrackerConfiguration trackerConfiguration = TrackerConfiguration
29 | .builder()
30 | .apiEndpoint(URI.create("https://your-matomo-domain.example/matomo."))
31 | .defaultAuthToken("bdeca231a312ab12cde124131bedfa23")
32 | .build();
33 |
34 | String authToken = AuthToken.determineAuthToken(null, null, trackerConfiguration);
35 |
36 | assertThat(authToken).isEqualTo("bdeca231a312ab12cde124131bedfa23");
37 | }
38 |
39 | @Test
40 | void determineAuthTokenFromTrackerConfigurationIfRequestTokenIsEmpty() {
41 |
42 | MatomoRequest request = MatomoRequests.ping().authToken("").build();
43 |
44 | TrackerConfiguration trackerConfiguration = TrackerConfiguration
45 | .builder()
46 | .apiEndpoint(URI.create("https://your-matomo-domain.example/matomo"))
47 | .defaultAuthToken("bdeca231a312ab12cde124131bedfa23")
48 | .build();
49 |
50 | String authToken = AuthToken.determineAuthToken(null, singleton(request), trackerConfiguration);
51 |
52 | assertThat(authToken).isEqualTo("bdeca231a312ab12cde124131bedfa23");
53 |
54 | }
55 |
56 | @Test
57 | void determineAuthTokenFromTrackerConfigurationIfRequestTokenIsBlank() {
58 |
59 | MatomoRequest request = MatomoRequests.pageView("Help").authToken(" ").build();
60 |
61 | TrackerConfiguration trackerConfiguration = TrackerConfiguration
62 | .builder()
63 | .apiEndpoint(URI.create("https://your-matomo-domain.example/matomo"))
64 | .defaultAuthToken("bdeca231a312ab12cde124131bedfa23")
65 | .build();
66 |
67 | String authToken = AuthToken.determineAuthToken(null, singleton(request), trackerConfiguration);
68 |
69 | assertThat(authToken).isEqualTo("bdeca231a312ab12cde124131bedfa23");
70 |
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/core/src/main/java/org/piwik/java/tracking/PiwikTracker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.piwik.java.tracking;
9 |
10 | import edu.umd.cs.findbugs.annotations.NonNull;
11 | import edu.umd.cs.findbugs.annotations.Nullable;
12 | import org.matomo.java.tracking.MatomoTracker;
13 |
14 | /**
15 | * Creates a new PiwikTracker instance. This class is deprecated and will be removed in the future.
16 | *
17 | * @author brettcsorba
18 | * @deprecated Use {@link MatomoTracker} instead.
19 | */
20 | @Deprecated
21 | public class PiwikTracker extends MatomoTracker {
22 |
23 | /**
24 | * Creates a new PiwikTracker instance with the given host URL.
25 | *
26 | * @param hostUrl the host URL of the Matomo server
27 | * @deprecated Use {@link MatomoTracker} instead.
28 | */
29 | @Deprecated
30 | public PiwikTracker(@NonNull String hostUrl) {
31 | super(hostUrl);
32 | }
33 |
34 | /**
35 | * Creates a new PiwikTracker instance with the given host URL and timeout in milliseconds. Use -1 for no timeout.
36 | *
37 | * @param hostUrl the host URL of the Matomo server
38 | * @param timeout the timeout in milliseconds or -1 for no timeout
39 | * @deprecated Use {@link MatomoTracker} instead.
40 | */
41 | @Deprecated
42 | public PiwikTracker(@NonNull String hostUrl, int timeout) {
43 | super(hostUrl, timeout);
44 | }
45 |
46 | /**
47 | * Creates a new PiwikTracker instance with the given host URL and proxy settings.
48 | *
49 | * @param hostUrl the host URL of the Matomo server
50 | * @param proxyHost the proxy host
51 | * @param proxyPort the proxy port
52 | * @deprecated Use {@link MatomoTracker} instead.
53 | */
54 | @Deprecated
55 | public PiwikTracker(@NonNull String hostUrl, @Nullable String proxyHost, int proxyPort) {
56 | super(hostUrl, proxyHost, proxyPort);
57 | }
58 |
59 | /**
60 | * Creates a new PiwikTracker instance with the given host URL, proxy settings and timeout in milliseconds. Use -1 for
61 | * no timeout.
62 | *
63 | * @param hostUrl the host URL of the Matomo server
64 | * @param proxyHost the proxy host
65 | * @param proxyPort the proxy port
66 | * @param timeout the timeout in milliseconds or -1 for no timeout
67 | * @deprecated Use {@link MatomoTracker} instead.
68 | */
69 | @Deprecated
70 | public PiwikTracker(@NonNull String hostUrl, @Nullable String proxyHost, int proxyPort, int timeout) {
71 | super(hostUrl, proxyHost, proxyPort, timeout);
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/TestSender.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static java.util.Collections.singleton;
4 |
5 | import edu.umd.cs.findbugs.annotations.NonNull;
6 | import edu.umd.cs.findbugs.annotations.Nullable;
7 | import java.util.ArrayList;
8 | import java.util.Collection;
9 | import java.util.concurrent.CompletableFuture;
10 | import lombok.Getter;
11 | import lombok.RequiredArgsConstructor;
12 |
13 | /**
14 | * A {@link Sender} implementation that does not send anything but stores the requests and queries.
15 | *
16 | *
This class is intended for testing purposes only. It does not send anything to the Matomo server. Instead, it
17 | * stores the requests and queries in collections that can be accessed via {@link #getRequests()} and {@link
18 | * #getQueries()}.
19 | */
20 | @RequiredArgsConstructor
21 | @Getter
22 | class TestSender implements Sender {
23 |
24 | private final Collection requests = new ArrayList<>();
25 |
26 | private final Collection queries = new ArrayList<>();
27 |
28 | private final TrackerConfiguration trackerConfiguration;
29 |
30 | private final QueryCreator queryCreator;
31 |
32 | @NonNull
33 | @Override
34 | public CompletableFuture sendSingleAsync(@NonNull MatomoRequest request) {
35 | createQueryAndAddRequest(request, null);
36 | return CompletableFuture.completedFuture(request);
37 | }
38 |
39 | @Override
40 | public void sendSingle(@NonNull MatomoRequest request) {
41 | createQueryAndAddRequest(request, null);
42 | }
43 |
44 | @Override
45 | public void sendBulk(
46 | @NonNull Iterable extends MatomoRequest> requests, @Nullable String overrideAuthToken
47 | ) {
48 | for (MatomoRequest request : requests) {
49 | createQueryAndAddRequest(request, overrideAuthToken);
50 | }
51 | }
52 |
53 | @NonNull
54 | @Override
55 | public CompletableFuture sendBulkAsync(
56 | @NonNull Collection extends MatomoRequest> requests, @Nullable String overrideAuthToken
57 | ) {
58 | for (MatomoRequest request : requests) {
59 | createQueryAndAddRequest(request, overrideAuthToken);
60 | }
61 | return CompletableFuture.completedFuture(null);
62 | }
63 |
64 |
65 |
66 | private void createQueryAndAddRequest(@lombok.NonNull MatomoRequest request, @Nullable String overrideAuthToken) {
67 | String authToken = AuthToken.determineAuthToken(overrideAuthToken, singleton(request), trackerConfiguration);
68 | queries.add(queryCreator.createQuery(request, authToken));
69 | requests.add(request);
70 | }
71 |
72 | @Override
73 | public void close() {
74 | // do nothing
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/servlet-javax/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | org.piwik.java.tracking
6 | matomo-java-tracker-parent
7 | 3.4.1-SNAPSHOT
8 | ../pom.xml
9 |
10 |
11 | matomo-java-tracker-servlet-javax
12 | 3.4.1-SNAPSHOT
13 | jar
14 |
15 | Matomo Java Tracker Servlet Javax
16 | Integrates Matomo Java Tracker into your javax servlet based application
17 |
18 |
19 | 1.8
20 | 1.8
21 |
22 |
23 |
24 |
25 | org.piwik.java.tracking
26 | matomo-java-tracker
27 | ${project.version}
28 |
29 |
30 | javax.servlet
31 | javax.servlet-api
32 | 4.0.1
33 | provided
34 |
35 |
36 | org.slf4j
37 | slf4j-api
38 |
39 |
40 | com.github.spotbugs
41 | spotbugs-annotations
42 |
43 |
44 | org.projectlombok
45 | lombok
46 | provided
47 |
48 |
49 | org.junit.jupiter
50 | junit-jupiter
51 | test
52 |
53 |
54 | org.eclipse.jetty
55 | jetty-servlet
56 | 11.0.0
57 | test
58 |
59 |
60 | org.assertj
61 | assertj-core
62 | test
63 |
64 |
65 | org.slf4j
66 | slf4j-simple
67 | test
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/servlet-jakarta/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | org.piwik.java.tracking
6 | matomo-java-tracker-parent
7 | 3.4.1-SNAPSHOT
8 | ../pom.xml
9 |
10 |
11 | matomo-java-tracker-servlet-jakarta
12 | 3.4.1-SNAPSHOT
13 | jar
14 |
15 | Matomo Java Tracker Servlet Jakarta
16 | Integrates Matomo Java Tracker into your Jakarta servlet based application
17 |
18 |
19 | 11
20 | 11
21 |
22 |
23 |
24 |
25 | org.piwik.java.tracking
26 | matomo-java-tracker-java11
27 | ${project.version}
28 |
29 |
30 | jakarta.servlet
31 | jakarta.servlet-api
32 | 6.1.0
33 | provided
34 |
35 |
36 | org.slf4j
37 | slf4j-api
38 |
39 |
40 | com.github.spotbugs
41 | spotbugs-annotations
42 |
43 |
44 | org.projectlombok
45 | lombok
46 | provided
47 |
48 |
49 | org.junit.jupiter
50 | junit-jupiter
51 | test
52 |
53 |
54 | org.eclipse.jetty.ee10
55 | jetty-ee10-servlet
56 | 12.0.16
57 | test
58 |
59 |
60 | org.assertj
61 | assertj-core
62 | test
63 |
64 |
65 | org.slf4j
66 | slf4j-simple
67 | test
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/spring/src/test/java/org/matomo/java/tracking/spring/MatomoTrackerAutoConfigurationIT.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.spring;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import java.net.URI;
6 | import java.time.Duration;
7 | import org.junit.jupiter.api.Test;
8 | import org.matomo.java.tracking.MatomoTracker;
9 | import org.matomo.java.tracking.TrackerConfiguration;
10 | import org.springframework.boot.autoconfigure.AutoConfigurations;
11 | import org.springframework.boot.test.context.runner.ApplicationContextRunner;
12 | import org.springframework.context.annotation.Bean;
13 | import org.springframework.context.annotation.Configuration;
14 |
15 | class MatomoTrackerAutoConfigurationIT {
16 |
17 | private final ApplicationContextRunner contextRunner =
18 | new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(MatomoTrackerAutoConfiguration.class));
19 |
20 |
21 | @Test
22 | void matomoTrackerRegistration() {
23 | contextRunner.withPropertyValues("matomo.tracker.api-endpoint:https://test.com/matomo.php").run(context -> {
24 | assertThat(context).hasSingleBean(MatomoTracker.class).hasBean("matomoTracker");
25 | });
26 | }
27 |
28 | @Test
29 | void additionalTrackerConfigurationBuilderCustomization() {
30 | this.contextRunner
31 | .withPropertyValues("matomo.tracker.api-endpoint:https://test.com/matomo.php")
32 | .withUserConfiguration(TrackerConfigurationBuilderCustomizerConfig.class)
33 | .run(context -> {
34 | TrackerConfiguration trackerConfiguration = context.getBean(TrackerConfiguration.class);
35 | assertThat(trackerConfiguration.getConnectTimeout()).isEqualTo(Duration.ofMinutes(1L));
36 | });
37 | }
38 |
39 | @Test
40 | void customTrackerConfigurationBuilder() {
41 | this.contextRunner
42 | .withPropertyValues("matomo.tracker.api-endpoint:https://test.com/matomo.php")
43 | .withUserConfiguration(TrackerConfigurationBuilderConfig.class)
44 | .run(context -> {
45 | TrackerConfiguration trackerConfiguration = context.getBean(TrackerConfiguration.class);
46 | assertThat(trackerConfiguration.isDisableSslHostVerification()).isTrue();
47 | });
48 | }
49 |
50 | @Configuration
51 | static class TrackerConfigurationBuilderCustomizerConfig {
52 |
53 | @Bean
54 | TrackerConfigurationBuilderCustomizer customConnectTimeout() {
55 | return configurationBuilder -> configurationBuilder.connectTimeout(Duration.ofMinutes(1L));
56 | }
57 |
58 | }
59 |
60 | @Configuration
61 | static class TrackerConfigurationBuilderConfig {
62 |
63 | @Bean
64 | TrackerConfiguration.TrackerConfigurationBuilder customTrackerConfigurationBuilder() {
65 | return TrackerConfiguration.builder().apiEndpoint(URI.create("https://test.com/matomo.php")).disableSslHostVerification(true);
66 | }
67 |
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/spring/src/test/java/org/matomo/java/tracking/spring/StandardTrackerConfigurationBuilderCustomizerIT.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.spring;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import java.time.Duration;
6 | import org.junit.jupiter.api.Test;
7 | import org.matomo.java.tracking.TrackerConfiguration;
8 |
9 | class StandardTrackerConfigurationBuilderCustomizerIT {
10 |
11 | @Test
12 | void createsStandardTrackerConfigurationBuilderCustomizer() {
13 | MatomoTrackerProperties properties = new MatomoTrackerProperties();
14 | properties.setApiEndpoint("https://test.com/matomo.php");
15 | properties.setDefaultSiteId(1);
16 | properties.setDefaultAuthToken("abc123def4563123abc123def4563123");
17 | properties.setEnabled(true);
18 | properties.setConnectTimeout(Duration.ofMinutes(1L));
19 | properties.setSocketTimeout(Duration.ofMinutes(2L));
20 | properties.setProxyHost("proxy.example.com");
21 | properties.setProxyPort(8080);
22 | properties.setProxyUsername("user");
23 | properties.setProxyPassword("password");
24 | properties.setUserAgent("Mozilla/5.0 (compatible; AcmeInc/1.0; +https://example.com/bot.html)");
25 | properties.setLogFailedTracking(true);
26 | properties.setDisableSslCertValidation(true);
27 | properties.setDisableSslHostVerification(true);
28 | properties.setThreadPoolSize(10);
29 | StandardTrackerConfigurationBuilderCustomizer customizer =
30 | new StandardTrackerConfigurationBuilderCustomizer(properties);
31 | TrackerConfiguration.TrackerConfigurationBuilder builder = TrackerConfiguration.builder();
32 |
33 | customizer.customize(builder);
34 |
35 | assertThat(customizer.getOrder()).isZero();
36 | TrackerConfiguration configuration = builder.build();
37 | assertThat(configuration.getApiEndpoint()).hasToString("https://test.com/matomo.php");
38 | assertThat(configuration.getDefaultSiteId()).isEqualTo(1);
39 | assertThat(configuration.getDefaultAuthToken()).isEqualTo("abc123def4563123abc123def4563123");
40 | assertThat(configuration.isEnabled()).isTrue();
41 | assertThat(configuration.getConnectTimeout()).hasSeconds(60L);
42 | assertThat(configuration.getSocketTimeout()).hasSeconds(120L);
43 | assertThat(configuration.getProxyHost()).isEqualTo("proxy.example.com");
44 | assertThat(configuration.getProxyPort()).isEqualTo(8080);
45 | assertThat(configuration.getProxyUsername()).isEqualTo("user");
46 | assertThat(configuration.getProxyPassword()).isEqualTo("password");
47 | assertThat(configuration.getUserAgent()).isEqualTo(
48 | "Mozilla/5.0 (compatible; AcmeInc/1.0; +https://example.com/bot.html)");
49 | assertThat(configuration.isLogFailedTracking()).isTrue();
50 | assertThat(configuration.isDisableSslCertValidation()).isTrue();
51 | assertThat(configuration.isDisableSslHostVerification()).isTrue();
52 | assertThat(configuration.getThreadPoolSize()).isEqualTo(10);
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/java11/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | org.piwik.java.tracking
6 | matomo-java-tracker-parent
7 | 3.4.1-SNAPSHOT
8 | ../pom.xml
9 |
10 |
11 | matomo-java-tracker-java11
12 | 3.4.1-SNAPSHOT
13 | jar
14 |
15 | Matomo Java Tracker Java 11
16 | Official Java implementation of the Matomo Tracking HTTP API for Java 11.
17 |
18 |
19 | 11
20 | 11
21 |
22 |
23 |
24 |
25 | org.piwik.java.tracking
26 | matomo-java-tracker-core
27 | ${project.version}
28 |
29 |
30 | org.projectlombok
31 | lombok
32 | provided
33 |
34 |
35 | com.github.spotbugs
36 | spotbugs-annotations
37 |
38 |
39 | org.junit.jupiter
40 | junit-jupiter
41 | test
42 |
43 |
44 | org.assertj
45 | assertj-core
46 | test
47 |
48 |
49 | org.wiremock
50 | wiremock
51 | 3.11.0
52 | test
53 |
54 |
55 | org.slf4j
56 | slf4j-simple
57 | test
58 |
59 |
60 |
61 |
62 |
63 |
64 | org.apache.maven.plugins
65 | maven-failsafe-plugin
66 |
67 |
68 | true
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/java11/src/main/java/org/matomo/java/tracking/Java11SenderProvider.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import edu.umd.cs.findbugs.annotations.Nullable;
4 | import java.net.CookieManager;
5 | import java.net.InetSocketAddress;
6 | import java.net.ProxySelector;
7 | import java.net.http.HttpClient;
8 | import java.security.SecureRandom;
9 | import java.util.concurrent.ExecutorService;
10 | import java.util.concurrent.Executors;
11 | import javax.net.ssl.SSLContext;
12 | import javax.net.ssl.TrustManager;
13 |
14 | /**
15 | * Provides a {@link Sender} implementation based on Java 11.
16 | */
17 | public class Java11SenderProvider implements SenderProvider {
18 |
19 | private static final TrustManager[] TRUST_ALL_MANAGERS = {new TrustingX509TrustManager()};
20 |
21 | @Override
22 | public Sender provideSender(
23 | TrackerConfiguration trackerConfiguration, QueryCreator queryCreator
24 | ) {
25 | CookieManager cookieManager = new CookieManager();
26 | ExecutorService executorService = Executors.newFixedThreadPool(
27 | trackerConfiguration.getThreadPoolSize(),
28 | new DaemonThreadFactory()
29 | );
30 | HttpClient.Builder builder = HttpClient
31 | .newBuilder()
32 | .cookieHandler(cookieManager)
33 | .executor(executorService);
34 | if (trackerConfiguration.getConnectTimeout() != null
35 | && trackerConfiguration.getConnectTimeout().toMillis() > 0L) {
36 | builder.connectTimeout(trackerConfiguration.getConnectTimeout());
37 | }
38 | if (!isEmpty(trackerConfiguration.getProxyHost()) && trackerConfiguration.getProxyPort() > 0) {
39 | builder.proxy(ProxySelector.of(new InetSocketAddress(
40 | trackerConfiguration.getProxyHost(),
41 | trackerConfiguration.getProxyPort()
42 | )));
43 | if (!isEmpty(trackerConfiguration.getProxyUsername())
44 | && !isEmpty(trackerConfiguration.getProxyPassword())) {
45 | builder.authenticator(new ProxyAuthenticator(
46 | trackerConfiguration.getProxyUsername(),
47 | trackerConfiguration.getProxyPassword()
48 | ));
49 | }
50 | }
51 | if (trackerConfiguration.isDisableSslCertValidation()) {
52 | try {
53 | SSLContext sslContext = SSLContext.getInstance("SSL");
54 | sslContext.init(null, TRUST_ALL_MANAGERS, new SecureRandom());
55 | builder.sslContext(sslContext);
56 | } catch (Exception e) {
57 | throw new MatomoException("Could not disable SSL certification validation", e);
58 | }
59 | }
60 | if (trackerConfiguration.isDisableSslHostVerification()) {
61 | throw new MatomoException("Please disable SSL hostname verification manually using the system parameter -Djdk.internal.httpclient.disableHostnameVerification=true");
62 | }
63 |
64 | return new Java11Sender(
65 | trackerConfiguration,
66 | queryCreator,
67 | builder.build(),
68 | cookieManager.getCookieStore(),
69 | executorService
70 | );
71 | }
72 |
73 | private static boolean isEmpty(
74 | @Nullable String str
75 | ) {
76 | return str == null || str.isEmpty() || str.trim().isEmpty();
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/TrackingParameterMethodTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
4 |
5 | import java.util.regex.Pattern;
6 | import org.junit.jupiter.api.Test;
7 |
8 | class TrackingParameterMethodTest {
9 |
10 | @Test
11 | void validateParameterValueFailsIfPatternDoesNotMatch() {
12 | TrackingParameterMethod trackingParameterMethod = TrackingParameterMethod
13 | .builder()
14 | .parameterName("foo")
15 | .pattern(Pattern.compile("bar"))
16 | .build();
17 |
18 | assertThatThrownBy(() -> trackingParameterMethod.validateParameterValue("baz"))
19 | .isInstanceOf(MatomoException.class)
20 | .hasMessage("Invalid value for foo. Must match regex bar");
21 | }
22 |
23 | @Test
24 | void doNothingIfPatternIsNull() {
25 | TrackingParameterMethod trackingParameterMethod =
26 | TrackingParameterMethod.builder().parameterName("foo").maxLength(255).build();
27 |
28 | trackingParameterMethod.validateParameterValue("baz");
29 | }
30 |
31 | @Test
32 | void doNothingIfParameterValueIsNotCharSequence() {
33 | TrackingParameterMethod trackingParameterMethod = TrackingParameterMethod
34 | .builder()
35 | .parameterName("foo")
36 | .pattern(Pattern.compile("bar"))
37 | .maxLength(255)
38 | .min(1)
39 | .max(1)
40 | .build();
41 |
42 | trackingParameterMethod.validateParameterValue(1);
43 | }
44 |
45 | @Test
46 | void failIfParameterValueIsNull() {
47 | TrackingParameterMethod trackingParameterMethod = TrackingParameterMethod
48 | .builder()
49 | .parameterName("foo")
50 | .pattern(Pattern.compile("bar"))
51 | .maxLength(255)
52 | .build();
53 |
54 | assertThatThrownBy(() -> trackingParameterMethod.validateParameterValue(null))
55 | .isInstanceOf(NullPointerException.class)
56 | .hasMessage("parameterValue is marked non-null but is null");
57 | }
58 |
59 | @Test
60 | void validateParameterValueFailsIfMaxLengthIsExceeded() {
61 | TrackingParameterMethod trackingParameterMethod =
62 | TrackingParameterMethod.builder().parameterName("foo").maxLength(3).build();
63 |
64 | assertThatThrownBy(() -> trackingParameterMethod.validateParameterValue("foobar"))
65 | .isInstanceOf(MatomoException.class)
66 | .hasMessage("Invalid value for foo. Must be less or equal than 3 characters");
67 | }
68 |
69 | @Test
70 | void failIfParameterValueIsLessThanMin() {
71 | TrackingParameterMethod trackingParameterMethod =
72 | TrackingParameterMethod.builder().parameterName("foo").min(3.0).build();
73 |
74 | assertThatThrownBy(() -> trackingParameterMethod.validateParameterValue(1))
75 | .isInstanceOf(MatomoException.class)
76 | .hasMessage("Invalid value for foo. Must be greater or equal than 3");
77 | }
78 |
79 | @Test
80 | void failIfParameterValueIsGreaterThanMax() {
81 | TrackingParameterMethod trackingParameterMethod =
82 | TrackingParameterMethod.builder().parameterName("foo").max(3.0).build();
83 |
84 | assertThatThrownBy(() -> trackingParameterMethod.validateParameterValue(4))
85 | .isInstanceOf(MatomoException.class)
86 | .hasMessage("Invalid value for foo. Must be less or equal than 3");
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/spring/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | org.piwik.java.tracking
6 | matomo-java-tracker-parent
7 | 3.4.1-SNAPSHOT
8 | ../pom.xml
9 |
10 |
11 | matomo-java-tracker-spring-boot-starter
12 | jar
13 |
14 | Matomo Java Tracker Spring Boot Starter
15 | Spring integration of Matomo Java Tracker
16 |
17 |
18 | 17
19 | 17
20 | 3.4.2
21 |
22 |
23 |
24 |
25 |
26 | org.springframework.boot
27 | spring-boot-dependencies
28 | ${spring-boot.version}
29 | pom
30 | import
31 |
32 |
33 |
34 |
35 |
36 |
37 | org.piwik.java.tracking
38 | matomo-java-tracker-java11
39 | ${project.version}
40 |
41 |
42 | org.piwik.java.tracking
43 | matomo-java-tracker-servlet-jakarta
44 | ${project.version}
45 |
46 |
47 | jakarta.servlet
48 | jakarta.servlet-api
49 | provided
50 |
51 |
52 | org.springframework.boot
53 | spring-boot-starter
54 |
55 |
56 | org.springframework.boot
57 | spring-boot-autoconfigure-processor
58 | true
59 |
60 |
61 | org.springframework.boot
62 | spring-boot-configuration-processor
63 | true
64 |
65 |
66 | org.projectlombok
67 | lombok
68 | provided
69 |
70 |
71 | org.springframework.boot
72 | spring-boot-starter-test
73 | test
74 |
75 |
76 | org.slf4j
77 | slf4j-simple
78 | test
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/RequestValidatorTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
4 |
5 | import java.util.Locale;
6 | import org.junit.jupiter.api.Test;
7 | import org.piwik.java.tracking.PiwikDate;
8 | import org.piwik.java.tracking.PiwikLocale;
9 |
10 | class RequestValidatorTest {
11 |
12 | private final MatomoRequest request = new MatomoRequest();
13 |
14 |
15 | @Test
16 | void testSearchResultsCount() {
17 |
18 | request.setSearchResultsCount(100L);
19 |
20 | assertThatThrownBy(() -> RequestValidator.validate(request, null))
21 | .isInstanceOf(MatomoException.class)
22 | .hasMessage("Search query must be set if search results count is set");
23 |
24 | }
25 |
26 | @Test
27 | void testVisitorLongitude() {
28 | request.setVisitorLongitude(20.5);
29 |
30 | assertThatThrownBy(() -> RequestValidator.validate(request, null))
31 | .isInstanceOf(MatomoException.class)
32 | .hasMessage(
33 | "Auth token must be present if visitor longitude, latitude, region, city, country or IP are set");
34 | }
35 |
36 | @Test
37 | void testVisitorLatitude() {
38 | request.setVisitorLatitude(10.5);
39 |
40 | assertThatThrownBy(() -> RequestValidator.validate(request, null))
41 | .isInstanceOf(MatomoException.class)
42 | .hasMessage(
43 | "Auth token must be present if visitor longitude, latitude, region, city, country or IP are set");
44 | }
45 |
46 | @Test
47 | void testVisitorCity() {
48 | request.setVisitorCity("city");
49 |
50 | assertThatThrownBy(() -> RequestValidator.validate(request, null))
51 | .isInstanceOf(MatomoException.class)
52 | .hasMessage(
53 | "Auth token must be present if visitor longitude, latitude, region, city, country or IP are set");
54 | }
55 |
56 | @Test
57 | void testVisitorRegion() {
58 | request.setVisitorRegion("region");
59 |
60 | assertThatThrownBy(() -> RequestValidator.validate(request, null))
61 | .isInstanceOf(MatomoException.class)
62 | .hasMessage(
63 | "Auth token must be present if visitor longitude, latitude, region, city, country or IP are set");
64 | }
65 |
66 | @Test
67 | void testVisitorCountryTE() {
68 | PiwikLocale country = new PiwikLocale(Locale.US);
69 | request.setVisitorCountry(country);
70 |
71 |
72 | assertThatThrownBy(() -> RequestValidator.validate(request, null))
73 | .isInstanceOf(MatomoException.class)
74 | .hasMessage(
75 | "Auth token must be present if visitor longitude, latitude, region, city, country or IP are set");
76 | }
77 |
78 | @Test
79 | void testRequestDatetime() {
80 |
81 | PiwikDate date = new PiwikDate(1000L);
82 | request.setRequestDatetime(date);
83 |
84 | assertThatThrownBy(() -> RequestValidator.validate(request, null))
85 | .isInstanceOf(MatomoException.class)
86 | .hasMessage("Auth token must be present if request timestamp is more than four hours ago");
87 |
88 | }
89 |
90 | @Test
91 | void failsIfAuthTokenIsNot32CharactersLong() {
92 | assertThatThrownBy(() -> RequestValidator.validate(request, "123456789012345678901234567890"))
93 | .isInstanceOf(IllegalArgumentException.class)
94 | .hasMessage("Auth token must be exactly 32 characters long");
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/MatomoRequestBuilderTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static java.util.Collections.singletonMap;
4 | import static org.assertj.core.api.Assertions.assertThat;
5 |
6 | import org.junit.jupiter.api.Test;
7 | import org.matomo.java.tracking.parameters.CustomVariables;
8 |
9 |
10 | class MatomoRequestBuilderTest {
11 |
12 | @Test
13 | void buildsRequest() {
14 | CustomVariable pageCustomVariable =
15 | new CustomVariable("pageCustomVariableName", "pageCustomVariableValue");
16 | CustomVariable visitCustomVariable =
17 | new CustomVariable("visitCustomVariableName", "visitCustomVariableValue");
18 |
19 | MatomoRequest matomoRequest = new MatomoRequestBuilder()
20 | .siteId(42)
21 | .actionName("ACTION_NAME")
22 | .actionUrl("https://www.your-domain.tld/some/page?query=foo")
23 | .referrerUrl("https://referrer.com")
24 | .additionalParameters(singletonMap(
25 | "trackingParameterName",
26 | "trackingParameterValue"
27 | ))
28 | .pageCustomVariables(new CustomVariables().add(pageCustomVariable, 2))
29 | .visitCustomVariables(new CustomVariables().add(visitCustomVariable, 3))
30 | .customAction(true)
31 | .build();
32 |
33 | assertThat(matomoRequest.getSiteId()).isEqualTo(42);
34 | assertThat(matomoRequest.getActionName()).isEqualTo("ACTION_NAME");
35 | assertThat(matomoRequest.getApiVersion()).isEqualTo("1");
36 | assertThat(matomoRequest.getActionUrl()).isEqualTo(
37 | "https://www.your-domain.tld/some/page?query=foo");
38 | assertThat(matomoRequest.getVisitorId().toString()).hasSize(16).isHexadecimal();
39 | assertThat(matomoRequest.getRandomValue().toString()).hasSize(20).isHexadecimal();
40 | assertThat(matomoRequest.getResponseAsImage()).isFalse();
41 | assertThat(matomoRequest.getRequired()).isTrue();
42 | assertThat(matomoRequest.getReferrerUrl()).isEqualTo("https://referrer.com");
43 | assertThat(matomoRequest.getCustomTrackingParameter("trackingParameterName")).isEqualTo(
44 | "trackingParameterValue");
45 | assertThat(matomoRequest.getPageCustomVariables()).hasToString(
46 | "{\"2\":[\"pageCustomVariableName\",\"pageCustomVariableValue\"]}");
47 | assertThat(matomoRequest.getVisitCustomVariables()).hasToString(
48 | "{\"3\":[\"visitCustomVariableName\",\"visitCustomVariableValue\"]}");
49 | assertThat(matomoRequest.getCustomAction()).isTrue();
50 |
51 | }
52 |
53 | @Test
54 | void setCustomTrackingParameters() {
55 | MatomoRequest matomoRequest = new MatomoRequestBuilder()
56 | .customTrackingParameters(singletonMap("foo", "bar"))
57 | .siteId(42)
58 | .actionName("ACTION_NAME")
59 | .actionUrl("https://www.your-domain.tld/some/page?query=foo")
60 | .referrerUrl("https://referrer.com")
61 | .build();
62 |
63 | assertThat(matomoRequest.getCustomTrackingParameter("foo")).isEqualTo("bar");
64 | }
65 |
66 | @Test
67 | void setCustomTrackingParametersWithCollectopm() {
68 | MatomoRequest matomoRequest = new MatomoRequestBuilder()
69 | .customTrackingParameters(singletonMap("foo", "bar"))
70 | .siteId(42)
71 | .actionName("ACTION_NAME")
72 | .actionUrl("https://www.your-domain.tld/some/page?query=foo")
73 | .referrerUrl("https://referrer.com")
74 | .build();
75 |
76 | assertThat(matomoRequest.getCustomTrackingParameter("foo")).isEqualTo("bar");
77 | }
78 |
79 | @Test
80 | void acceptsNullAsHeaderAcceptLanguage() {
81 | MatomoRequest matomoRequest = new MatomoRequestBuilder()
82 | .headerAcceptLanguage((String) null)
83 | .build();
84 |
85 | assertThat(matomoRequest.getHeaderAcceptLanguage()).isNull();
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/parameters/Country.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import edu.umd.cs.findbugs.annotations.Nullable;
11 | import java.util.List;
12 | import java.util.Locale;
13 | import java.util.Locale.LanguageRange;
14 | import lombok.AccessLevel;
15 | import lombok.NonNull;
16 | import lombok.RequiredArgsConstructor;
17 |
18 | /**
19 | * A two-letter country code representing a country.
20 | *
21 | * See ISO 3166-1 alpha-2 for a list of valid codes.
22 | */
23 | @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
24 | public class Country {
25 |
26 | @NonNull
27 | private String code;
28 |
29 | /**
30 | * Only for internal use to grant downwards compatibility to {@link org.matomo.java.tracking.MatomoLocale}.
31 | *
32 | * @param locale A locale that must contain a country code
33 | */
34 | @Deprecated
35 | protected Country(
36 | @edu.umd.cs.findbugs.annotations.NonNull
37 | Locale locale
38 | ) {
39 | setLocale(locale);
40 | }
41 |
42 | /**
43 | * Creates a country from a given code.
44 | *
45 | * @param code Must consist of two lower letters or simply null. Case is ignored
46 | * @return The country or null if code was null
47 | */
48 | @Nullable
49 | public static Country fromCode(
50 | @Nullable
51 | String code
52 | ) {
53 | if (code == null || code.isEmpty() || code.trim().isEmpty()) {
54 | return null;
55 | }
56 | if (code.length() == 2) {
57 | return new Country(code.toLowerCase(Locale.ROOT));
58 | }
59 | throw new IllegalArgumentException("Invalid country code");
60 | }
61 |
62 | /**
63 | * Extracts the country from the given accept language header.
64 | *
65 | * @param ranges A language range list. See {@link LanguageRange#parse(String)}
66 | * @return The country or null if ranges was null
67 | */
68 | @Nullable
69 | public static Country fromLanguageRanges(
70 | @Nullable
71 | String ranges
72 | ) {
73 | if (ranges == null || ranges.isEmpty() || ranges.trim().isEmpty()) {
74 | return null;
75 | }
76 | List languageRanges = LanguageRange.parse(ranges);
77 | for (LanguageRange languageRange : languageRanges) {
78 | String range = languageRange.getRange();
79 | String[] split = range.split("-");
80 | if (split.length == 2 && split[1].length() == 2) {
81 | return new Country(split[1].toLowerCase(Locale.ROOT));
82 | }
83 | }
84 | throw new IllegalArgumentException("Invalid country code");
85 | }
86 |
87 | /**
88 | * Returns the locale for this country.
89 | *
90 | * @return The locale for this country
91 | * @see Locale#forLanguageTag(String)
92 | * @deprecated Since you instantiate this class, you can determine the language on your own
93 | * using {@link Locale#forLanguageTag(String)}
94 | */
95 | @Deprecated
96 | public Locale getLocale() {
97 | return Locale.forLanguageTag(code);
98 | }
99 |
100 | /**
101 | * Sets the locale for this country.
102 | *
103 | * @param locale A locale that must contain a country code
104 | * @see Locale#getCountry()
105 | * @deprecated Since you instantiate this class, you can determine the language on your own
106 | * using {@link Locale#getCountry()}
107 | */
108 | @Deprecated
109 | public final void setLocale(Locale locale) {
110 | if (locale == null || locale.getCountry() == null || locale.getCountry().isEmpty()) {
111 | throw new IllegalArgumentException("Invalid locale");
112 | }
113 | code = locale.getCountry().toLowerCase(Locale.ENGLISH);
114 | }
115 |
116 | @Override
117 | public String toString() {
118 | return code;
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/parameters/CountryTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
12 |
13 | import java.util.Locale;
14 | import org.junit.jupiter.api.Test;
15 | import org.junit.jupiter.params.ParameterizedTest;
16 | import org.junit.jupiter.params.provider.NullAndEmptySource;
17 |
18 | class CountryTest {
19 |
20 | @Test
21 | void createsCountryFromCode() {
22 |
23 | Country country = Country.fromCode("DE");
24 |
25 | assertThat(country).hasToString("de");
26 |
27 | }
28 |
29 | @Test
30 | void createsCountryFromAcceptLanguageHeader() {
31 |
32 | Country country = Country.fromLanguageRanges("en-GB;q=0.7,de,de-DE;q=0.9,en;q=0.8,en-US;q=0.6");
33 |
34 | assertThat(country).hasToString("de");
35 |
36 | }
37 |
38 | @ParameterizedTest
39 | @NullAndEmptySource
40 | void returnsNullOnEmptyRanges(String ranges) {
41 |
42 | Country country = Country.fromLanguageRanges(ranges);
43 |
44 | assertThat(country).isNull();
45 |
46 | }
47 |
48 | @Test
49 | void failsOnInvalidCountryCode() {
50 |
51 | assertThatThrownBy(() -> Country.fromCode("invalid"))
52 | .isInstanceOf(IllegalArgumentException.class)
53 | .hasMessage("Invalid country code");
54 |
55 | }
56 |
57 | @Test
58 | void failsOnInvalidCountryCodeLength() {
59 |
60 | assertThatThrownBy(() -> Country.fromCode("invalid"))
61 | .isInstanceOf(IllegalArgumentException.class)
62 | .hasMessage("Invalid country code");
63 |
64 | }
65 |
66 | @Test
67 | void returnsNullOnNullCode() {
68 |
69 | Country country = Country.fromCode(null);
70 |
71 | assertThat(country).isNull();
72 |
73 | }
74 |
75 | @Test
76 | void returnsNullOnEmptyCode() {
77 |
78 | Country country = Country.fromCode("");
79 |
80 | assertThat(country).isNull();
81 |
82 | }
83 |
84 | @Test
85 | void returnsNullOnBlankCode() {
86 |
87 | Country country = Country.fromCode(" ");
88 |
89 | assertThat(country).isNull();
90 |
91 | }
92 |
93 | @Test
94 | void returnsNullOnNullRanges() {
95 |
96 | Country country = Country.fromLanguageRanges(null);
97 |
98 | assertThat(country).isNull();
99 |
100 | }
101 |
102 | @Test
103 | void returnsNullOnBlankRanges() {
104 |
105 | Country country = Country.fromLanguageRanges(" ");
106 |
107 | assertThat(country).isNull();
108 |
109 | }
110 |
111 | @Test
112 | void failsOnInvalidRanges() {
113 |
114 | assertThatThrownBy(() -> Country.fromLanguageRanges("invalid"))
115 | .isInstanceOf(IllegalArgumentException.class)
116 | .hasMessage("Invalid country code");
117 |
118 | }
119 |
120 | @Test
121 | void failsOnLocaleWithoutCountryCode() {
122 |
123 | assertThatThrownBy(() -> new Country(Locale.forLanguageTag("de")))
124 | .isInstanceOf(IllegalArgumentException.class)
125 | .hasMessage("Invalid locale");
126 |
127 | }
128 |
129 | @Test
130 | void setLocaleFailsOnNullLocale() {
131 |
132 | assertThatThrownBy(() -> new Country(Locale.forLanguageTag("de")).setLocale(null))
133 | .isInstanceOf(IllegalArgumentException.class)
134 | .hasMessage("Invalid locale");
135 |
136 | }
137 |
138 | @Test
139 | void setLocaleFailsOnNullCountryCode() {
140 |
141 | assertThatThrownBy(() -> new Country(Locale.forLanguageTag("de")).setLocale(Locale.forLanguageTag(
142 | "de"))).isInstanceOf(IllegalArgumentException.class).hasMessage("Invalid locale");
143 |
144 | }
145 |
146 | @Test
147 | void setLocaleFailsOnEmptyCountryCode() {
148 |
149 | assertThatThrownBy(() -> new Country(Locale.forLanguageTag("de")).setLocale(Locale.forLanguageTag(
150 | "de"))).isInstanceOf(IllegalArgumentException.class).hasMessage("Invalid locale");
151 |
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Process
9 |
10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a
11 | build.
12 | 2. Update the README.md with details of changes to the interface, this includes new environment
13 | variables, exposed ports, useful file locations and container parameters.
14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this
15 | Pull Request would represent. The versioning scheme we use is [SemVer](https://semver.org/).
16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
17 | do not have permission to do that, you may request the second reviewer to merge it for you.
18 |
19 | ## Code of Conduct
20 |
21 | ### Our Pledge
22 |
23 | In the interest of fostering an open and welcoming environment, we as
24 | contributors and maintainers pledge to making participation in our project and
25 | our community a harassment-free experience for everyone, regardless of age, body
26 | size, disability, ethnicity, gender identity and expression, level of experience,
27 | nationality, personal appearance, race, religion, or sexual identity and
28 | orientation.
29 |
30 | ### Our Standards
31 |
32 | Examples of behavior that contributes to creating a positive environment
33 | include:
34 |
35 | * Using welcoming and inclusive language
36 | * Being respectful of differing viewpoints and experiences
37 | * Gracefully accepting constructive criticism
38 | * Focusing on what is best for the community
39 | * Showing empathy towards other community members
40 |
41 | Examples of unacceptable behavior by participants include:
42 |
43 | * The use of sexualized language or imagery and unwelcome sexual attention or
44 | advances
45 | * Trolling, insulting/derogatory comments, and personal or political attacks
46 | * Public or private harassment
47 | * Publishing others' private information, such as a physical or electronic
48 | address, without explicit permission
49 | * Other conduct which could reasonably be considered inappropriate in a
50 | professional setting
51 |
52 | ### Our Responsibilities
53 |
54 | Project maintainers are responsible for clarifying the standards of acceptable
55 | behavior and are expected to take appropriate and fair corrective action in
56 | response to any instances of unacceptable behavior.
57 |
58 | Project maintainers have the right and responsibility to remove, edit, or
59 | reject comments, commits, code, wiki edits, issues, and other contributions
60 | that are not aligned to this Code of Conduct, or to ban temporarily or
61 | permanently any contributor for other behaviors that they deem inappropriate,
62 | threatening, offensive, or harmful.
63 |
64 | ### Scope
65 |
66 | This Code of Conduct applies both within project spaces and in public spaces
67 | when an individual is representing the project or its community. Examples of
68 | representing a project or community include using an official project e-mail
69 | address, posting via an official social media account, or acting as an appointed
70 | representative at an online or offline event. Representation of a project may be
71 | further defined and clarified by project maintainers.
72 |
73 | ### Enforcement
74 |
75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
76 | reported by contacting the project team at mail@daniel-heid.de. All
77 | complaints will be reviewed and investigated and will result in a response that
78 | is deemed necessary and appropriate to the circumstances. The project team is
79 | obligated to maintain confidentiality with regard to the reporter of an incident.
80 | Further details of specific enforcement policies may be posted separately.
81 |
82 | Project maintainers who do not follow or enforce the Code of Conduct in good
83 | faith may face temporary or permanent repercussions as determined by other
84 | members of the project's leadership.
85 |
86 | ### Attribution
87 |
88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
89 | available at [https://contributor-covenant.org/version/1/4][version]
90 |
91 | [homepage]: https://contributor-covenant.org
92 |
93 | [version]: https://contributor-covenant.org/version/1/4/
94 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/MatomoRequestTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
5 |
6 | import org.junit.jupiter.api.Test;
7 |
8 | class MatomoRequestTest {
9 |
10 | private MatomoRequest request = new MatomoRequest();
11 |
12 | @Test
13 | void returnsEmptyListWhenCustomTrackingParametersDoesNotContainKey() {
14 |
15 | request.setCustomTrackingParameter("foo", "bar");
16 |
17 | assertThat(request.getCustomTrackingParameter("baz")).isNull();
18 | assertThat(request.getAdditionalParameters()).isNotEmpty();
19 | assertThat(request.getCustomTrackingParameter("foo")).isEqualTo("bar");
20 | }
21 |
22 | @Test
23 | void getPageCustomVariableReturnsNullIfPageCustomVariablesIsNull() {
24 | assertThat(request.getPageCustomVariable("foo")).isNull();
25 | }
26 |
27 | @Test
28 | void getPageCustomVariableReturnsValueIfPageCustomVariablesIsNotNull() {
29 | request.setPageCustomVariable("foo", "bar");
30 | assertThat(request.getPageCustomVariable("foo")).isEqualTo("bar");
31 | }
32 |
33 | @Test
34 | void setPageCustomVariableRequiresNonNullKey() {
35 | assertThatThrownBy(() -> request.setPageCustomVariable(null, "bar")).isInstanceOf(
36 | NullPointerException.class);
37 | }
38 |
39 | @Test
40 | void setPageCustomVariableDoesNothingIfValueIsNull() {
41 | request.setPageCustomVariable("foo", null);
42 | assertThat(request.getPageCustomVariable("foo")).isNull();
43 | }
44 |
45 | @Test
46 | void setPageCustomVariableRemovesValueIfValueIsNull() {
47 | request.setPageCustomVariable("foo", "bar");
48 | request.setPageCustomVariable("foo", null);
49 | assertThat(request.getPageCustomVariable("foo")).isNull();
50 | }
51 |
52 | @Test
53 | void setPageCustomVariableAddsCustomVariableIfValueIsNotNull() {
54 | request.setPageCustomVariable("foo", "bar");
55 | assertThat(request.getPageCustomVariable("foo")).isEqualTo("bar");
56 | }
57 |
58 | @Test
59 | void setPageCustomVariableDoesNothingIfCustomVariableParameterIsNullAndIndexIsPositive() {
60 | request.setPageCustomVariable(null, 1);
61 | assertThat(request.getPageCustomVariable(1)).isNull();
62 | }
63 |
64 | @Test
65 | void setPageCustomVariableInitializesPageCustomVariablesIfCustomVariableParameterIsNullAndIndexIsPositive() {
66 | request.setPageCustomVariable(new CustomVariable("key", "value"), 1);
67 | assertThat(request.getPageCustomVariables()).isNotNull();
68 | }
69 |
70 | @Test
71 | void setUserCustomVariableDoesNothingIfValueIsNull() {
72 | request.setUserCustomVariable("foo", null);
73 | assertThat(request.getUserCustomVariable("foo")).isNull();
74 | }
75 |
76 | @Test
77 | void setUserCustomVariableRemovesValueIfValueIsNull() {
78 | request.setUserCustomVariable("foo", "bar");
79 | request.setUserCustomVariable("foo", null);
80 | assertThat(request.getUserCustomVariable("foo")).isNull();
81 | }
82 |
83 | @Test
84 | void setVisitCustomVariableDoesNothingIfCustomVariableParameterIsNullAndIndexIsPositive() {
85 | request.setVisitCustomVariable(null, 1);
86 | assertThat(request.getVisitCustomVariable(1)).isNull();
87 | }
88 |
89 | @Test
90 | void setVisitCustomVariableInitializesVisitCustomVariablesIfCustomVariableParameterIsNullAndIndexIsPositive() {
91 | request.setVisitCustomVariable(new CustomVariable("key", "value"), 1);
92 | assertThat(request.getVisitCustomVariables()).isNotNull();
93 | }
94 |
95 | @Test
96 | void setsCustomParameter() {
97 | request.setParameter("foo", 1);
98 | assertThat(request.getCustomTrackingParameter("foo")).isEqualTo(1);
99 | }
100 |
101 | @Test
102 | void failsToSetCustomParameterIfKeyIsNull() {
103 | assertThatThrownBy(() -> request.setParameter(
104 | null,
105 | 1
106 | )).isInstanceOf(NullPointerException.class);
107 | }
108 |
109 | @Test
110 | void doesNothingWhenSettingCustomParameterIfValueIsNull() {
111 | request.setParameter("foo", null);
112 | assertThat(request.getAdditionalParameters()).isNull();
113 | }
114 |
115 | @Test
116 | void removesCustomParameter() {
117 | request.setParameter("foo", 1);
118 | request.setParameter("foo", null);
119 | assertThat(request.getAdditionalParameters()).isEmpty();
120 | }
121 |
122 | @Test
123 | void setsDeviceResolutionString() {
124 | request.setDeviceResolution("1920x1080");
125 | assertThat(request.getDeviceResolution().toString()).isEqualTo("1920x1080");
126 | }
127 |
128 | @Test
129 | void failsIfSetParameterParameterNameIsBlank() {
130 | assertThatThrownBy(() -> request.setParameter(" ", "bar")).isInstanceOf(
131 | IllegalArgumentException.class);
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/core/src/main/java/org/matomo/java/tracking/parameters/VisitorId.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.parameters;
9 |
10 | import edu.umd.cs.findbugs.annotations.Nullable;
11 | import java.security.SecureRandom;
12 | import java.util.Random;
13 | import java.util.UUID;
14 | import java.util.regex.Pattern;
15 | import lombok.NonNull;
16 |
17 | /**
18 | * The unique visitor ID, must be a 16 characters hexadecimal string. Every unique visitor must be assigned a different
19 | * ID and this ID must not change after it is assigned. If this value is not set Matomo will still track visits, but the
20 | * unique visitors metric might be less accurate.
21 | */
22 | public class VisitorId {
23 |
24 | private static final Random RANDOM = new SecureRandom();
25 | private static final Pattern HEX_DIGITS = Pattern.compile("[0-9a-fA-F]+");
26 |
27 | private final byte[] representation = new byte[8];
28 |
29 | /**
30 | * Static factory to generate a random visitor id.
31 | *
32 | * Please consider creating a fixed id for each visitor by getting a hash code from e.g. the username and
33 | * using {@link #fromHash(long)} or {@link #fromString(String)} instead of using this method.
34 | *
35 | * @return A randomly generated visitor id
36 | */
37 | @edu.umd.cs.findbugs.annotations.NonNull
38 | public static VisitorId random() {
39 | VisitorId visitorId = new VisitorId();
40 | RANDOM.nextBytes(visitorId.representation);
41 | return visitorId;
42 | }
43 |
44 | /**
45 | * Creates always the same visitor id for the given input.
46 | *
47 | *
You can use e.g. {@link Object#hashCode()} to generate a hash code for an object, e.g. a username
48 | * string as input.
49 | *
50 | * @param hash A number (a hash code) to create the visitor id from
51 | * @return Always the same visitor id for the same input
52 | */
53 | @edu.umd.cs.findbugs.annotations.NonNull
54 | public static VisitorId fromHash(long hash) {
55 | VisitorId visitorId = new VisitorId();
56 | long remainingHash = hash;
57 | for (int i = visitorId.representation.length - 1; i >= 0; i--) {
58 | visitorId.representation[i] = (byte) (remainingHash & 0xFF);
59 | remainingHash >>= Byte.SIZE;
60 | }
61 | return visitorId;
62 | }
63 |
64 | /**
65 | * Creates a visitor id from a UUID.
66 | *
67 | *
Uses the most significant bits of the UUID to create the visitor id.
68 | *
69 | * @param uuid A UUID to create the visitor id from
70 | * @return The visitor id for the given UUID
71 | */
72 | @edu.umd.cs.findbugs.annotations.NonNull
73 | public static VisitorId fromUUID(@NonNull UUID uuid) {
74 | return fromHash(uuid.getMostSignificantBits());
75 | }
76 |
77 | /**
78 | * Creates a visitor id from a hexadecimal string.
79 | *
80 | *
The input must be a valid hexadecimal string with a maximum length of 16 characters. If the input is shorter
81 | * than 16 characters it will be padded with zeros.
82 | *
83 | * @param inputHex A hexadecimal string to create the visitor id from
84 | * @return The visitor id for the given input
85 | */
86 | @edu.umd.cs.findbugs.annotations.NonNull
87 | public static VisitorId fromHex(@NonNull String inputHex) {
88 | if (inputHex.trim().isEmpty()) {
89 | throw new IllegalArgumentException("Hex string must not be null or empty");
90 | }
91 | if (inputHex.length() > 16) {
92 | throw new IllegalArgumentException("Hex string must not be longer than 16 characters");
93 | }
94 | if (!HEX_DIGITS.matcher(inputHex).matches()) {
95 | throw new IllegalArgumentException("Input must be a valid hex string");
96 | }
97 | VisitorId visitorId = new VisitorId();
98 | for (int charIndex = inputHex.length() - 1, representationIndex =
99 | visitorId.representation.length - 1;
100 | charIndex >= 0;
101 | charIndex -= 2, representationIndex--) {
102 | String hex = inputHex.substring(Math.max(0, charIndex - 1), charIndex + 1);
103 | try {
104 | visitorId.representation[representationIndex] = (byte) Integer.parseInt(hex, 16);
105 | } catch (NumberFormatException e) {
106 | throw new IllegalArgumentException("Input must be a valid hex string", e);
107 | }
108 | }
109 | return visitorId;
110 | }
111 |
112 | /**
113 | * Creates a visitor id from a string. The string will be hashed to create the visitor id.
114 | *
115 | * @param str A string to create the visitor id from
116 | * @return The visitor id for the given string or null if the string is null or empty
117 | */
118 | @Nullable
119 | public static VisitorId fromString(@Nullable String str) {
120 | if (str == null || str.trim().isEmpty()) {
121 | return null;
122 | }
123 | return fromHash(str.hashCode());
124 | }
125 |
126 | @Override
127 | @edu.umd.cs.findbugs.annotations.NonNull
128 | public String toString() {
129 | return Hex.fromBytes(representation);
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/spring/src/main/java/org/matomo/java/tracking/spring/MatomoTrackerProperties.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.spring;
9 |
10 | import java.time.Duration;
11 | import lombok.Getter;
12 | import lombok.Setter;
13 | import org.matomo.java.tracking.TrackerConfiguration;
14 | import org.springframework.boot.context.properties.ConfigurationProperties;
15 |
16 | /**
17 | * Configuration properties for the Matomo Tracker.
18 | *
19 | * These properties can be configured in the application.properties file. For example:
20 | *
21 | * matomo.tracker.api-endpoint=https://your-matomo-domain.example/matomo.php
22 | * matomo.tracker.default-site-id=1
23 | * matomo.tracker.default-auth-token=1234567890abcdef1234567890abcdef
24 | * matomo.tracker.enabled=true
25 | * matomo.tracker.connect-timeout=10s
26 | * matomo.tracker.socket-timeout=30s
27 | * matomo.tracker.proxy-host=proxy.example.com
28 | * matomo.tracker.proxy-port=8080
29 | * matomo.tracker.proxy-username=proxyuser
30 | * matomo.tracker.proxy-password=proxypassword
31 | * matomo.tracker.user-agent=MatomoJavaClient
32 | * matomo.tracker.log-failed-tracking=true
33 | * matomo.tracker.disable-ssl-cert-validation=true
34 | * matomo.tracker.disable-ssl-host-validation=true
35 | * matomo.tracker.thread-pool-size=2
36 | *
37 | *
38 | * @see MatomoTrackerAutoConfiguration
39 | * @see TrackerConfiguration
40 | */
41 | @ConfigurationProperties(prefix = "matomo.tracker")
42 | @Getter
43 | @Setter
44 | public class MatomoTrackerProperties {
45 |
46 | /**
47 | * The Matomo Tracking HTTP API endpoint, for example https://your-matomo-domain.example/matomo.php
48 | */
49 | private String apiEndpoint;
50 |
51 | /**
52 | * The default ID of the website that will be used if not specified explicitly.
53 | */
54 | private Integer defaultSiteId;
55 |
56 | /**
57 | * The authorization token (parameter token_auth) to use if not specified explicitly.
58 | */
59 | private String defaultAuthToken;
60 |
61 | /**
62 | * Allows to stop the tracker to send requests to the Matomo endpoint.
63 | */
64 | private Boolean enabled = true;
65 |
66 | /**
67 | * The timeout until a connection is established.
68 | *
69 | * A timeout value of zero is interpreted as an infinite timeout.
70 | * A `null` value is interpreted as undefined (system default if applicable).
71 | *
72 | * Default: 10 seconds
73 | */
74 | private Duration connectTimeout = Duration.ofSeconds(5L);
75 |
76 | /**
77 | * The socket timeout ({@code SO_TIMEOUT}), which is the timeout for waiting for data or, put differently, a maximum
78 | * period inactivity between two consecutive data packets.
79 | *
80 | * A timeout value of zero is interpreted as an infinite timeout.
81 | * A `null value is interpreted as undefined (system default if applicable).
82 | *
83 | * Default: 30 seconds
84 | */
85 | private Duration socketTimeout = Duration.ofSeconds(5L);
86 |
87 | /**
88 | * The hostname or IP address of an optional HTTP proxy. {@code proxyPort} must be configured as well
89 | */
90 | private String proxyHost;
91 |
92 | /**
93 | * The port of an HTTP proxy. {@code proxyHost} must be configured as well.
94 | */
95 | private Integer proxyPort;
96 |
97 | /**
98 | * If the HTTP proxy requires a username for basic authentication, it can be configured here. Proxy host, port and
99 | * password must also be set.
100 | */
101 | private String proxyUsername;
102 |
103 | /**
104 | * The corresponding password for the basic auth proxy user. The proxy host, port and username must be set as well.
105 | */
106 | private String proxyPassword;
107 |
108 | /**
109 | * A custom user agent to be set. Defaults to "MatomoJavaClient"
110 | */
111 | private String userAgent = "MatomoJavaClient";
112 |
113 | /**
114 | * Logs if the Matomo Tracking API endpoint responds with an erroneous HTTP code. Defaults to
115 | * false.
116 | */
117 | private Boolean logFailedTracking;
118 |
119 | /**
120 | * Disables SSL certificate validation. This is useful for testing with self-signed certificates.
121 | * Do not use in production environments. Defaults to false.
122 | *
123 | * Attention: This slows down performance
124 |
125 | * @see #disableSslHostVerification
126 | */
127 | private Boolean disableSslCertValidation;
128 |
129 | /**
130 | * Disables SSL host verification. This is useful for testing with self-signed certificates. Do
131 | * not use in production environments. Defaults to false.
132 | *
133 | *
Attention: This slows down performance
134 | *
135 | * @see #disableSslCertValidation
136 | */
137 | private Boolean disableSslHostVerification;
138 |
139 | /**
140 | * The thread pool size for the async sender. Defaults to 2.
141 | *
142 | *
Attention: If you use this library in a web application, make sure that this thread pool
143 | * does not exceed the thread pool of the web application. Otherwise, you might run into
144 | * problems.
145 | */
146 | private Integer threadPoolSize = 2;
147 |
148 | }
149 |
--------------------------------------------------------------------------------
/spring/src/main/java/org/matomo/java/tracking/spring/MatomoTrackerAutoConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Matomo Java Tracker
3 | *
4 | * @link https://github.com/matomo/matomo-java-tracker
5 | * @license https://github.com/matomo/matomo-java-tracker/blob/master/LICENSE BSD-3 Clause
6 | */
7 |
8 | package org.matomo.java.tracking.spring;
9 |
10 | import java.net.URI;
11 | import java.util.List;
12 | import org.matomo.java.tracking.MatomoTracker;
13 | import org.matomo.java.tracking.TrackerConfiguration;
14 | import org.matomo.java.tracking.servlet.MatomoTrackerFilter;
15 | import org.springframework.boot.autoconfigure.AutoConfiguration;
16 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
17 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
18 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
19 | import org.springframework.boot.web.servlet.FilterRegistrationBean;
20 | import org.springframework.context.annotation.Bean;
21 | import org.springframework.lang.NonNull;
22 |
23 | /**
24 | * {@link AutoConfiguration Auto configuration} for Matomo Tracker.
25 | *
26 | * @see MatomoTrackerProperties
27 | * @see TrackerConfiguration
28 | * @see MatomoTracker
29 | */
30 | @AutoConfiguration
31 | @ConditionalOnProperty(prefix = "matomo.tracker", name = "api-endpoint")
32 | @EnableConfigurationProperties(MatomoTrackerProperties.class)
33 | public class MatomoTrackerAutoConfiguration {
34 |
35 | /**
36 | * Creates a {@link TrackerConfiguration.TrackerConfigurationBuilder} and applies all
37 | * {@link TrackerConfigurationBuilderCustomizer}s. Can be overridden by custom beans.
38 | *
39 | * @param customizers the customizers to apply to the builder instance (never {@code null})
40 | * @return the {@link TrackerConfiguration.TrackerConfigurationBuilder} instance (never {@code null})
41 | * @see TrackerConfiguration#builder()
42 | */
43 | @Bean
44 | @ConditionalOnMissingBean
45 | @NonNull
46 | public TrackerConfiguration.TrackerConfigurationBuilder trackerConfigurationBuilder(
47 | @NonNull List customizers
48 | ) {
49 | TrackerConfiguration.TrackerConfigurationBuilder builder = TrackerConfiguration.builder();
50 | customizers.forEach(customizer -> customizer.customize(builder));
51 | return builder;
52 | }
53 |
54 | /**
55 | * Creates a {@link TrackerConfiguration} instance based on the current configuration. Can be
56 | * overridden by custom beans.
57 | *
58 | * If you define your own {@link TrackerConfiguration} bean, please don't forget to set the
59 | * API endpoint.
60 | *
61 | * @param builder the {@link TrackerConfiguration.TrackerConfigurationBuilder} instance (never {@code null})
62 | * @return the {@link TrackerConfiguration} instance (never {@code null})
63 | * @see TrackerConfiguration#builder()
64 | * @see TrackerConfiguration.TrackerConfigurationBuilder#apiEndpoint(URI)
65 | */
66 | @Bean
67 | @ConditionalOnMissingBean
68 | @NonNull
69 | public TrackerConfiguration trackerConfiguration(
70 | TrackerConfiguration.TrackerConfigurationBuilder builder
71 | ) {
72 | return builder.build();
73 | }
74 |
75 | /**
76 | * Configures the {@link TrackerConfiguration.TrackerConfigurationBuilder} with the properties from
77 | * {@link MatomoTrackerProperties}.
78 | *
79 | * @param properties the {@link MatomoTrackerProperties} instance (never {@code null})
80 | * @return the {@link StandardTrackerConfigurationBuilderCustomizer} instance (never {@code null})
81 | * @see MatomoTrackerProperties
82 | * @see TrackerConfiguration.TrackerConfigurationBuilder
83 | */
84 | @Bean
85 | @NonNull
86 | public StandardTrackerConfigurationBuilderCustomizer standardTrackerConfigurationBuilderCustomizer(
87 | @NonNull MatomoTrackerProperties properties
88 | ) {
89 | return new StandardTrackerConfigurationBuilderCustomizer(properties);
90 | }
91 |
92 | /**
93 | * A {@link MatomoTracker} instance based on the current configuration. Only created if a bean of the same type is not
94 | * already configured.
95 | *
96 | * @param trackerConfiguration the {@link TrackerConfiguration} instance (never {@code null})
97 | * @return the {@link MatomoTracker} instance (never {@code null})
98 | * @see MatomoTracker
99 | * @see TrackerConfiguration
100 | */
101 | @Bean
102 | @ConditionalOnMissingBean
103 | @NonNull
104 | public MatomoTracker matomoTracker(@NonNull TrackerConfiguration trackerConfiguration) {
105 | return new MatomoTracker(trackerConfiguration);
106 | }
107 |
108 | /**
109 | * A {@link FilterRegistrationBean} for the {@link MatomoTrackerFilter}.
110 | *
111 | *
Only created if a bean of the same type is not already configured. The filter is only registered if
112 | * {@code matomo.tracker.filter.enabled} is set to {@code true}.
113 | *
114 | * @param matomoTracker the {@link MatomoTracker} instance (never {@code null})
115 | * @return the {@link FilterRegistrationBean} instance (never {@code null})
116 | */
117 | @Bean
118 | @ConditionalOnProperty(value = "matomo.tracker.filter.enabled", havingValue = "true")
119 | @NonNull
120 | public FilterRegistrationBean matomoTrackerSpringFilter(
121 | @NonNull MatomoTracker matomoTracker
122 | ) {
123 | return new FilterRegistrationBean<>(new MatomoTrackerFilter(matomoTracker));
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | mail@daniel-heid.de.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/core/src/test/java/org/matomo/java/tracking/parameters/VisitorIdTest.java:
--------------------------------------------------------------------------------
1 | package org.matomo.java.tracking.parameters;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
5 |
6 | import java.util.stream.Stream;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.params.ParameterizedTest;
9 | import org.junit.jupiter.params.provider.Arguments;
10 | import org.junit.jupiter.params.provider.MethodSource;
11 | import org.junit.jupiter.params.provider.ValueSource;
12 |
13 | class VisitorIdTest {
14 |
15 | private static Stream validHexStrings() {
16 | return Stream.of(
17 | Arguments.of("0", "0000000000000000"),
18 | Arguments.of("0000", "0000000000000000"),
19 | Arguments.of("1", "0000000000000001"),
20 | Arguments.of("a", "000000000000000a"),
21 | Arguments.of("1a", "000000000000001a"),
22 | Arguments.of("01a", "000000000000001a"),
23 | Arguments.of("1a2b", "0000000000001a2b"),
24 | Arguments.of("1a2b3c", "00000000001a2b3c"),
25 | Arguments.of("1a2b3c4d", "000000001a2b3c4d"),
26 | Arguments.of("1a2b3c4d5e", "0000001a2b3c4d5e"),
27 | Arguments.of("1A2B3C4D5E", "0000001a2b3c4d5e"),
28 | Arguments.of("1a2b3c4d5e6f", "00001a2b3c4d5e6f"),
29 | Arguments.of("1a2b3c4d5e6f7a", "001a2b3c4d5e6f7a")
30 | );
31 | }
32 |
33 | @Test
34 | void hasCorrectFormat() {
35 |
36 | VisitorId visitorId = VisitorId.random();
37 |
38 | assertThat(visitorId.toString()).matches("^[a-z0-9]{16}$");
39 |
40 | }
41 |
42 | @Test
43 | void createsRandomVisitorId() {
44 |
45 | VisitorId first = VisitorId.random();
46 | VisitorId second = VisitorId.random();
47 |
48 | assertThat(first).doesNotHaveToString(second.toString());
49 |
50 | }
51 |
52 | @Test
53 | void fixedVisitorIdForLongHash() {
54 |
55 | VisitorId visitorId = VisitorId.fromHash(987654321098765432L);
56 |
57 | assertThat(visitorId).hasToString("0db4da5f49f8b478");
58 |
59 | }
60 |
61 | @Test
62 | void fixedVisitorIdForIntHash() {
63 |
64 | VisitorId visitorId = VisitorId.fromHash(777777777);
65 |
66 | assertThat(visitorId).hasToString("000000002e5bf271");
67 |
68 | }
69 |
70 | @Test
71 | void sameVisitorIdForSameHash() {
72 |
73 | VisitorId first = VisitorId.fromHash(1234567890L);
74 | VisitorId second = VisitorId.fromHash(1234567890);
75 |
76 | assertThat(first).hasToString(second.toString());
77 |
78 | }
79 |
80 | @Test
81 | void alwaysTheSameToString() {
82 |
83 | VisitorId visitorId = VisitorId.random();
84 |
85 | assertThat(visitorId).hasToString(visitorId.toString());
86 |
87 | }
88 |
89 | @Test
90 | void createsVisitorIdFrom16CharacterHex() {
91 |
92 | VisitorId visitorId = VisitorId.fromHex("1234567890abcdef");
93 |
94 | assertThat(visitorId).hasToString("1234567890abcdef");
95 |
96 | }
97 |
98 | @Test
99 | void createsVisitorIdFrom1CharacterHex() {
100 |
101 | VisitorId visitorId = VisitorId.fromHex("a");
102 |
103 | assertThat(visitorId).hasToString("000000000000000a");
104 |
105 | }
106 |
107 | @Test
108 | void createsVisitorIdFrom2CharacterHex() {
109 |
110 | VisitorId visitorId = VisitorId.fromHex("12");
111 |
112 | assertThat(visitorId).hasToString("0000000000000012");
113 |
114 | }
115 |
116 | @Test
117 | void failsOnInvalidHexString() {
118 |
119 | assertThatThrownBy(() -> VisitorId.fromHex("invalid123456789"))
120 | .isInstanceOf(IllegalArgumentException.class)
121 | .hasMessage("Input must be a valid hex string");
122 |
123 | }
124 |
125 | @ParameterizedTest
126 | @ValueSource(strings =
127 | {"g", "gh", "ghi", "ghij", "ghijk", "ghijkl", "ghijklm", "ghijklmn", "ghijklmn", "-1"})
128 | void failsOnInvalidHexString(String hex) {
129 | assertThatThrownBy(() -> VisitorId.fromHex(hex))
130 | .isInstanceOf(IllegalArgumentException.class)
131 | .hasMessage("Input must be a valid hex string");
132 | }
133 |
134 | @ParameterizedTest
135 | @MethodSource("validHexStrings")
136 | void createsVisitorIdFromHex(
137 | String hex, String expected
138 | ) {
139 |
140 | VisitorId visitorId = VisitorId.fromHex(hex);
141 |
142 | assertThat(visitorId).hasToString(expected);
143 |
144 | }
145 |
146 | @ParameterizedTest
147 | @ValueSource(strings = {"", " "})
148 | void failsOnEmptyStrings(String hex) {
149 | assertThatThrownBy(() -> VisitorId.fromHex(hex))
150 | .isInstanceOf(IllegalArgumentException.class)
151 | .hasMessage("Hex string must not be null or empty");
152 | }
153 |
154 | @ParameterizedTest
155 | @ValueSource(strings = {"1234567890abcdefg", "1234567890abcdeff"})
156 | void failsOnInvalidHexStringLength(String hex) {
157 | assertThatThrownBy(() -> VisitorId.fromHex(hex))
158 | .isInstanceOf(IllegalArgumentException.class)
159 | .hasMessage("Hex string must not be longer than 16 characters");
160 | }
161 |
162 | @Test
163 | void createsVisitorIdFromUUID() {
164 |
165 | VisitorId visitorId = VisitorId.fromUUID(
166 | java.util.UUID.fromString("12345678-90ab-cdef-1234-567890abcdef")
167 | );
168 |
169 | assertThat(visitorId).hasToString("1234567890abcdef");
170 |
171 | }
172 |
173 | @Test
174 | void failsOnNullUUID() {
175 | assertThatThrownBy(() -> VisitorId.fromUUID(null))
176 | .isInstanceOf(NullPointerException.class)
177 | .hasMessage("uuid is marked non-null but is null");
178 | }
179 |
180 | @Test
181 | void createsVisitorIdFromString() {
182 |
183 | VisitorId visitorId = VisitorId.fromString("test");
184 |
185 | assertThat(visitorId).hasToString("0000000000364492");
186 |
187 | }
188 |
189 | }
190 |
--------------------------------------------------------------------------------