diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a4e8d76 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,72 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + java-version: ['8', '11', '17', '21'] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK ${{ matrix.java-version }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java-version }} + distribution: 'temurin' + + - name: Cache Maven dependencies + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Run tests + run: mvn clean test + + - name: Build project + run: mvn clean package + + - name: Generate JavaDoc + run: mvn javadoc:javadoc + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results-java-${{ matrix.java-version }} + path: target/surefire-reports/ + + code-quality: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + + - name: Cache Maven dependencies + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Check for dependency updates + run: mvn versions:display-dependency-updates \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..64f777c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,144 @@ +name: Release to Maven Central + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + version: + description: 'Version to release (e.g., 1.5.4)' + required: true + type: string + +jobs: + release: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.set-version.outputs.version }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + + - name: Cache Maven dependencies + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Import GPG key + env: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + run: | + echo "$GPG_PRIVATE_KEY" | base64 --decode | gpg --batch --import + echo "allow-preset-passphrase" >> ~/.gnupg/gpg-agent.conf + echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf + gpg --list-secret-keys --keyid-format LONG + + - name: Configure Maven settings + env: + MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }} + run: | + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml << EOF + + + + central + ${MAVEN_CENTRAL_USERNAME} + ${MAVEN_CENTRAL_TOKEN} + + + + EOF + + - name: Set version from tag + id: set-version + if: startsWith(github.ref, 'refs/tags/') + run: | + VERSION=${GITHUB_REF#refs/tags/v} + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "version=$VERSION" >> $GITHUB_OUTPUT + mvn versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false + + - name: Set version from input + id: set-version-input + if: github.event_name == 'workflow_dispatch' + run: | + VERSION=${{ github.event.inputs.version }} + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "version=$VERSION" >> $GITHUB_OUTPUT + mvn versions:set -DnewVersion=$VERSION -DgenerateBackupPoms=false + + - name: Run tests + run: mvn clean test + + - name: Deploy to Maven Central + env: + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + run: | + mvn clean deploy -Dgpg.passphrase=$GPG_PASSPHRASE + + - name: Create GitHub Release + if: startsWith(github.ref, 'refs/tags/') + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const releaseBody = `## Mixpanel Java SDK v${process.env.VERSION} + + ### Maven + \`\`\`xml + + com.mixpanel + mixpanel-java + ${process.env.VERSION} + + \`\`\` + + ### Changes + See [CHANGELOG](https://github.com/mixpanel/mixpanel-java/blob/master/CHANGELOG.md) for details. + + ### Links + - [Maven Central](https://central.sonatype.com/artifact/com.mixpanel/mixpanel-java/${process.env.VERSION}) + - [JavaDoc](http://mixpanel.github.io/mixpanel-java/)`; + + await github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: context.ref.replace('refs/tags/', ''), + name: `Release ${process.env.VERSION}`, + body: releaseBody, + draft: false, + prerelease: false + }); + + verify: + needs: release + runs-on: ubuntu-latest + if: success() + + steps: + - name: Wait for Maven Central sync + run: sleep 300 # Wait 5 minutes for synchronization + + - name: Verify artifact on Maven Central + run: | + VERSION=${{ needs.release.outputs.version }} + RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://repo1.maven.org/maven2/com/mixpanel/mixpanel-java/${VERSION}/mixpanel-java-${VERSION}.jar) + if [ $RESPONSE -eq 200 ]; then + echo "✅ Artifact successfully published to Maven Central" + else + echo "⚠️ Artifact not yet available on Maven Central (HTTP $RESPONSE). This is normal - it may take up to 30 minutes to appear." + fi \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fb0b356 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,158 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is the official Mixpanel tracking library for Java - a production-ready library for sending analytics events and user profile updates to Mixpanel from Java server-side applications. + +## Release Process + +### Quick Commands for Releases + +```bash +# 1. Update version (remove -SNAPSHOT from pom.xml) +mvn versions:set -DnewVersion=1.5.4 + +# 2. Run tests +mvn clean test + +# 3. Deploy to Maven Central Portal +mvn clean deploy + +# 4. After release, prepare next version +mvn versions:set -DnewVersion=1.5.5-SNAPSHOT +``` + +### Key Files +- **RELEASE.md**: Complete release documentation with step-by-step instructions +- **.github/workflows/release.yml**: Automated release workflow triggered by version tags +- **.github/workflows/ci.yml**: Continuous integration for all PRs and master commits + +### Maven Central Portal +- The project uses the new Maven Central Portal (not the deprecated OSSRH) +- Deployments are visible at: https://central.sonatype.com/publishing/deployments +- Published artifacts: https://central.sonatype.com/artifact/com.mixpanel/mixpanel-java + +### Required GitHub Secrets for CI/CD +- `GPG_PRIVATE_KEY`: Base64-encoded GPG private key +- `GPG_PASSPHRASE`: GPG key passphrase +- `MAVEN_CENTRAL_USERNAME`: Maven Central Portal username +- `MAVEN_CENTRAL_TOKEN`: Maven Central Portal token + +## Build and Development Commands + +```bash +# Build the project and create JAR +mvn clean package + +# Run all tests +mvn test + +# Run a specific test class +mvn test -Dtest=MixpanelAPITest + +# Run a specific test method +mvn test -Dtest=MixpanelAPITest#testBuildEventMessage + +# Install to local Maven repository +mvn install + +# Generate JavaDoc +mvn javadoc:javadoc + +# Clean build artifacts +mvn clean + +# Run the demo application (after building) +java -cp target/mixpanel-java-1.5.3.jar:target/classes:lib/json-20231013.jar com.mixpanel.mixpanelapi.demo.MixpanelAPIDemo +``` + +## High-Level Architecture + +### Core Design Pattern +The library implements a **Producer-Consumer** pattern with intentional thread separation: + +1. **Message Production** (`MessageBuilder`): Creates properly formatted JSON messages on application threads +2. **Message Batching** (`ClientDelivery`): Collects messages into efficient batches (max 50 per request) +3. **Message Transmission** (`MixpanelAPI`): Sends batched messages to Mixpanel servers + +This separation allows for flexible threading models - the library doesn't impose any specific concurrency pattern, letting applications control their own threading strategy. + +### Key Architectural Decisions + +**No Built-in Threading**: Unlike some analytics libraries, this one doesn't start background threads. Applications must manage their own async patterns, as demonstrated in `MixpanelAPIDemo` which uses a `ConcurrentLinkedQueue` with producer/consumer threads. + +**Message Format Validation**: `MessageBuilder` performs validation during message construction, throwing `MixpanelMessageException` for malformed data before network transmission. + +**Batch Encoding**: Messages are JSON-encoded, then Base64-encoded, then URL-encoded for HTTP POST transmission. This triple encoding ensures compatibility with Mixpanel's API requirements. + +**Network Communication**: Uses Java's built-in `java.net.URL` and `URLConnection` classes - no external HTTP client dependencies. Connection timeout is 2 seconds, read timeout is 10 seconds. + +### Message Types and Endpoints + +The library supports three message categories, each sent to different endpoints: + +- **Events** (`/track`): User actions and behaviors +- **People** (`/engage`): User profile updates (set, increment, append, etc.) +- **Groups** (`/groups`): Group profile updates + +Each message type has specific JSON structure requirements validated by `MessageBuilder`. + +## Package Structure + +All production code is in the `com.mixpanel.mixpanelapi` package: + +- `MixpanelAPI`: HTTP communication with Mixpanel servers +- `MessageBuilder`: Constructs and validates JSON messages +- `ClientDelivery`: Batches multiple messages for efficient transmission +- `Config`: Contains API endpoints and configuration constants +- `Base64Coder`: Base64 encoding utility (modified third-party code) +- `MixpanelMessageException`: Runtime exception for message format errors +- `MixpanelServerException`: IOException for server rejection responses + +## Testing Approach + +Tests extend JUnit 4's `TestCase` and are located in `MixpanelAPITest`. The test suite covers: + +- Message format validation for all message types +- Property operations (set, setOnce, increment, append, union, remove, unset) +- Large batch delivery behavior +- Encoding verification +- Error conditions and exception handling + +When adding new functionality, follow the existing test patterns - each message type operation has corresponding test methods that verify both the JSON structure and the encoded format. + +## Common Development Tasks + +### Adding a New Message Type +1. Add the message construction method to `MessageBuilder` +2. Validate required fields and structure +3. Add corresponding tests in `MixpanelAPITest` +4. Update `ClientDelivery` if special handling is needed + +### Modifying Network Behavior +Network configuration is centralized in `MixpanelAPI.sendData()`. Connection and read timeouts are hardcoded but could be made configurable by modifying the `Config` class. + +### Debugging Failed Deliveries +The library throws `MixpanelServerException` with the HTTP response code and server message. Check: +1. Token validity in `MessageBuilder` constructor +2. Message size (batches limited to 50 messages) +3. JSON structure using the test suite patterns + +## Dependencies + +The library has minimal dependencies: +- **Production**: `org.json:json:20231013` for JSON manipulation +- **Test**: `junit:junit:4.13.2` for unit testing +- **Java Version**: Requires Java 8 or higher + +## API Patterns to Follow + +When working with this codebase: + +1. **Immutable Messages**: Once created by `MessageBuilder`, JSON messages should not be modified +2. **Fail Fast**: Validate message structure early in `MessageBuilder` rather than during transmission +3. **Preserve Thread Safety**: `MessageBuilder` instances are NOT thread-safe; create one per thread +4. **Batch Appropriately**: `ClientDelivery` handles batching; don't exceed 50 messages per delivery +5. **Exception Handling**: Distinguish between `MixpanelMessageException` (client error) and `MixpanelServerException` (server error) \ No newline at end of file diff --git a/README.md b/README.md index a4d254d..f96e562 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Latest Version ``` -You can alternatively download the library jar directly from Maven [here](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.mixpanel%22%20AND%20a%3A%22mixpanel-java%22). +You can alternatively download the library jar directly from Maven Central [here](https://central.sonatype.com/artifact/com.mixpanel/mixpanel-java). How To Use ---------- diff --git a/pom.xml b/pom.xml index 280b269..be3d320 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.mixpanel mixpanel-java - 1.5.3 + 1.5.4-SNAPSHOT jar mixpanel-java @@ -44,22 +44,24 @@ - ossrh - https://oss.sonatype.org/content/repositories/snapshots + central + https://central.sonatype.com/repository/maven-snapshots/ + - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 true - ossrh - https://oss.sonatype.org/ - false + central + mixpanel-java-${project.version} + + false @@ -93,7 +95,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.4 + 3.2.4 sign-artifacts @@ -101,22 +103,15 @@ sign + + + --pinentry-mode + loopback + + - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 - true - - ossrh - https://oss.sonatype.org/ - true - - -