diff --git a/.gitignore b/.gitignore index 62aa5740..7238112c 100644 --- a/.gitignore +++ b/.gitignore @@ -132,6 +132,9 @@ conf/local/ conf/*.local.cfg local.properties +# ActiveMQ data directory +data/ + ## Code Coverage ## jacoco*.exec *.lcov diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..462104e6 --- /dev/null +++ b/Makefile @@ -0,0 +1,112 @@ +# GOSS Makefile +# Provides version management and release automation + +.PHONY: help version release snapshot build test clean push-snapshot-local push-release-local \ + bump-patch bump-minor bump-major next-snapshot check-api format format-check + +# Default target +help: + @echo "GOSS Build and Release Management" + @echo "" + @echo "Usage:" + @echo " make version Show versions of all bundles" + @echo " make release VERSION=x.y.z Create a new release (removes -SNAPSHOT)" + @echo " make snapshot VERSION=x.y.z Set version with -SNAPSHOT suffix" + @echo " make build Build all bundles" + @echo " make test Run tests" + @echo " make clean Clean build artifacts" + @echo " make format Format all Java files using Spotless" + @echo " make format-check Check formatting without making changes" + @echo "" + @echo "Version bumping:" + @echo " make check-api Analyze API changes and suggest version bump type" + @echo " make next-snapshot Bump patch version after release (e.g., 11.0.0 -> 11.0.1-SNAPSHOT)" + @echo " make bump-patch Same as next-snapshot" + @echo " make bump-minor Bump minor version (e.g., 11.0.0 -> 11.1.0-SNAPSHOT)" + @echo " make bump-major Bump major version (e.g., 11.0.0 -> 12.0.0-SNAPSHOT)" + @echo "" + @echo "Repository targets (local ../GOSS-Repository):" + @echo " make push-snapshot-local Push snapshot JARs to ../GOSS-Repository/snapshot/" + @echo " make push-release-local Push release JARs to ../GOSS-Repository/release/" + @echo " Add FORCE=1 to overwrite existing JARs (e.g., make push-release-local FORCE=1)" + @echo "" + @echo "Release workflow:" + @echo " 1. make version # Check current version" + @echo " 2. make release VERSION=11.0.0 # Set release version" + @echo " 3. make build && make test # Build and test" + @echo " 4. make push-release-local # Push to GOSS-Repository" + @echo " 5. git tag v11.0.0 && git push # Tag and push" + @echo " 6. make next-snapshot # Bump to next snapshot" + @echo "" + @echo "Examples:" + @echo " make version" + @echo " make release VERSION=11.0.0" + @echo " make snapshot VERSION=11.1.0" + @echo " make build && make push-snapshot-local" + +# Show all bundle versions +version: + @python3 scripts/version.py show + +# Create a release (remove -SNAPSHOT suffix) +release: +ifndef VERSION + $(error VERSION is required. Usage: make release VERSION=x.y.z) +endif + @python3 scripts/version.py release $(VERSION) + +# Set snapshot version +snapshot: +ifndef VERSION + $(error VERSION is required. Usage: make snapshot VERSION=x.y.z) +endif + @python3 scripts/version.py snapshot $(VERSION) + +# Build all bundles +build: + ./gradlew build + +# Run tests +test: + ./gradlew check + +# Clean build artifacts +clean: + ./gradlew clean + +# Push snapshot JARs to local GOSS-Repository +push-snapshot-local: + @python3 push-to-local-goss-repository.py --snapshot $(if $(FORCE),--force,) + +# Push release JARs to local GOSS-Repository (also releases to cnf/releaserepo) +push-release-local: + ./gradlew release + @python3 push-to-local-goss-repository.py --release $(if $(FORCE),--force,) + +# Version bumping commands +bump-patch: + @python3 scripts/version.py bump-patch + +bump-minor: + @python3 scripts/version.py bump-minor + +bump-major: + @python3 scripts/version.py bump-major + +next-snapshot: + @python3 scripts/version.py next-snapshot + +# API change detection +check-api: + @python3 scripts/check-api.py + +# Code formatting targets (uses Spotless with Eclipse formatter) +format: + @echo "Formatting Java files..." + ./gradlew spotlessApply + @echo "Formatting complete." + +format-check: + @echo "Checking code formatting..." + ./gradlew spotlessCheck + @echo "Format check complete." diff --git a/README.md b/README.md index 39de5ef2..fbbb450d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # GridOPTICS Software System (GOSS) -Current GOSS build status: ![GOSS build status](https://travis-ci.org/GridOPTICS/GOSS.svg?branch=master) +[![Build Status](https://github.com/GridOPTICS/GOSS/actions/workflows/build.yml/badge.svg)](https://github.com/GridOPTICS/GOSS/actions) + +GOSS is a JMS-based messaging framework providing client/server architecture, request/response patterns, and security integration for distributed power grid applications. It serves as the foundation for [GridAPPS-D](https://github.com/GRIDAPPSD/GOSS-GridAPPS-D) and other grid simulation platforms. **⚠️ IMPORTANT: Java 21 + Jakarta EE Migration ⚠️** This project has been upgraded to **Java 21** with modern dependencies: @@ -138,6 +140,139 @@ cd /path/to/your/project ## Version 11.0.0 Features +### Queue and Topic Destination Type Support + +The Java client now supports both **Queue** and **Topic** destination types, matching the Python client's behavior: + +```java +import pnnl.goss.core.Client.DESTINATION_TYPE; + +// Subscribe to a queue (point-to-point, for request/response patterns) +client.subscribe("goss.gridappsd.process.request", handler, DESTINATION_TYPE.QUEUE); + +// Subscribe to a topic (pub/sub, for broadcast events) +client.subscribe("goss.gridappsd.simulation.output.123", handler, DESTINATION_TYPE.TOPIC); + +// Publish to a queue +client.publish("goss.gridappsd.process.request", message, DESTINATION_TYPE.QUEUE); + +// Publish to a topic +client.publish("goss.gridappsd.platform.log", message, DESTINATION_TYPE.TOPIC); + +// Send request and get response (defaults to QUEUE to match Python) +client.getResponse(request, "goss.gridappsd.process.request", RESPONSE_FORMAT.JSON); + +// Send request with explicit destination type +client.getResponse(request, "my.topic", RESPONSE_FORMAT.JSON, DESTINATION_TYPE.TOPIC); +``` + +**Key differences:** + +| Destination | Publishing Behavior | Subscribing Behavior | +|-------------|--------------------|--------------------| +| **QUEUE** | Message delivered to **one** consumer. If no consumers, message is **stored** until one connects. | Only one subscriber receives each message (load balancing). | +| **TOPIC** | Message delivered to **all** active subscribers. If no subscribers, message is **lost**. | All subscribers receive a copy of each message (broadcast). | + +**Default behaviors:** +- `getResponse()` defaults to **QUEUE** (matches Python client, ensures request reaches the handler) +- `subscribe()` defaults to **TOPIC** (typical for event streaming) +- `publish(destination, message)` defaults to **TOPIC** (for backward compatibility) + +### Command-Line Client (goss-cli.jar) + +A new CLI tool for subscribing and publishing to GOSS queues/topics: + +```bash +# Build the CLI +./gradlew :pnnl.goss.core.runner:createCli + +# Subscribe to a queue +java -jar pnnl.goss.core.runner/generated/executable/goss-cli.jar subscribe --queue goss.gridappsd.process.request + +# Subscribe to a topic +java -jar pnnl.goss.core.runner/generated/executable/goss-cli.jar sub --topic goss.gridappsd.simulation.output.123 + +# Publish to a queue (default) +java -jar pnnl.goss.core.runner/generated/executable/goss-cli.jar publish goss.gridappsd.process.request '{"type":"query"}' + +# Publish to a topic +java -jar pnnl.goss.core.runner/generated/executable/goss-cli.jar pub --topic goss.gridappsd.platform.log 'Test message' + +# With authentication and custom broker +java -jar pnnl.goss.core.runner/generated/executable/goss-cli.jar sub -b tcp://activemq:61616 -u admin -p admin -q my.queue +``` + +**Options:** +- `-t, --topic` - Use a topic (default for subscribe) +- `-q, --queue` - Use a queue (default for publish) +- `-b, --broker URL` - Broker URL (default: tcp://localhost:61616) +- `-u, --user USER` - Username for authentication +- `-p, --password PW` - Password for authentication +- `-h, --help` - Show help message + +#### Example: Topic (Broadcast to All Subscribers) + +```bash +# Terminal 1 - Start first subscriber +$ java -jar goss-cli.jar sub --topic events +GOSS Subscriber +=============== +Broker: tcp://localhost:61616 +Destination: events +Type: TOPIC + +Connected! Waiting for messages... (Ctrl+C to stop) + +# Terminal 2 - Start second subscriber +$ java -jar goss-cli.jar sub --topic events +Connected! Waiting for messages... (Ctrl+C to stop) + +# Terminal 3 - Publish a message +$ java -jar goss-cli.jar pub --topic events "Hello everyone!" +Message published successfully! + +# Result: BOTH Terminal 1 AND Terminal 2 receive: +--- Message Received --- +Hello everyone! +------------------------ +``` + +#### Example: Queue (Load Balanced, One Consumer Per Message) + +```bash +# Terminal 1 - Start first consumer +$ java -jar goss-cli.jar sub --queue tasks +GOSS Subscriber +=============== +Broker: tcp://localhost:61616 +Destination: tasks +Type: QUEUE + +Connected! Waiting for messages... (Ctrl+C to stop) + +# Terminal 2 - Start second consumer +$ java -jar goss-cli.jar sub --queue tasks +Connected! Waiting for messages... (Ctrl+C to stop) + +# Terminal 3 - Publish messages +$ java -jar goss-cli.jar pub --queue tasks "Task 1" +$ java -jar goss-cli.jar pub --queue tasks "Task 2" + +# Result: Terminal 1 receives "Task 1", Terminal 2 receives "Task 2" (load balanced) +# Each message goes to only ONE consumer + +# Queue persistence - messages wait for consumers: +$ java -jar goss-cli.jar pub --queue waiting "I'll wait here" +# (no subscribers running - message is stored) + +# Later, start a subscriber: +$ java -jar goss-cli.jar sub --queue waiting +--- Message Received --- +I'll wait here +------------------------ +# Message was stored and delivered when consumer connected! +``` + ### JWT Token Authentication Support GOSS now includes optional JWT (JSON Web Token) authentication support: @@ -164,20 +299,128 @@ goss.system.manager.password=admin-password ### Session Auto-Renewal Clients now automatically renew their JMS session when publish operations fail, improving reliability in long-running applications. -### Release Management -Use the included `release.sh` script for version management: +### Version Management + +GOSS uses [Semantic Versioning](https://semver.org/) with automated API change detection: + +| Change Type | Version Bump | Example | +|-------------|--------------|---------| +| **MAJOR** | X.0.0 | Interface changes, removed public methods, breaking changes | +| **MINOR** | x.Y.0 | New public methods on classes, new classes (backward compatible) | +| **PATCH** | x.y.Z | Implementation-only changes, bug fixes | + +#### Basic Commands + +```bash +make version # Show versions of all bundles +make build # Build all bundles +make test # Run tests +make clean # Clean build artifacts +``` + +#### API Change Detection + +Before bumping versions, analyze your changes to determine the appropriate version bump: + +```bash +make check-api # Analyze API changes and get recommendation +``` + +Example output: +``` +API Change Analysis +============================================================ +pnnl.goss.core.core-api + MAJOR changes detected: + - Interface method removed: public abstract void publish(javax.jms.Destination, ...) + - Interface method added: public abstract void publish(jakarta.jms.Destination, ...) + +pnnl.goss.core.goss-client + MINOR changes detected: + - Public method added: public void reconnect() + +pnnl.goss.core.goss-core-commands + No API changes +============================================================ +Recommended Version Bump: + MAJOR - Breaking API changes detected + Run: make bump-major +``` + +#### Version Bumping Commands + +```bash +# Automatic version bumping (reads current version, increments appropriately) +make bump-major # 11.0.0 -> 12.0.0-SNAPSHOT (breaking changes) +make bump-minor # 11.0.0 -> 11.1.0-SNAPSHOT (new features) +make bump-patch # 11.0.0 -> 11.0.1-SNAPSHOT (bug fixes) +make next-snapshot # Same as bump-patch (use after release) + +# Manual version setting +make release VERSION=11.0.0 # Set exact release version (removes -SNAPSHOT) +make snapshot VERSION=11.1.0 # Set exact snapshot version (adds -SNAPSHOT) +``` + +#### Complete Release Workflow ```bash -# Prepare release (remove -SNAPSHOT) -./release.sh release # 11.0.0-SNAPSHOT → 11.0.0 +# 1. Analyze changes to determine version bump type +make check-api + +# 2. If currently on snapshot, set release version +make version # Verify: 11.0.0-SNAPSHOT +make release VERSION=11.0.0 # Changes to: 11.0.0 + +# 3. Build, test, and push release +make build && make test +make push-release # Push to ../GOSS-Repository/release/ + +# 4. Tag and commit release +git add -A && git commit -m "Release 11.0.0" +git tag v11.0.0 +git push && git push --tags + +# 5. Start next development cycle +make next-snapshot # Bumps to: 11.0.1-SNAPSHOT +git add -A && git commit -m "Start 11.0.1-SNAPSHOT development" +git push +``` + +### Publishing to GOSS-Repository -# Set specific version -./release.sh release 11.1.0 # Set all to 11.1.0 +GOSS bundles can be published to a local [GOSS-Repository](https://github.com/GridOPTICS/GOSS-Repository) clone for OSGi resolution: -# Return to development -./release.sh bump # 11.0.0 → 11.0.1-SNAPSHOT +```bash +# Push snapshot JARs to ../GOSS-Repository/snapshot/ +make push-snapshot + +# Push release JARs to ../GOSS-Repository/release/ +make push-release ``` +**Note:** The GOSS-Repository must be cloned as a sibling directory (`../GOSS-Repository`). This is the local repository used for BND workspace resolution, not a remote Maven repository. + +### API Compatibility Guidelines + +When making changes to GOSS, follow these guidelines: + +**MAJOR version bump required:** +- Adding or removing methods from an **interface** (breaks implementors) +- Removing public methods from a class +- Changing method signatures (parameters, return types) +- Changing class hierarchy (superclass, implemented interfaces) + +**MINOR version bump required:** +- Adding new public methods to a **class** (not interface) +- Adding new classes +- Adding new packages + +**PATCH version bump required:** +- Bug fixes with no API changes +- Performance improvements +- Internal refactoring +- Documentation updates + ## Documentation ### Getting Started @@ -302,3 +545,61 @@ import jakarta.annotation.PostConstruct; **Removed Java EE APIs:** - All `javax.jms`, `javax.annotation`, etc. → Use Jakarta equivalents + +## Project Structure + +``` +GOSS/ +├── pnnl.goss.core/ # Core bundles +│ ├── core-api/ # Core API interfaces +│ ├── goss-client/ # Client implementation +│ ├── goss-core-server/ # Server implementation +│ ├── goss-core-server-api/ # Server API interfaces +│ ├── goss-core-security/ # Security integration (Shiro) +│ └── security-propertyfile/ # Property-file authentication +├── pnnl.goss.core.runner/ # Executable runners +│ ├── goss-core.bndrun # OSGi runtime definition +│ └── conf/ # Runtime configuration +├── buildSrc/ # Gradle plugins (BndRunnerPlugin) +├── cnf/ # BND workspace configuration +├── scripts/ # Build and release scripts +├── Makefile # Build automation +└── push-to-local-goss-repository.py # Repository publishing tool +``` + +## Users of GOSS + +GOSS serves as the messaging foundation for: + +- **[GridAPPS-D](https://github.com/GRIDAPPSD/GOSS-GridAPPS-D)** - Grid Application Platform for Planning and Simulation with Distribution. GridAPPS-D is built as an OSGi application on top of GOSS, using its messaging framework for simulation orchestration, data management, and application integration. + +## Technology Stack + +- **Java 21** with modern language features +- **OSGi** (Apache Felix 7.0.5) for modular service architecture +- **BND Tools 6.4.0** for OSGi bundle management +- **Apache ActiveMQ 6.2.0** for JMS messaging (Jakarta EE compatible) +- **Apache Shiro 2.0** for authentication and authorization +- **Gradle 8.10** build system + +## Related Repositories + +| Repository | Description | +|------------|-------------| +| [GOSS-GridAPPS-D](https://github.com/GRIDAPPSD/GOSS-GridAPPS-D) | GridAPPS-D platform built on GOSS | +| [GOSS-Repository](https://github.com/GridOPTICS/GOSS-Repository) | OSGi bundle repository for BND resolution | +| [gridappsd-docker](https://github.com/GRIDAPPSD/gridappsd-docker) | Docker deployment for GridAPPS-D | +| [gridappsd-python](https://github.com/GRIDAPPSD/gridappsd-python) | Python client library | + +## License + +This project is licensed under the BSD-3-Clause License. See [LICENSE](LICENSE) for details. + +## Contributing + +Contributions are welcome! Please submit pull requests to the `develop` branch. + +## Support + +- **Documentation**: See the [docs/](docs/) directory +- **Issues**: https://github.com/GridOPTICS/GOSS/issues diff --git a/build/config/LDAPLogin.xml b/build/config/LDAPLogin.xml deleted file mode 100644 index 81bbda49..00000000 --- a/build/config/LDAPLogin.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - debug=true - initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory - connectionURL=ldap://${ldap.host}:${ldap.port} - connectionUsername=${ldap.connection.user} - connectionPassword=${ldap.connection.pw} - connectionProtocol= - authentication=simple - userBase=ou=users,${ldap.goss.base} - userSearchMatching=(uid={0}) - userSearchSubtree=false - roleBase=ou=groups,${ldap.goss.base} - roleName=cn - roleSearchMatching=(member=uid={1}) - roleSearchSubtree=false - - - - - - - - - - \ No newline at end of file diff --git a/build/config/features.xml b/build/config/features.xml deleted file mode 100644 index a1868f66..00000000 --- a/build/config/features.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - mvn:org.apache.cxf.karaf/apache-cxf/2.7.13/xml/features - mvn:org.apache.activemq/activemq-karaf/5.10.0/xml/features - - - activemq - mvn:pnnl.goss/goss-activemq-broker-nosecurity/0.2.0-SNAPSHOT/cfg/goss-broker-config - mvn:pnnl.goss/goss-activemq-broker-nosecurity/0.2.0-SNAPSHOT/xml/goss-broker-nosecurity - - - - mvn:eu.infomas/annotation-detector/3.0.4 - mvn:org.codehaus.groovy/groovy-all/2.3.3 - - mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.xstream/1.4.3_1 - mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.commons-io/1.4_3 - mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.commons-dbcp/1.4_3 - mvn:com.google.code.gson/gson/2.3 - mvn:mysql/mysql-connector-java/5.1.33 - mvn:org.apache.httpcomponents/httpcore-osgi/4.3.3 - mvn:org.apache.httpcomponents/httpclient-osgi/4.3.3 - - mvn:org.fusesource.stompjms/stompjms-client/1.19 - mvn:org.fusesource.hawtdispatch/hawtdispatch-transport/1.21 - mvn:org.fusesource.hawtdispatch/hawtdispatch/1.21 - mvn:org.fusesource.hawtbuf/hawtbuf/1.11 - mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.reflections/0.9.8_1 - transaction - openjpa - jndi - activemq - cxf - - - - - - goss-dependencies - - - mvn:pnnl.goss/goss-core-security-utils/0.2.0-SNAPSHOT - - mvn:pnnl.goss/goss-core/0.2.0-SNAPSHOT - mvn:pnnl.goss/goss-core-client/0.2.0-SNAPSHOT - mvn:pnnl.goss/goss-core-security/0.2.0-SNAPSHOT - mvn:pnnl.goss/goss-core-server/0.2.0-SNAPSHOT - - - - - goss-core-feature - http - war - - mvn:pnnl.goss/goss-core-web/0.2.0-SNAPSHOT/war - - diff --git a/build/config/pnnl-goss-activemq-broker-nosecurity.xml b/build/config/pnnl-goss-activemq-broker-nosecurity.xml deleted file mode 100644 index 8ee2eca5..00000000 --- a/build/config/pnnl-goss-activemq-broker-nosecurity.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/config/pnnl-goss-activemq-broker.xml b/build/config/pnnl-goss-activemq-broker.xml deleted file mode 100644 index 3b97920a..00000000 --- a/build/config/pnnl-goss-activemq-broker.xml +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/build/config/pnnl.goss.activemq.server-default.cfg b/build/config/pnnl.goss.activemq.server-default.cfg deleted file mode 100644 index 31dbcafe..00000000 --- a/build/config/pnnl.goss.activemq.server-default.cfg +++ /dev/null @@ -1,16 +0,0 @@ -# This file is read by the default activemq server bits. It should be located -# in the etc directory of a karaf instances under the filename -# org.apache.activemq.server-default.cfg. - -# Transport protocols -activemq.openwire.port = 61616 -activemq.stomp.port = 61613 -activemq.ws.port = 61614 - -# By default the application will use one of the following broker files -goss.use.authorization = false - -# These are items that get installed -broker-name=goss-activemq-broker -data=${karaf.data}/${broker-name} -config=${karaf.base}/etc/pnnl-goss-activemq-broker.xml diff --git a/build/config/pnnl.goss.core.cfg b/build/config/pnnl.goss.core.cfg deleted file mode 100644 index ea034a74..00000000 --- a/build/config/pnnl.goss.core.cfg +++ /dev/null @@ -1,18 +0,0 @@ -# Configuration for the goss-core module. Each jar in -# goss-core can be configured using the following. During -# compilation 'mvn compile' the place holders @@ will be -# replaced with values from a global goss.properties file. - -# The following are used when creating the broker. -goss.openwire.uri = tcp://localhost:61616 -goss.stomp.uri = tcp://localhost:61617 - -# For connecting to the server using authorization -goss.system.user = goss -goss.system.password = goss - -# Should authorization be used. -goss.use.authorization = true -goss.ldap.uri = ldap://localhost:10389 -goss.ldap.admin.user = ldap -goss.ldap.admin.password = ldap diff --git a/build/config/pnnl.goss.core.client.cfg b/build/config/pnnl.goss.core.client.cfg deleted file mode 100644 index 545060bf..00000000 --- a/build/config/pnnl.goss.core.client.cfg +++ /dev/null @@ -1,8 +0,0 @@ -# Configuration for the goss-core module. Each jar in -# goss-core can be configured using the following. During -# compilation 'mvn compile' the place holders @@ will be -# replaced with values from a global goss.properties file. - -# The following are used for the core-client connection. -goss.openwire.uri = tcp://localhost:61616 -goss.stomp.uri = tcp://localhost:61613 diff --git a/build/config/pnnl.goss.datasources.cfg b/build/config/pnnl.goss.datasources.cfg deleted file mode 100644 index 2d6d0223..00000000 --- a/build/config/pnnl.goss.datasources.cfg +++ /dev/null @@ -1,24 +0,0 @@ -# Properties for Powergrid datasources -powergrid.db.uri = jdbc:mysql://localhost:3306 -powergrid.db.user = root -powergrid.db.password = rootpass - -# Mysql datasource for fusion project. -fusion.db.uri = jdbc:mysql://eioc-goss:3306/fusion -fusion.db.user = root -fusion.db.password = goss!4evr - -# Mysql datasource for mdart project. -mdart.db.uri = jdbc:mysql://localhost:3306/mdart -mdart.db.user = root -mdart.db.password = rootpass - -# Mysql datasource for gridmw project. -gridmw.db.uri = jdbc:mysql://localhost:3306/gridopticsdb -gridmw.db.user = root -gridmw.db.password = rootpass - -# Mysql datasource for tutorial project. -tutorial.db.uri = jdbc:mysql://localhost:3306/tutorial -tutorial.db.user = root -tutorial.db.password = rootpass \ No newline at end of file diff --git a/buildSrc/src/main/groovy/com/pnnl/goss/gradle/BndRunnerPlugin.groovy b/buildSrc/src/main/groovy/com/pnnl/goss/gradle/BndRunnerPlugin.groovy index 59263377..8c1bfa71 100644 --- a/buildSrc/src/main/groovy/com/pnnl/goss/gradle/BndRunnerPlugin.groovy +++ b/buildSrc/src/main/groovy/com/pnnl/goss/gradle/BndRunnerPlugin.groovy @@ -57,6 +57,7 @@ class BndRunnerPlugin implements Plugin { // Core OSGi services - updated versions gossRuntime 'org.apache.felix:org.apache.felix.scr:2.2.12' gossRuntime 'org.apache.felix:org.apache.felix.configadmin:1.9.26' + gossRuntime 'org.apache.felix:org.apache.felix.fileinstall:3.7.4' gossRuntime 'org.apache.felix:org.apache.felix.gogo.runtime:1.1.6' gossRuntime 'org.apache.felix:org.apache.felix.gogo.shell:1.1.4' gossRuntime 'org.apache.felix:org.apache.felix.gogo.command:1.1.2' diff --git a/cnf/build.bnd b/cnf/build.bnd index 879496ae..18e8635c 100644 --- a/cnf/build.bnd +++ b/cnf/build.bnd @@ -50,8 +50,9 @@ javac.target: 21 ## To enable baselining, uncomment the following lines: --baseline: * --baselinerepo: Release +## Baselining temporarily disabled - no previous release to compare against +#-baseline: * +#-baselinerepo: Release copyright = Copyrigth 2015 Pacific Northwest National Laboratory (PNNL) ## If you use git, you might want to uncomment the following lines: diff --git a/cnf/ext/central.maven b/cnf/ext/central.maven index 4c2dd8ea..dcbc0711 100644 --- a/cnf/ext/central.maven +++ b/cnf/ext/central.maven @@ -22,6 +22,7 @@ org.apache.felix:org.apache.felix.gogo.shell:1.1.4 org.apache.felix:org.apache.felix.http.servlet-api:3.0.0 org.apache.felix:org.apache.felix.http.jetty:5.1.26 org.apache.felix:org.apache.felix.log:1.3.0 +org.apache.felix:org.apache.felix.fileinstall:3.7.4 # Pax Logging - OSGi logging framework org.ops4j.pax.logging:pax-logging-api:2.2.7 diff --git a/cnf/ext/libraries.bnd b/cnf/ext/libraries.bnd index 2af7ed14..fbb528bf 100644 --- a/cnf/ext/libraries.bnd +++ b/cnf/ext/libraries.bnd @@ -68,6 +68,12 @@ configadmin: ${repo;org.apache.felix:org.apache.felix.configadmin;[1.9.0,2);HIGH configadmin-buildpath: ${configadmin};version=file configadmin-runpath: ${configadmin};version=file +# FileInstall - Watches conf directory for .cfg files and loads them into ConfigAdmin +# This is required for GOSS security realms to activate (they use configurationPolicy=REQUIRE) +fileinstall: ${repo;org.apache.felix:org.apache.felix.fileinstall;[3.7.0,4);HIGHEST} +fileinstall-buildpath: ${fileinstall};version=file +fileinstall-runpath: ${fileinstall};version=file + # Gogo Shell - Latest gogo-command: ${repo;org.apache.felix:org.apache.felix.gogo.command;[1.1.0,2);HIGHEST} gogo-runtime: ${repo;org.apache.felix:org.apache.felix.gogo.runtime;[1.1.0,2);HIGHEST} diff --git a/cnf/releaserepo/index.xml b/cnf/releaserepo/index.xml index 18b985f7..10755260 100644 --- a/cnf/releaserepo/index.xml +++ b/cnf/releaserepo/index.xml @@ -1,401 +1,431 @@ - + - + - - - - - - - - - + - - + + - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - + - - - + + - - - + + - - + + + - + - - - - - - - - + - - - - - - + + + + + + - - + + - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - + + - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + - + + + + - + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + - - + + - - + + - + - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + - + + + + - + - - - - - - - - - - - - - - - - + - - + + - - + + - + - - + + - - + + - - + + + + + + - - + + - + + + + + - + + + + + + + + - + - + + + + + + + + + - - + + - - + + - + - - + + + + + + + + + + - + + + + + @@ -403,137 +433,170 @@ - - + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - - - + + + + + + - - + + - - + + - + - - + + - - - - @@ -543,21 +606,9 @@ - - - - - - - - - + - - - - @@ -566,345 +617,361 @@ - + - + - - + + - - + + - + - - + + + + + + - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - + + - - + + - - - + + - - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - + - + - + - - + + - - + + - + - - + + + + + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + - + + + + - + - + - - + + - - - - - - - - - - + + - - + + - + - - + + + + + + - - + + - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - + + + + - + - - - - - - - - - + - - + + - - + + - + - - + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + - + - + + + + + - + @@ -912,97 +979,102 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - + + + - - - + + + + + - - - + + + - - - + + + + + - - - + + + - - - + + + + + - - + + + + + - + - + - - + + - - + + - + - - + + - - + + + + + + + + + + + + + + + + + + + + + + @@ -1012,33 +1084,41 @@ - - + + - - + + - - + + - - + + - - + + - + + + + + + + + + @@ -1046,203 +1126,234 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - + - - + + - - + + - + - - + + - - + + - - + + - - + + + + + + + + + + + + + + - + + + + + + + + - + - + + + + + + + + + - - + + - - + + - + - - + + - - - - - - - - - - - - - - - - - - + + - - + + - + - - - - - - - - - + - + - + + + + + + + + - - + + - - + + - + - - + + - - + + - - + + - + - + - + + + + + + + + + + + + + + + + - - + + - - + + - + - - + + - - - + + - - - + + - - - - - - - - - - - - - - - - - - + + @@ -1257,17 +1368,20 @@ - - + + - - + + - - - + + + + + + @@ -1277,185 +1391,176 @@ - + - + - - - - - - + + + + + - - + + - - + + - + - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - + + + + - + - - - - - - - - - - - - - - - - + - - + + - - + + - + - - + + + + + + - - - - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - + - - - - - - - - + - - - - - - + + + + + + - - + + - - + + - + - - + + + + + + @@ -1482,61 +1587,85 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - + + + + - + - - + + - - + + - + - - + + + + + + - - + + @@ -1546,109 +1675,55 @@ - + - + + + + + + + + + - - + + - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - + + + - - + + - - + + - - + + - - + + @@ -1662,10 +1737,6 @@ - - - - @@ -1679,17 +1750,49 @@ - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1699,17 +1802,20 @@ - + - + + + + @@ -1721,33 +1827,12 @@ - - - - - - - - - - - - - - - - - - - - - + - - + @@ -1757,255 +1842,228 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - + + + - + + + + + + + + + - - + + - - + + - + - - + + - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + - + - - - - - - + + + + + + - - + + - - + + - + - - + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + - + + + + - + - - - - - - - - + - - - - - - + + + + + + - - + + - - + + - + - - + + + + + + + + + + - + + + + + @@ -2013,54 +2071,52 @@ - - + + - - + + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -2071,19 +2127,19 @@ - + - + - + - + @@ -2091,200 +2147,254 @@ - - - - - - - - - + - + - - - - - + - + - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - - - + + + + + + - - + + - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + - - + + - - + + - + - + + + + + + + + + - + - - - + + + + + + + + + + + + + + + + - + - + - + - + - - + + - + - + - + - - + + - + @@ -2292,4771 +2402,646 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - - - - - - + + + + + + - - + + - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - + - + - - - - - + - + - + - - + + - - + + - + - - + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + - + + + + + + + + - + - + - - + + - - + + - + - - + + + + + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + - + + + + - + - + - - + + - - + + - + - - + + - - + + + - - + + + - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - + + + - - + + + - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - - + + - - + + - + - - + + + + + + - + - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - + + - + - - - - - - - - - - - - - - - - + + + + - + + + + - + - - - - - - - - + - - - - - - + + + + + + - - + + - - + + - + - - + + - - - - - - + + + + + + - - + + + + + + + + + + + + + + - + - + - + - - + + - - + + - + - - + + - - + + + - - + + + - - + + + - - + + + - - + + + - - + + + - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -7066,6 +3051,10 @@ + + + + @@ -7075,432 +3064,301 @@ - - + + - - + + - - + + - - + + - - + + + - - - - + - + + + + + + + + - - + + - - + + - + - - + + - - + + - - + + - + - + - + - - + + - - + + - + - - + + - - - - - - + + + - - + + + - - + + + - - + + + - - + + + - - + + + - - + + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - + - - + + - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + - + - + - + - - + + - - + + - + - - + + + + + + + + + + + + + + - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - + - - + + - + - + - + @@ -7508,138 +3366,154 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - - - + + + + + + - - + + - - + + - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - + - + - - - - - + - + - - - - - - - - + - - - - - - + + + + + + - - + + - - + + - + - - + + + + + + + + + + - + + + + + @@ -7647,54 +3521,52 @@ - - + + - - + + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -7705,19 +3577,19 @@ - + - + - + - + @@ -7725,90 +3597,133 @@ - - - - - - - - - + - + - - - - - + - + - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - + + - - + + - + - - + + + + + + - - + + - - - - - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7817,7 +3732,7 @@ - + @@ -7825,35 +3740,51 @@ - + - + - - - - - + + + + + - - + + - - + + - + - - + + + + + + + + + + + + + + + + + + @@ -7861,190 +3792,170 @@ + + + - + - + - - + + - - + + - + - - + + + + + + - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - + + + + + + + + - + - + - - + + - - + + - + - - + + + + + + - - - - - - + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - + - - + + - + + + + + + + + diff --git a/cnf/releaserepo/index.xml.sha b/cnf/releaserepo/index.xml.sha index 527b3b88..2b1e1281 100644 --- a/cnf/releaserepo/index.xml.sha +++ b/cnf/releaserepo/index.xml.sha @@ -1 +1 @@ -a94fef9083251b5151275f06cb9c810859ba022dba41d64ce517294c1b335c9f \ No newline at end of file +d9805919b8a7bf9193720ac4c556ea0c6af88e6db1fd2e9911b6971aace17ed2 \ No newline at end of file diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.0.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.0.jar deleted file mode 100644 index 6b58ecbb..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.0.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.1.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.1.jar deleted file mode 100644 index 4fd826c0..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.1.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.10.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.10.jar deleted file mode 100644 index df8f13b5..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.10.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.2.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.2.jar deleted file mode 100644 index 8aca2709..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.2.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.3.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.3.jar deleted file mode 100644 index 38540d29..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.3.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.4.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.4.jar deleted file mode 100644 index 8ab289e9..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.4.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.5.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.5.jar deleted file mode 100644 index eb9907be..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.5.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.6.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.6.jar deleted file mode 100644 index c9117ee7..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.6.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.7.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.7.jar deleted file mode 100644 index 1e161732..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.7.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.8.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.8.jar deleted file mode 100644 index 2b6cfef8..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.8.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.9.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.9.jar deleted file mode 100644 index ace912c7..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-10.0.9.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-11.0.0.jar index 3243caef..35e791e2 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-8.0.0.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-12.0.0.jar similarity index 73% rename from cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-8.0.0.jar rename to cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-12.0.0.jar index 3a77a7ed..8d3d8ff5 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-8.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-7.1.2.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-12.1.0.jar similarity index 73% rename from cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-7.1.2.jar rename to cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-12.1.0.jar index abab5354..d3d8a170 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-7.1.2.jar and b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-7.1.1.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-7.1.1.jar deleted file mode 100644 index a1431a76..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-7.1.1.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-9.1.66.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-9.1.66.jar deleted file mode 100644 index f6db228c..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-9.1.66.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-9.1.9.jar b/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-9.1.9.jar deleted file mode 100644 index 6a7eba2e..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.core-api/pnnl.goss.core.core-api-9.1.9.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-11.0.0.jar index 72c0b6aa..89c1362e 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-12.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-12.0.0.jar new file mode 100644 index 00000000..0bbdda2f Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-12.1.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-12.1.0.jar new file mode 100644 index 00000000..49c1d7c6 Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-2.0.29.jar b/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-2.0.29.jar deleted file mode 100644 index 2d51c955..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-2.0.29.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-2.0.30.jar b/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-2.0.30.jar deleted file mode 100644 index 350b57ef..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-2.0.30.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-3.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-3.0.0.jar deleted file mode 100644 index bfd3089d..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-client/pnnl.goss.core.goss-client-3.0.0.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-11.0.0.jar index 23ef41f6..f5dcfc12 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-12.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-12.0.0.jar new file mode 100644 index 00000000..6faff792 Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-12.1.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-12.1.0.jar new file mode 100644 index 00000000..aa98d7de Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-2.0.18.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-2.0.18.jar deleted file mode 100644 index 7f129902..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-2.0.18.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-2.0.19.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-2.0.19.jar deleted file mode 100644 index 47babaa0..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-2.0.19.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-3.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-3.0.0.jar deleted file mode 100644 index 6cd80488..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-commands/pnnl.goss.core.goss-core-commands-3.0.0.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-11.0.0.jar index 3049d25b..f9fc1cd1 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-2.1.1.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-12.0.0.jar similarity index 64% rename from cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-2.1.1.jar rename to cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-12.0.0.jar index 4594065e..fb46318f 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-2.1.1.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-3.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-12.1.0.jar similarity index 64% rename from cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-3.0.0.jar rename to cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-12.1.0.jar index 35928f08..27bbd4dd 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-3.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-2.1.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-2.1.0.jar deleted file mode 100644 index 5ebfa747..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-exceptions/pnnl.goss.core.goss-core-exceptions-2.1.0.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-11.0.0.jar index d9d01565..00385878 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-12.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-12.0.0.jar new file mode 100644 index 00000000..8d9a715c Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-12.1.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-12.1.0.jar new file mode 100644 index 00000000..abfabe0e Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-2.1.17.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-2.1.17.jar deleted file mode 100644 index 8fb04ed1..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-2.1.17.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-2.1.18.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-2.1.18.jar deleted file mode 100644 index 5c059adc..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-2.1.18.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-3.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-3.0.0.jar deleted file mode 100644 index 4443d97b..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-3.0.0.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.0.jar deleted file mode 100644 index 7965a912..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.0.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.1.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.1.jar deleted file mode 100644 index ef1bebb9..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.1.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.2.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.2.jar deleted file mode 100644 index 9fd6ae00..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.2.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.3.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.3.jar deleted file mode 100644 index 7425e11f..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.3.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.4.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.4.jar deleted file mode 100644 index 149abcc2..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-security/pnnl.goss.core.goss-core-security-9.0.4.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-11.0.0.jar index 36e6de13..d6898fd0 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-3.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-12.0.0.jar similarity index 64% rename from cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-3.0.0.jar rename to cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-12.0.0.jar index 49c9e1dc..5fb7be8b 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-3.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-2.0.19.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-12.1.0.jar similarity index 64% rename from cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-2.0.19.jar rename to cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-12.1.0.jar index 6ebc1f13..dd58a6d5 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-2.0.19.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-2.0.18.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-2.0.18.jar deleted file mode 100644 index 68085c34..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-api/pnnl.goss.core.goss-core-server-api-2.0.18.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-1.0.18.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-1.0.18.jar deleted file mode 100644 index b309f303..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-1.0.18.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-11.0.0.jar index f64b5570..9c293f9d 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-1.0.19.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-12.0.0.jar similarity index 79% rename from cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-1.0.19.jar rename to cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-12.0.0.jar index 47e4b7f2..6de6e8ee 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-1.0.19.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-2.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-12.1.0.jar similarity index 79% rename from cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-2.0.0.jar rename to cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-12.1.0.jar index b5e7c2b1..cf1ee99b 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-2.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server-registry/pnnl.goss.core.goss-core-server-registry-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-1.1.1.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-1.1.1.jar deleted file mode 100644 index 805226a9..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-1.1.1.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-11.0.0.jar index 2fd1ff4f..cdae0390 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-1.1.2.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-12.0.0.jar similarity index 96% rename from cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-1.1.2.jar rename to cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-12.0.0.jar index 134017b0..ce8aa2f4 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-1.1.2.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-2.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-12.1.0.jar similarity index 96% rename from cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-2.0.0.jar rename to cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-12.1.0.jar index 51d74ff3..942c100b 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-2.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server-web/pnnl.goss.core.goss-core-server-web-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-11.0.0.jar index 9d282207..826b7f25 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-3.0.0.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-12.0.0.jar similarity index 59% rename from cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-3.0.0.jar rename to cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-12.0.0.jar index 534719af..b4e9bf6a 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-3.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-2.0.28.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-12.1.0.jar similarity index 58% rename from cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-2.0.28.jar rename to cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-12.1.0.jar index f74045dd..2c1b8e90 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-2.0.28.jar and b/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-2.0.27.jar b/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-2.0.27.jar deleted file mode 100644 index 019e9ff4..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.goss-core-server/pnnl.goss.core.goss-core-server-2.0.27.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-11.0.0.jar index 5233a563..547bd764 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-3.0.0.jar b/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-12.0.0.jar similarity index 76% rename from cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-3.0.0.jar rename to cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-12.0.0.jar index 35c1511e..36b51f86 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-3.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-12.1.0.jar b/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-12.1.0.jar new file mode 100644 index 00000000..669627b4 Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-2.0.1.jar b/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-2.0.1.jar deleted file mode 100644 index 5382cb87..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-2.0.1.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-2.0.2.jar b/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-2.0.2.jar deleted file mode 100644 index c20f5d77..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.itests/pnnl.goss.core.itests-2.0.2.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-11.0.0.jar index 656216e9..51a093e4 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-2.0.6.jar b/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-12.0.0.jar similarity index 55% rename from cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-2.0.6.jar rename to cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-12.0.0.jar index 120dee87..34afa936 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-2.0.6.jar and b/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-12.1.0.jar b/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-12.1.0.jar new file mode 100644 index 00000000..469201b5 Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-2.0.5.jar b/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-2.0.5.jar deleted file mode 100644 index b6e02ecc..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-2.0.5.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-3.0.0.jar b/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-3.0.0.jar deleted file mode 100644 index a27ba414..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.runner/pnnl.goss.core.runner-3.0.0.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-1.0.5.jar b/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-1.0.5.jar deleted file mode 100644 index 4e71fa24..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-1.0.5.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-1.0.6.jar b/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-1.0.6.jar deleted file mode 100644 index 5db914ca..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-1.0.6.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-11.0.0.jar index c4ffdf64..a6ff0544 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-12.0.0.jar b/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-12.0.0.jar new file mode 100644 index 00000000..a9d7d791 Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-12.1.0.jar b/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-12.1.0.jar new file mode 100644 index 00000000..ccd84c88 Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-2.0.0.jar b/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-2.0.0.jar deleted file mode 100644 index 15350731..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.security-ldap/pnnl.goss.core.security-ldap-2.0.0.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-11.0.0.jar index dbe25759..259f8ce2 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-12.0.0.jar b/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-12.0.0.jar new file mode 100644 index 00000000..021cc970 Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-12.1.0.jar b/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-12.1.0.jar new file mode 100644 index 00000000..474065a5 Binary files /dev/null and b/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-12.1.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-2.0.8.jar b/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-2.0.8.jar deleted file mode 100644 index 34f51fe2..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-2.0.8.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-2.0.9.jar b/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-2.0.9.jar deleted file mode 100644 index 1cdb4118..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-2.0.9.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-3.0.0.jar b/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-3.0.0.jar deleted file mode 100644 index c7a8f745..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.security-propertyfile/pnnl.goss.core.security-propertyfile-3.0.0.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-1.0.0.jar b/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-1.0.0.jar deleted file mode 100644 index f3edf79f..00000000 Binary files a/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-1.0.0.jar and /dev/null differ diff --git a/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-11.0.0.jar b/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-11.0.0.jar index 6e9d0030..9b4e9018 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-11.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-11.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-1.0.1.jar b/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-12.0.0.jar similarity index 52% rename from cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-1.0.1.jar rename to cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-12.0.0.jar index 7d040812..6ab5e1d8 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-1.0.1.jar and b/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-12.0.0.jar differ diff --git a/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-2.0.0.jar b/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-12.1.0.jar similarity index 52% rename from cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-2.0.0.jar rename to cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-12.1.0.jar index b0feb83e..248a0f14 100644 Binary files a/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-2.0.0.jar and b/cnf/releaserepo/pnnl.goss.core.testutil/pnnl.goss.core.testutil-12.1.0.jar differ diff --git a/pnnl.goss.core.itests/.classpath b/pnnl.goss.core.itests/.classpath index 3fca595a..735afd17 100644 --- a/pnnl.goss.core.itests/.classpath +++ b/pnnl.goss.core.itests/.classpath @@ -1,16 +1,20 @@ - + - - + + + - + - + + + + @@ -21,14 +25,10 @@ - + - - - + - - diff --git a/pnnl.goss.core.itests/bnd.bnd b/pnnl.goss.core.itests/bnd.bnd index 63371125..b7989c76 100644 --- a/pnnl.goss.core.itests/bnd.bnd +++ b/pnnl.goss.core.itests/bnd.bnd @@ -1,4 +1,4 @@ -Bundle-Version: 11.0.0-SNAPSHOT +Bundle-Version: 12.1.0 # Build dependencies - JUnit 5 # Note: Using osgi-core-buildpath only (not full osgi-buildpath) to avoid javax.jms from osgi.enterprise @@ -82,6 +82,7 @@ Import-Package: \ ${jakarta-activation-runpath},\ ${slf4j-runpath},\ ${configadmin-runpath},\ + ${fileinstall-runpath},\ ${gson-runpath},\ ${httpclient-osgi-runpath},\ ${osgi-service-component-runpath},\ diff --git a/pnnl.goss.core.itests/itest.bnd b/pnnl.goss.core.itests/itest.bnd index 3d4ffc0c..eb0cf728 100644 --- a/pnnl.goss.core.itests/itest.bnd +++ b/pnnl.goss.core.itests/itest.bnd @@ -1,5 +1,5 @@ # Modern OSGi Integration Test Configuration -Bundle-Version: 2.0.2-SNAPSHOT +Bundle-Version: 12.1.0 # Use JUnit 5 and OSGi Test # Note: Using osgi-core-buildpath to avoid javax.jms from osgi.enterprise diff --git a/pnnl.goss.core.runner/.classpath b/pnnl.goss.core.runner/.classpath index f1ad1268..498205a1 100644 --- a/pnnl.goss.core.runner/.classpath +++ b/pnnl.goss.core.runner/.classpath @@ -1,24 +1,24 @@ + + + + + + + + + - - - - - - - - - diff --git a/pnnl.goss.core.runner/bnd.bnd b/pnnl.goss.core.runner/bnd.bnd index 32390f36..f439fa7d 100644 --- a/pnnl.goss.core.runner/bnd.bnd +++ b/pnnl.goss.core.runner/bnd.bnd @@ -1,4 +1,4 @@ -Bundle-Version: 11.0.0-SNAPSHOT +Bundle-Version: 12.1.0 -buildpath: \ org.apache.felix.gogo.command,\ org.apache.felix.gogo.runtime,\ @@ -12,6 +12,7 @@ Bundle-Version: 11.0.0-SNAPSHOT jakarta.jms:jakarta.jms-api;version=3.1.0,\ org.apache.activemq:activemq-osgi;version=6.2.0,\ pnnl.goss.core.core-api;version=latest,\ + pnnl.goss.core.goss-client;version=latest,\ pnnl.goss.core.goss-core-security;version=latest,\ pnnl.goss.core.goss-core-server-api;version=latest,\ pnnl.goss.core.goss-core-server;version=latest,\ diff --git a/pnnl.goss.core.runner/build.gradle b/pnnl.goss.core.runner/build.gradle index f5c1a388..137e2921 100644 --- a/pnnl.goss.core.runner/build.gradle +++ b/pnnl.goss.core.runner/build.gradle @@ -46,21 +46,44 @@ task createSSLRunner(type: Jar) { archiveBaseName = 'goss-ssl-runner' archiveVersion = '' destinationDirectory = file("$buildDir/executable") - + manifest { attributes( 'Main-Class': 'pnnl.goss.core.runner.GossSSLRunner' ) } - + // Include everything for SSL runner from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } - + from sourceSets.main.output from project(':pnnl.goss.core').sourceSets.main.output - + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +// CLI tool for subscribing and publishing to queues/topics +task createCli(type: Jar) { + archiveBaseName = 'goss-cli' + archiveVersion = '' + destinationDirectory = file("$buildDir/executable") + + manifest { + attributes( + 'Main-Class': 'pnnl.goss.core.runner.GossCli' + ) + } + + // Include everything needed for the CLI + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } + + from sourceSets.main.output + from project(':pnnl.goss.core').sourceSets.main.output + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } @@ -148,8 +171,9 @@ createGossRunner.dependsOn(':pnnl.goss.core:jar', 'jar') createGossSSLRunner.dependsOn(':pnnl.goss.core:jar', 'jar') createSimpleRunner.dependsOn(':pnnl.goss.core:jar', 'jar') createSSLRunner.dependsOn(':pnnl.goss.core:jar', 'jar') +createCli.dependsOn(':pnnl.goss.core:jar', 'jar') -build.dependsOn createGossRunner, createGossSSLRunner, createSimpleRunner, createSSLRunner +build.dependsOn createGossRunner, createGossSSLRunner, createSimpleRunner, createSSLRunner, createCli // Note: Generic buildRunner. tasks are now provided by BndRunnerPlugin // Usage: ./gradlew buildRunner.goss-core (builds from goss-core.bndrun) diff --git a/pnnl.goss.core.runner/goss-core-ssl.bndrun b/pnnl.goss.core.runner/goss-core-ssl.bndrun index 63605c40..bbba43a1 100644 --- a/pnnl.goss.core.runner/goss-core-ssl.bndrun +++ b/pnnl.goss.core.runner/goss-core-ssl.bndrun @@ -9,6 +9,7 @@ ${activemq-runpath},\ ${javax-runpath},\ ${configadmin-runpath},\ + ${fileinstall-runpath},\ ${gogo-runpath},\ ${scr-runpath},\ ${pax-logging-runpath},\ diff --git a/pnnl.goss.core.runner/goss-core.bndrun b/pnnl.goss.core.runner/goss-core.bndrun index 9e34ffa7..d53e16bd 100644 --- a/pnnl.goss.core.runner/goss-core.bndrun +++ b/pnnl.goss.core.runner/goss-core.bndrun @@ -12,6 +12,8 @@ -runbundles: \ ${activemq-runpath},\ ${jakarta-runpath},\ + ${configadmin-runpath},\ + ${fileinstall-runpath},\ pnnl.goss.core.core-api;version=latest,\ pnnl.goss.core.goss-client;version=latest,\ pnnl.goss.core.goss-core-commands;version=latest,\ @@ -32,7 +34,11 @@ broker-name=broker,\ activemq.start.broker=true,\ stomp.port=61613,\ - ws.port=61614 + ws.port=61614,\ + felix.fileinstall.dir=conf,\ + felix.fileinstall.poll=1000,\ + felix.fileinstall.noInitialDelay=true,\ + felix.fileinstall.log.level=3 diff --git a/pnnl.goss.core.runner/goss-core.shared.bndrun b/pnnl.goss.core.runner/goss-core.shared.bndrun index e02e3897..c6befa22 100644 --- a/pnnl.goss.core.runner/goss-core.shared.bndrun +++ b/pnnl.goss.core.runner/goss-core.shared.bndrun @@ -7,7 +7,11 @@ shared.runprops: \ broker-name=broker,\ activemq.start.broker=true,\ stomp.port=61613,\ - ws.port=61614 + ws.port=61614,\ + felix.fileinstall.dir=conf,\ + felix.fileinstall.poll=1000,\ + felix.fileinstall.noInitialDelay=true,\ + felix.fileinstall.log.level=3 # Include from the home directory some private properties. If # there were a shared.runprops then values would overwrite diff --git a/pnnl.goss.core.runner/src/main/java/pnnl/goss/core/runner/GossCli.java b/pnnl.goss.core.runner/src/main/java/pnnl/goss/core/runner/GossCli.java new file mode 100644 index 00000000..b91eeb6a --- /dev/null +++ b/pnnl.goss.core.runner/src/main/java/pnnl/goss/core/runner/GossCli.java @@ -0,0 +1,347 @@ +package pnnl.goss.core.runner; + +import org.apache.http.auth.UsernamePasswordCredentials; + +import pnnl.goss.core.Client.DESTINATION_TYPE; +import pnnl.goss.core.Client.PROTOCOL; +import pnnl.goss.core.GossResponseEvent; +import pnnl.goss.core.client.GossClient; + +/** + * Command-line GOSS client tool for subscribing and publishing. + * + * Usage: java -jar goss-cli.jar subscribe [options] destination java -jar + * goss-cli.jar publish [options] destination message + * + * Options: -t, --topic Use a topic (default for subscribe) -q, --queue Use a + * queue (default for publish) -b, --broker URL Broker URL (default: + * tcp://localhost:61616) -u, --user USER Username for authentication -p, + * --password PW Password for authentication -h, --help Show help message + * + * Examples: java -jar goss-cli.jar subscribe --queue + * goss.gridappsd.process.request java -jar goss-cli.jar subscribe --topic + * goss.gridappsd.simulation.output.123 java -jar goss-cli.jar publish --queue + * goss.gridappsd.process.request '{"requestType":"query"}' java -jar + * goss-cli.jar publish --topic goss.gridappsd.platform.log 'Test message' + */ +public class GossCli { + + private static final String DEFAULT_BROKER = "tcp://localhost:61616"; + + public static void main(String[] args) { + if (args.length == 0 || hasFlag(args, "-h", "--help")) { + printUsage(); + System.exit(0); + } + + String command = args[0].toLowerCase(); + + switch (command) { + case "subscribe" : + case "sub" : + handleSubscribe(args); + break; + case "publish" : + case "pub" : + handlePublish(args); + break; + default : + System.err.println("Unknown command: " + command); + printUsage(); + System.exit(1); + } + } + + private static void handleSubscribe(String[] args) { + String brokerUrl = getOption(args, "-b", "--broker", DEFAULT_BROKER); + String username = getOption(args, "-u", "--user", null); + String password = getOption(args, "-p", "--password", null); + boolean useQueue = hasFlag(args, "-q", "--queue"); + + String destination = getPositionalArg(args, 1); + if (destination == null) { + System.err.println("Error: No destination specified"); + printUsage(); + System.exit(1); + } + + // Default to topic for subscribe + DESTINATION_TYPE destType = useQueue ? DESTINATION_TYPE.QUEUE : DESTINATION_TYPE.TOPIC; + + System.out.println("GOSS Subscriber"); + System.out.println("==============="); + System.out.println("Broker: " + brokerUrl); + System.out.println("Destination: " + destination); + System.out.println("Type: " + destType); + if (username != null) { + System.out.println("User: " + username); + } + System.out.println(); + + GossClient client = createClient(brokerUrl, username, password); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.out.println("\nShutting down..."); + client.close(); + })); + + try { + client.createSession(); + System.out.println("Connected! Waiting for messages... (Ctrl+C to stop)\n"); + + client.subscribe(destination, new GossResponseEvent() { + @Override + public void onMessage(java.io.Serializable response) { + System.out.println("--- Message Received ---"); + System.out.println(formatMessage(response)); + System.out.println("------------------------\n"); + } + }, destType); + + Thread.currentThread().join(); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + + private static void handlePublish(String[] args) { + String brokerUrl = getOption(args, "-b", "--broker", DEFAULT_BROKER); + String username = getOption(args, "-u", "--user", null); + String password = getOption(args, "-p", "--password", null); + boolean useTopic = hasFlag(args, "-t", "--topic"); + + String destination = getPositionalArg(args, 1); + String message = getPositionalArg(args, 2); + + if (destination == null) { + System.err.println("Error: No destination specified"); + printUsage(); + System.exit(1); + } + if (message == null) { + System.err.println("Error: No message specified"); + printUsage(); + System.exit(1); + } + + // Default to queue for publish (matches Python behavior) + DESTINATION_TYPE destType = useTopic ? DESTINATION_TYPE.TOPIC : DESTINATION_TYPE.QUEUE; + + System.out.println("GOSS Publisher"); + System.out.println("=============="); + System.out.println("Broker: " + brokerUrl); + System.out.println("Destination: " + destination); + System.out.println("Type: " + destType); + if (username != null) { + System.out.println("User: " + username); + } + System.out.println(); + + GossClient client = createClient(brokerUrl, username, password); + + try { + client.createSession(); + System.out.println("Connected! Publishing message...\n"); + + client.publish(destination, message, destType); + + System.out.println("Message published successfully!"); + client.close(); + System.exit(0); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + + private static GossClient createClient(String brokerUrl, String username, String password) { + if (username != null && password != null) { + return new GossClient(PROTOCOL.OPENWIRE, + new UsernamePasswordCredentials(username, password), + brokerUrl, null); + } else { + return new GossClient(PROTOCOL.OPENWIRE, null, brokerUrl, null); + } + } + + private static void printUsage() { + System.out.println("GOSS CLI - Command-line tool for GOSS messaging"); + System.out.println(); + System.out.println("Usage:"); + System.out.println(" java -jar goss-cli.jar subscribe [options] destination"); + System.out.println(" java -jar goss-cli.jar publish [options] destination message"); + System.out.println(); + System.out.println("Commands:"); + System.out.println(" subscribe (sub) Subscribe to a destination and print messages"); + System.out.println(" publish (pub) Publish a message to a destination"); + System.out.println(); + System.out.println("Options:"); + System.out.println(" -t, --topic Use a topic (default for subscribe)"); + System.out.println(" -q, --queue Use a queue (default for publish)"); + System.out.println(" -b, --broker URL Broker URL (default: tcp://localhost:61616)"); + System.out.println(" -u, --user USER Username for authentication"); + System.out.println(" -p, --password PW Password for authentication"); + System.out.println(" -h, --help Show this help message"); + System.out.println(); + System.out.println("Examples:"); + System.out.println(" # Subscribe to a queue"); + System.out.println(" java -jar goss-cli.jar subscribe --queue goss.gridappsd.process.request"); + System.out.println(); + System.out.println(" # Subscribe to a topic"); + System.out.println(" java -jar goss-cli.jar sub --topic goss.gridappsd.simulation.output.123"); + System.out.println(); + System.out.println(" # Publish to a queue (default)"); + System.out.println(" java -jar goss-cli.jar publish goss.gridappsd.process.request '{\"type\":\"query\"}'"); + System.out.println(); + System.out.println(" # Publish to a topic"); + System.out.println(" java -jar goss-cli.jar pub --topic goss.gridappsd.platform.log 'Test message'"); + System.out.println(); + System.out.println(" # With authentication"); + System.out.println(" java -jar goss-cli.jar sub -u admin -p admin -q my.queue"); + } + + private static boolean hasFlag(String[] args, String... flags) { + for (String arg : args) { + for (String flag : flags) { + if (arg.equals(flag)) { + return true; + } + } + } + return false; + } + + private static String getOption(String[] args, String shortOpt, String longOpt, String defaultValue) { + for (int i = 0; i < args.length - 1; i++) { + if (args[i].equals(shortOpt) || args[i].equals(longOpt)) { + return args[i + 1]; + } + } + return defaultValue; + } + + /** + * Get positional argument at index, skipping options and their values. Index 0 + * is the command (subscribe/publish), 1 is first positional arg, etc. + */ + private static String getPositionalArg(String[] args, int index) { + int positionalCount = 0; + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if (arg.startsWith("-")) { + // Skip options that take values + if (arg.equals("-b") || arg.equals("--broker") || + arg.equals("-u") || arg.equals("--user") || + arg.equals("-p") || arg.equals("--password")) { + i++; // Skip the value + } + continue; + } + if (positionalCount == index) { + return arg; + } + positionalCount++; + } + return null; + } + + private static String formatMessage(java.io.Serializable response) { + if (response == null) { + return ""; + } + + String text = response.toString(); + + // Check if it looks like JSON (starts with { or [) + String trimmed = text.trim(); + if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || + (trimmed.startsWith("[") && trimmed.endsWith("]"))) { + try { + // Simple JSON pretty-printing without external dependencies + return prettyPrintJson(trimmed); + } catch (Exception e) { + // If parsing fails, return as-is + return text; + } + } + + return text; + } + + private static String prettyPrintJson(String json) { + StringBuilder result = new StringBuilder(); + int indent = 0; + boolean inString = false; + boolean escaped = false; + + for (int i = 0; i < json.length(); i++) { + char c = json.charAt(i); + + if (escaped) { + result.append(c); + escaped = false; + continue; + } + + if (c == '\\' && inString) { + result.append(c); + escaped = true; + continue; + } + + if (c == '"') { + inString = !inString; + result.append(c); + continue; + } + + if (inString) { + result.append(c); + continue; + } + + switch (c) { + case '{' : + case '[' : + result.append(c); + indent++; + result.append('\n').append(getIndent(indent)); + break; + case '}' : + case ']' : + indent--; + result.append('\n').append(getIndent(indent)).append(c); + break; + case ',' : + result.append(c).append('\n').append(getIndent(indent)); + break; + case ':' : + result.append(": "); + break; + case ' ' : + case '\t' : + case '\n' : + case '\r' : + // Skip whitespace outside strings + break; + default : + result.append(c); + } + } + + return result.toString(); + } + + private static String getIndent(int level) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < level; i++) { + sb.append(" "); + } + return sb.toString(); + } +} diff --git a/pnnl.goss.core.testutil/bnd.bnd b/pnnl.goss.core.testutil/bnd.bnd index f5f64bf1..7eb80bb0 100644 --- a/pnnl.goss.core.testutil/bnd.bnd +++ b/pnnl.goss.core.testutil/bnd.bnd @@ -1,4 +1,4 @@ -Bundle-Version: 11.0.0-SNAPSHOT +Bundle-Version: 12.1.0 -buildpath: \ ${configadmin-buildpath},\ pnnl.goss.core.core-api,\ diff --git a/pnnl.goss.core/bnd.bnd b/pnnl.goss.core/bnd.bnd index 814fd413..b677e69a 100644 --- a/pnnl.goss.core/bnd.bnd +++ b/pnnl.goss.core/bnd.bnd @@ -20,6 +20,7 @@ org.apache.commons:commons-pool2;version=2.12.0,\ commons-dbcp:commons-dbcp;version=1.4,\ org.apache.httpcomponents:httpclient;version=4.5.14,\ + org.apache.httpcomponents:httpcore;version=4.4.16,\ org.apache.httpcomponents.client5:httpclient5;version=5.4,\ org.apache.felix:org.apache.felix.http.servlet-api;version=3.0.0,\ org.apache.felix:org.apache.felix.gogo.runtime;version=1.1.6,\ diff --git a/pnnl.goss.core/core-api.bnd b/pnnl.goss.core/core-api.bnd index 598d1ebc..40a19a3b 100644 --- a/pnnl.goss.core/core-api.bnd +++ b/pnnl.goss.core/core-api.bnd @@ -1,4 +1,4 @@ Export-Package: \ com.northconcepts.exception,\ pnnl.goss.core -Bundle-Version: 11.0.0-SNAPSHOT \ No newline at end of file +Bundle-Version: 12.1.0 \ No newline at end of file diff --git a/pnnl.goss.core/goss-client.bnd b/pnnl.goss.core/goss-client.bnd index 611bd8cf..993f7e01 100644 --- a/pnnl.goss.core/goss-client.bnd +++ b/pnnl.goss.core/goss-client.bnd @@ -1,3 +1,3 @@ Export-Package: \ pnnl.goss.core.client -Bundle-Version: 11.0.0-SNAPSHOT \ No newline at end of file +Bundle-Version: 12.1.0 \ No newline at end of file diff --git a/pnnl.goss.core/goss-core-commands.bnd b/pnnl.goss.core/goss-core-commands.bnd index b516549c..fb119956 100644 --- a/pnnl.goss.core/goss-core-commands.bnd +++ b/pnnl.goss.core/goss-core-commands.bnd @@ -1,3 +1,3 @@ Private-Package: \ pnnl.goss.core.commands -Bundle-Version: 11.0.0-SNAPSHOT \ No newline at end of file +Bundle-Version: 12.1.0 \ No newline at end of file diff --git a/pnnl.goss.core/goss-core-exceptions.bnd b/pnnl.goss.core/goss-core-exceptions.bnd index acbf80b0..671b7944 100644 --- a/pnnl.goss.core/goss-core-exceptions.bnd +++ b/pnnl.goss.core/goss-core-exceptions.bnd @@ -1,5 +1,5 @@ Private-Package: \ pnnl.goss.core.exception -Bundle-Version: 11.0.0-SNAPSHOT +Bundle-Version: 12.1.0 Export-Package: \ com.northconcepts.exception \ No newline at end of file diff --git a/pnnl.goss.core/goss-core-security.bnd b/pnnl.goss.core/goss-core-security.bnd index d434ada1..25c308dc 100644 --- a/pnnl.goss.core/goss-core-security.bnd +++ b/pnnl.goss.core/goss-core-security.bnd @@ -1,7 +1,14 @@ Private-Package: \ pnnl.goss.core.security.impl -# Bundle-Activator: pnnl.goss.core.security.impl.Activator # Disabled - converted to OSGi DS +# Using OSGi DS Component (Activator.java) instead of Bundle-Activator +# The Activator class provides SecurityManager via @Component annotation Export-Package: \ pnnl.goss.core.security -Bundle-Version: 11.0.0-SNAPSHOT \ No newline at end of file +Bundle-Version: 12.1.0 + +# Require FileInstall to be present in the runtime +# FileInstall watches the conf directory for .cfg files and loads them into ConfigAdmin +# Without FileInstall, security realms (PropertyBasedRealm, GossLDAPRealm) won't activate +# because they use configurationPolicy=REQUIRE and need their .cfg files loaded +Require-Bundle: org.apache.felix.fileinstall \ No newline at end of file diff --git a/pnnl.goss.core/goss-core-server-api.bnd b/pnnl.goss.core/goss-core-server-api.bnd index 9a637671..bd947e07 100644 --- a/pnnl.goss.core/goss-core-server-api.bnd +++ b/pnnl.goss.core/goss-core-server-api.bnd @@ -1,3 +1,3 @@ Export-Package: \ pnnl.goss.core.server -Bundle-Version: 11.0.0-SNAPSHOT \ No newline at end of file +Bundle-Version: 12.1.0 \ No newline at end of file diff --git a/pnnl.goss.core/goss-core-server-registry.bnd b/pnnl.goss.core/goss-core-server-registry.bnd index 7d85718f..c077b74a 100644 --- a/pnnl.goss.core/goss-core-server-registry.bnd +++ b/pnnl.goss.core/goss-core-server-registry.bnd @@ -1,4 +1,4 @@ -Bundle-Version: 11.0.0-SNAPSHOT +Bundle-Version: 12.1.0 Private-Package: \ pnnl.goss.server.registry DynamicImport-Package: * \ No newline at end of file diff --git a/pnnl.goss.core/goss-core-server-web.bnd b/pnnl.goss.core/goss-core-server-web.bnd index 953daa27..0c3b237c 100644 --- a/pnnl.goss.core/goss-core-server-web.bnd +++ b/pnnl.goss.core/goss-core-server-web.bnd @@ -2,7 +2,7 @@ DynamicImport-Package: * Private-Package: \ pnnl.goss.core.server.web -Bundle-Version: 11.0.0-SNAPSHOT +Bundle-Version: 12.1.0 # Import webroot folder to path resources/webroot Include-Resource: resources/webroot=webroot diff --git a/pnnl.goss.core/goss-core-server.bnd b/pnnl.goss.core/goss-core-server.bnd index dcf47fc6..ecd0fffe 100644 --- a/pnnl.goss.core/goss-core-server.bnd +++ b/pnnl.goss.core/goss-core-server.bnd @@ -9,4 +9,4 @@ Import-Package: \ * #Include-Resource: \ # OSGI-INF/blueprint/blueprint.xml=config/blueprint.xml -Bundle-Version: 11.0.0-SNAPSHOT \ No newline at end of file +Bundle-Version: 12.1.0 \ No newline at end of file diff --git a/pnnl.goss.core/security-ldap.bnd b/pnnl.goss.core/security-ldap.bnd index 38b24b2a..21388870 100644 --- a/pnnl.goss.core/security-ldap.bnd +++ b/pnnl.goss.core/security-ldap.bnd @@ -1,3 +1,3 @@ Private-Package: \ pnnl.goss.core.security.ldap -Bundle-Version: 11.0.0-SNAPSHOT +Bundle-Version: 12.1.0 diff --git a/pnnl.goss.core/security-propertyfile.bnd b/pnnl.goss.core/security-propertyfile.bnd index 016eaa6f..b21e4f26 100644 --- a/pnnl.goss.core/security-propertyfile.bnd +++ b/pnnl.goss.core/security-propertyfile.bnd @@ -1,3 +1,3 @@ Private-Package: \ pnnl.goss.core.security.propertyfile -Bundle-Version: 11.0.0-SNAPSHOT +Bundle-Version: 12.1.0 diff --git a/pnnl.goss.core/src/pnnl/goss/core/Client.java b/pnnl.goss.core/src/pnnl/goss/core/Client.java index 2290a27f..b5f86395 100644 --- a/pnnl.goss.core/src/pnnl/goss/core/Client.java +++ b/pnnl.goss.core/src/pnnl/goss/core/Client.java @@ -18,17 +18,43 @@ public enum PROTOCOL { }; /** - * Makes synchronous call to the server + * Destination type for JMS messaging. TOPIC: Publish/subscribe semantics - + * message delivered to all subscribers QUEUE: Point-to-point semantics - + * message delivered to one consumer + */ + public enum DESTINATION_TYPE { + TOPIC, QUEUE + }; + + /** + * Makes synchronous call to the server using QUEUE destination (default). This + * matches Python client behavior where bare destination names are treated as + * queues. * * @param request - * @param topic + * @param destination * @param responseFormat * @return * @throws SystemException */ - public Serializable getResponse(Serializable request, String topic, + public Serializable getResponse(Serializable request, String destination, RESPONSE_FORMAT responseFormat) throws SystemException, JMSException; + /** + * Makes synchronous call to the server with specified destination type. + * + * @param request + * @param destination + * destination name + * @param responseFormat + * @param destinationType + * TOPIC or QUEUE + * @return + * @throws SystemException + */ + public Serializable getResponse(Serializable request, String destination, + RESPONSE_FORMAT responseFormat, DESTINATION_TYPE destinationType) throws SystemException, JMSException; + /** * Lets the client subscribe to a Topic of the given name for event based * communication. @@ -41,9 +67,39 @@ public Serializable getResponse(Serializable request, String topic, public Client subscribe(String topic, GossResponseEvent event) throws SystemException; + /** + * Lets the client subscribe to a destination with specified type for event + * based communication. + * + * @param destinationName + * the destination name + * @param event + * the event handler + * @param destinationType + * TOPIC or QUEUE + * @return this client for chaining + * @throws SystemException + */ + public Client subscribe(String destinationName, GossResponseEvent event, DESTINATION_TYPE destinationType) + throws SystemException; + public void publish(String topicName, Serializable message) throws SystemException; + /** + * Publish a message to a destination with specified type. + * + * @param destinationName + * the destination name + * @param message + * the message to publish + * @param destinationType + * TOPIC or QUEUE + * @throws SystemException + */ + public void publish(String destinationName, Serializable message, DESTINATION_TYPE destinationType) + throws SystemException; + public void publish(Destination destination, Serializable data) throws SystemException; diff --git a/pnnl.goss.core/src/pnnl/goss/core/client/DefaultClientConsumer.java b/pnnl.goss.core/src/pnnl/goss/core/client/DefaultClientConsumer.java index ec769acc..6cd4ebba 100644 --- a/pnnl.goss.core/src/pnnl/goss/core/client/DefaultClientConsumer.java +++ b/pnnl.goss.core/src/pnnl/goss/core/client/DefaultClientConsumer.java @@ -49,18 +49,25 @@ import jakarta.jms.MessageConsumer; import jakarta.jms.Session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import pnnl.goss.core.ClientConsumer; import pnnl.goss.core.ClientListener; public class DefaultClientConsumer implements ClientConsumer { + private static final Logger log = LoggerFactory.getLogger(DefaultClientConsumer.class); MessageConsumer messageConsumer; public DefaultClientConsumer(ClientListener clientListener, Session session, Destination destination) { try { + log.info("Creating consumer for destination: {}", destination); setMessageConsumer(session.createConsumer(destination)); getMessageConsumer().setMessageListener(clientListener); + log.info("Successfully created consumer and set listener for: {}", destination); } catch (Exception e) { + log.error("Failed to create consumer for destination: {}", destination, e); e.printStackTrace(); } } diff --git a/pnnl.goss.core/src/pnnl/goss/core/client/DefaultClientListener.java b/pnnl.goss.core/src/pnnl/goss/core/client/DefaultClientListener.java index 96578012..e74ce244 100644 --- a/pnnl.goss.core/src/pnnl/goss/core/client/DefaultClientListener.java +++ b/pnnl.goss.core/src/pnnl/goss/core/client/DefaultClientListener.java @@ -1,5 +1,6 @@ package pnnl.goss.core.client; +import jakarta.jms.BytesMessage; import jakarta.jms.Message; import jakarta.jms.ObjectMessage; import jakarta.jms.TextMessage; @@ -11,6 +12,7 @@ import pnnl.goss.core.DataResponse; import pnnl.goss.core.GossResponseEvent; import pnnl.goss.core.Response; +import pnnl.goss.core.security.SecurityConstants; public class DefaultClientListener implements ClientListener { private static Logger log = LoggerFactory.getLogger(DefaultClientListener.class); @@ -23,7 +25,8 @@ public DefaultClientListener(GossResponseEvent event) { } public void onMessage(Message message) { - + log.info("DefaultClientListener.onMessage called with message type: {}", + message != null ? message.getClass().getSimpleName() : "null"); try { if (message instanceof ObjectMessage) { log.debug("message of type: " + message.getClass() + " received"); @@ -36,6 +39,11 @@ public void onMessage(Message message) { objectMessage.getObject()); if (response.getDestination() == null) response.setDestination(message.getJMSDestination().toString()); + // Set reply destination and username from JMS headers + if (message.getJMSReplyTo() != null) + response.setReplyDestination(message.getJMSReplyTo()); + if (message.getStringProperty(SecurityConstants.SUBJECT_HEADER) != null) + response.setUsername(message.getStringProperty(SecurityConstants.SUBJECT_HEADER)); responseEvent.onMessage(response); } } else if (message instanceof TextMessage) { @@ -43,16 +51,37 @@ public void onMessage(Message message) { DataResponse response = new DataResponse(textMessage.getText()); if (response.getDestination() == null) response.setDestination(message.getJMSDestination().toString()); + // Set reply destination and username from JMS headers + if (message.getJMSReplyTo() != null) + response.setReplyDestination(message.getJMSReplyTo()); + if (message.getStringProperty(SecurityConstants.SUBJECT_HEADER) != null) + response.setUsername(message.getStringProperty(SecurityConstants.SUBJECT_HEADER)); + responseEvent.onMessage(response); + } else if (message instanceof BytesMessage) { + // BytesMessage is used by STOMP clients (Python, JavaScript, etc.) + BytesMessage bytesMessage = (BytesMessage) message; + // Read the bytes and convert to string + long bodyLength = bytesMessage.getBodyLength(); + byte[] bytes = new byte[(int) bodyLength]; + bytesMessage.readBytes(bytes); + String text = new String(bytes, java.nio.charset.StandardCharsets.UTF_8); + log.debug("BytesMessage received, body length: {}, content: {}", bodyLength, text); + + DataResponse response = new DataResponse(text); + if (response.getDestination() == null) + response.setDestination(message.getJMSDestination().toString()); + // Set reply destination and username from JMS headers + if (message.getJMSReplyTo() != null) + response.setReplyDestination(message.getJMSReplyTo()); + if (message.getStringProperty(SecurityConstants.SUBJECT_HEADER) != null) + response.setUsername(message.getStringProperty(SecurityConstants.SUBJECT_HEADER)); responseEvent.onMessage(response); + } else { + log.warn("Unhandled message type: {}", message.getClass().getName()); } - // TODO Look at implementing these? - // Other possible types are + // Other possible types that could be implemented: // MapMessage - A set of keyword/value pairs. - // BytesMessage - A block of binary data, represented in Java as a byte array. - // This format is often used to interface with an external messaging system that - // defines its own binary protocol for message formats. - // StreamMessage - A list of Java primitive values. This type can be used to - // represent certain data types used by existing messaging systems. + // StreamMessage - A list of Java primitive values. } catch (Exception e) { log.error("ERROR Receiving message", e); diff --git a/pnnl.goss.core/src/pnnl/goss/core/client/GossClient.java b/pnnl.goss.core/src/pnnl/goss/core/client/GossClient.java index 1d62f0db..d0a191f4 100644 --- a/pnnl.goss.core/src/pnnl/goss/core/client/GossClient.java +++ b/pnnl.goss.core/src/pnnl/goss/core/client/GossClient.java @@ -252,7 +252,8 @@ else if (protocol.equals(PROTOCOL.OPENWIRE)) { } /** - * Sends request and gets response for synchronous communication. + * Sends request and gets response for synchronous communication. Defaults to + * QUEUE destination type to match Python client behavior. * * @param request * instance of pnnl.goss.core.Request or any of its subclass. @@ -264,26 +265,50 @@ else if (protocol.equals(PROTOCOL.OPENWIRE)) { * @throws JMSException */ @Override - public Serializable getResponse(Serializable message, String topic, + public Serializable getResponse(Serializable message, String destinationName, RESPONSE_FORMAT responseFormat) throws SystemException, JMSException { + // Default to QUEUE to match Python client behavior + return getResponse(message, destinationName, responseFormat, DESTINATION_TYPE.QUEUE); + } + + /** + * Sends request and gets response for synchronous communication with specified + * destination type. + * + * @param message + * instance of pnnl.goss.core.Request or any of its subclass. + * @param destinationName + * the destination name (topic or queue) + * @param responseFormat + * the response format + * @param destinationType + * TOPIC or QUEUE + * @return return an Object which could be a pnnl.goss.core.DataResponse, + * pnnl.goss.core.UploadResponse or pnnl.goss.core.DataError. + * @throws IllegalStateException + * when GossCLient is initialized with an GossResponseEvent. Cannot + * synchronously receive a message when a MessageListener is set. + * @throws JMSException + */ + @Override + public Serializable getResponse(Serializable message, String destinationName, + RESPONSE_FORMAT responseFormat, DESTINATION_TYPE destinationType) throws SystemException, JMSException { if (protocol == null) { protocol = PROTOCOL.OPENWIRE; } - if (topic == null) { - // TODO handle with a ErrorCode lookup! - return new ResponseError("topic cannot be null"); + if (destinationName == null) { + return new ResponseError("destination cannot be null"); } if (message == null) { - // TODO handle with a ErrorCode lookup! return new ResponseError("message cannot be null"); } Serializable response = null; Destination replyDestination = getTemporaryDestination(); - Destination destination = session.createQueue(topic); + Destination destination = getDestination(destinationName, destinationType); - log.debug("Creating consumer for destination " + replyDestination); + log.debug("Creating consumer for destination " + replyDestination + " (type: " + destinationType + ")"); DefaultClientConsumer clientConsumer = new DefaultClientConsumer( session, replyDestination); try { @@ -302,7 +327,7 @@ public Serializable getResponse(Serializable message, String topic, } } catch (JMSException e) { - SystemException.wrap(e).set("topic", topic).set("message", message); + SystemException.wrap(e).set("destination", destinationName).set("message", message); } finally { if (clientConsumer != null) { @@ -341,6 +366,39 @@ public Client subscribe(String topicName, GossResponseEvent event) return this; } + /** + * Lets the client subscribe to a destination with specified type for event + * based communication. + * + * @param destinationName + * the destination name + * @param event + * the event handler + * @param destinationType + * TOPIC or QUEUE + * @return this client for chaining + * @throws SystemException + */ + @Override + public Client subscribe(String destinationName, GossResponseEvent event, DESTINATION_TYPE destinationType) + throws SystemException { + try { + if (event == null) + throw new NullPointerException("event cannot be null"); + Destination destination = null; + if (this.protocol.equals(PROTOCOL.OPENWIRE) || this.protocol.equals(PROTOCOL.STOMP)) { + // Both OPENWIRE and STOMP use the same JMS patterns with ActiveMQ + destination = getDestination(destinationName, destinationType); + new DefaultClientConsumer(new DefaultClientListener(event), + session, destination); + } + } finally { + + } + + return this; + } + @Override public void publish(String topic, Serializable data) throws SystemException { try { @@ -399,6 +457,76 @@ public void publish(String topic, Serializable data) throws SystemException { } } + /** + * Publish a message to a destination with specified type. + * + * @param destinationName + * the destination name + * @param data + * the message to publish + * @param destinationType + * TOPIC or QUEUE + * @throws SystemException + */ + @Override + public void publish(String destinationName, Serializable data, DESTINATION_TYPE destinationType) + throws SystemException { + try { + if (data == null) + throw new NullPointerException("data cannot be null"); + + Destination destination = getDestination(destinationName, destinationType); + + if (data instanceof String) + clientPublisher.publish(destination, data); + else { + Gson gson = new Gson(); + clientPublisher.publish(destination, gson.toJson(data)); + } + + } catch (JMSException e) { + log.error("publish error", e); + + try { + // Ran into error publishing, reset the session and try again + log.info("Renewing session"); + session = null; + getSession(); + Destination destination = getDestination(destinationName, destinationType); + + if (data instanceof String) { + clientPublisher.publish(destination, data); + } else { + Gson gson = new Gson(); + clientPublisher.publish(destination, gson.toJson(data)); + } + } catch (Exception e2) { + log.error("Failed second attempt to publish ", e2); + e.printStackTrace(); + } + } catch (Exception e) { + e.printStackTrace(); + try { + // Ran into error publishing, reset the session and try again + log.info("Renewing session"); + session = null; + getSession(); + Destination destination = getDestination(destinationName, destinationType); + + if (data instanceof String) { + clientPublisher.publish(destination, data); + } else { + Gson gson = new Gson(); + clientPublisher.publish(destination, gson.toJson(data)); + } + } catch (Exception e2) { + log.error("Failed second attempt to publish ", e2); + e.printStackTrace(); + throw SystemException.wrap(e); + } + } + } + @Override public void publish(Destination destination, Serializable data) throws SystemException { try { @@ -526,12 +654,21 @@ private Destination getTemporaryDestination() throws SystemException { } private Destination getDestination(String topicName) throws SystemException { + return getDestination(topicName, DESTINATION_TYPE.TOPIC); + } + + private Destination getDestination(String destinationName, DESTINATION_TYPE destinationType) + throws SystemException { Destination destination = null; try { if (protocol.equals(PROTOCOL.OPENWIRE) || protocol.equals(PROTOCOL.STOMP)) { // Both OPENWIRE and STOMP use standard JMS with ActiveMQ - destination = getSession().createTopic(topicName); + if (destinationType == DESTINATION_TYPE.QUEUE) { + destination = getSession().createQueue(destinationName); + } else { + destination = getSession().createTopic(destinationName); + } if (destination == null) { throw new SystemException(ConnectionCode.DESTINATION_ERROR); diff --git a/pnnl.goss.core/src/pnnl/goss/core/security/GossRealm.java b/pnnl.goss.core/src/pnnl/goss/core/security/GossRealm.java index bf131f33..56ff3458 100644 --- a/pnnl.goss.core/src/pnnl/goss/core/security/GossRealm.java +++ b/pnnl.goss.core/src/pnnl/goss/core/security/GossRealm.java @@ -4,7 +4,13 @@ import org.apache.shiro.realm.Realm; -public interface GossRealm extends Realm { +/** + * GossRealm combines Shiro's Realm with PermissionAdapter functionality. This + * allows classes implementing GossRealm to satisfy both dependencies: - + * SecurityManager needs Realm instances - HandlerRegistryImpl needs + * PermissionAdapter + */ +public interface GossRealm extends Realm, PermissionAdapter { Set getPermissions(String identifier); diff --git a/pnnl.goss.core/src/pnnl/goss/core/security/impl/Activator.java b/pnnl.goss.core/src/pnnl/goss/core/security/impl/Activator.java index a94ef201..284cb1f7 100644 --- a/pnnl.goss.core/src/pnnl/goss/core/security/impl/Activator.java +++ b/pnnl.goss.core/src/pnnl/goss/core/security/impl/Activator.java @@ -1,54 +1,95 @@ package pnnl.goss.core.security.impl; -/* - * TODO: Convert to OSGi DS Component - * This activator needs to be rewritten to use OSGi DS instead of Felix DM - */ - -/* import java.util.HashSet; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.apache.activemq.shiro.mgt.DefaultActiveMqSecurityManager; import org.apache.shiro.SecurityUtils; +import org.apache.shiro.cache.MemoryConstrainedCacheManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; -import org.osgi.framework.BundleContext; - -public class Activator { // extends DependencyActivatorBase { +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; - @Override -*/ +import pnnl.goss.core.security.GossRealm; -// Disabled - needs conversion to OSGi DS -public class Activator { - // TODO: Rewrite using OSGi DS Component -} - -/* - * // public void init(BundleContext context, DependencyManager manager) throws - * Exception { - * - * //Factory factory = new DefaultSecurityManager(); //Secu new - * IniSecurityManagerFactory( // "conf/shiro.ini"); - * - * Realm defaultRealm = new SystemRealm("system", "manager"); Set realms - * = new HashSet<>(); realms.add(defaultRealm); DefaultActiveMqSecurityManager - * securityManager = new DefaultActiveMqSecurityManager(); - * - * securityManager.setRealms(realms); //CurrentAuthorizedPrincipals - * principleHandler = new CurrentAuthorizedPrincipals(); - * - * - * //gt((AbstractAuthenticator)securityManager.getAuthenticator()). - * getAuthenticationListeners().add(principleHandler); - * - * SecurityUtils.setSecurityManager(securityManager); +/** + * OSGi DS component that provides the Shiro SecurityManager service. * + * This replaces the old Felix DM Activator. The SecurityManager is used by GOSS + * for authentication and authorization. * - * manager.add(createComponent().setInterface( SecurityManager.class.getName(), - * null).setImplementation( securityManager)); } + * This component collects all GossRealm services and registers them with the + * SecurityManager before exposing the SecurityManager service. This ensures + * that authentication can work immediately when the broker starts. * - * @Override // public void destroy(BundleContext context, DependencyManager - * manager) throws Exception { // } } + * IMPORTANT: This component requires at least one GossRealm to be available + * (cardinality = AT_LEAST_ONE) before the SecurityManager service is + * registered. This prevents the race condition where GridOpticsServer tries to + * authenticate before any realms are configured. */ +@Component(service = SecurityManager.class, immediate = true) +public class Activator extends DefaultActiveMqSecurityManager { + + private static final Logger log = LoggerFactory.getLogger(Activator.class); + + private final Map, GossRealm> realmMap = new ConcurrentHashMap<>(); + + @Activate + public void activate() { + log.info("Activating SecurityManager service"); + + // Configure cache manager for authorization caching + // This eliminates the "No authorizationCache instance set" warnings + // and improves performance by caching authorization lookups + setCacheManager(new MemoryConstrainedCacheManager()); + log.debug("CacheManager configured for authorization caching"); + + // Register any realms that were added before activation + if (!realmMap.isEmpty()) { + registerAllRealms(); + } + + SecurityUtils.setSecurityManager(this); + log.info("SecurityManager registered with SecurityUtils"); + } + + @Deactivate + public void deactivate() { + log.info("Deactivating SecurityManager service"); + } + + @Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE, policy = ReferencePolicy.DYNAMIC, unbind = "realmRemoved") + public void realmAdded(ServiceReference ref, GossRealm handler) { + realmMap.put(ref, handler); + log.info("Realm added to SecurityManager: {}", handler.getClass().getName()); + registerAllRealms(); + } + + public void realmRemoved(ServiceReference ref) { + GossRealm removed = realmMap.remove(ref); + if (removed != null) { + log.info("Realm removed from SecurityManager: {}", removed.getClass().getName()); + registerAllRealms(); + } + } + + private synchronized void registerAllRealms() { + Set realms = new HashSet<>(); + for (GossRealm r : realmMap.values()) { + realms.add((Realm) r); + } + setRealms(realms); + log.info("Registered {} realms with SecurityManager: {}", realms.size(), + realms.stream().map(r -> r.getClass().getSimpleName()).toList()); + } +} diff --git a/pnnl.goss.core/src/pnnl/goss/core/security/impl/SecurityManagerRealmHandler.java b/pnnl.goss.core/src/pnnl/goss/core/security/impl/SecurityManagerRealmHandler.java index de820e4d..c12c272a 100644 --- a/pnnl.goss.core/src/pnnl/goss/core/security/impl/SecurityManagerRealmHandler.java +++ b/pnnl.goss.core/src/pnnl/goss/core/security/impl/SecurityManagerRealmHandler.java @@ -5,6 +5,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; @@ -13,6 +14,8 @@ import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; import org.osgi.framework.ServiceReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import pnnl.goss.core.security.GossRealm; import pnnl.goss.core.security.PermissionAdapter; @@ -20,32 +23,55 @@ @Component(service = PermissionAdapter.class) public class SecurityManagerRealmHandler implements PermissionAdapter { + private static final Logger log = LoggerFactory.getLogger(SecurityManagerRealmHandler.class); + @Reference private volatile SecurityManager securityManager; private final Map, GossRealm> realmMap = new ConcurrentHashMap<>(); + @Activate + public void activate() { + log.info("SecurityManagerRealmHandler activated with {} pending realms", realmMap.size()); + // Register any realms that were added before the SecurityManager was available + if (!realmMap.isEmpty()) { + registerAllRealms(); + } + } + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, unbind = "realmRemoved") public void realmAdded(ServiceReference ref, GossRealm handler) { - - DefaultSecurityManager defaultInstance = (DefaultSecurityManager) securityManager; realmMap.put(ref, handler); + log.debug("Realm added: {}", handler.getClass().getName()); - if (defaultInstance.getRealms() == null) { - defaultInstance.setRealms(new HashSet()); - Set realms = new HashSet<>(); - for (GossRealm r : realmMap.values()) { - realms.add((Realm) r); - } - defaultInstance.setRealms(realms); - } else { - defaultInstance.getRealms().add(handler); + // Only register if the SecurityManager is available + if (securityManager != null) { + registerAllRealms(); + } + } + + private synchronized void registerAllRealms() { + if (securityManager == null) { + log.warn("Cannot register realms - SecurityManager is null"); + return; } + DefaultSecurityManager defaultInstance = (DefaultSecurityManager) securityManager; + Set realms = new HashSet<>(); + for (GossRealm r : realmMap.values()) { + realms.add((Realm) r); + } + defaultInstance.setRealms(realms); + log.info("Registered {} realms with SecurityManager", realms.size()); } public void realmRemoved(ServiceReference ref) { - DefaultSecurityManager defaultInstance = (DefaultSecurityManager) securityManager; - defaultInstance.getRealms().remove(realmMap.get(ref)); + GossRealm removed = realmMap.remove(ref); + if (removed != null && securityManager != null) { + DefaultSecurityManager defaultInstance = (DefaultSecurityManager) securityManager; + if (defaultInstance.getRealms() != null) { + defaultInstance.getRealms().remove(removed); + } + } } @Override diff --git a/pnnl.goss.core/src/pnnl/goss/core/security/ldap/GossLDAPRealm.java b/pnnl.goss.core/src/pnnl/goss/core/security/ldap/GossLDAPRealm.java index d1b24ff8..729ebac6 100644 --- a/pnnl.goss.core/src/pnnl/goss/core/security/ldap/GossLDAPRealm.java +++ b/pnnl.goss.core/src/pnnl/goss/core/security/ldap/GossLDAPRealm.java @@ -1,141 +1,290 @@ package pnnl.goss.core.security.ldap; -import java.util.Map; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.URI; import java.util.HashSet; +import java.util.Map; import java.util.Set; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.Modified; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.authz.permission.PermissionResolver; +import org.apache.shiro.realm.ldap.DefaultLdapRealm; import org.apache.shiro.realm.ldap.JndiLdapContextFactory; -import org.apache.shiro.realm.ldap.JndiLdapRealm; import org.apache.shiro.subject.PrincipalCollection; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.northconcepts.exception.SystemException; import pnnl.goss.core.security.GossPermissionResolver; import pnnl.goss.core.security.GossRealm; -import com.northconcepts.exception.SystemException; +/** + * LDAP-based authentication realm for GOSS. + * + * This component only activates when a configuration file exists + * (pnnl.goss.core.security.ldap.cfg) with enabled=true and the LDAP server is + * reachable. + * + * Example configuration: + * + *
+ * enabled=true
+ * ldap.url=ldap://localhost:10389
+ * ldap.userDnTemplate=uid={0},ou=users,ou=goss,ou=system
+ * ldap.systemUsername=uid=admin,ou=system
+ * ldap.systemPassword=secret
+ * ldap.connectionTimeout=5000
+ * 
+ */ +@Component(service = GossRealm.class, configurationPid = "pnnl.goss.core.security.ldap", configurationPolicy = ConfigurationPolicy.REQUIRE) +public class GossLDAPRealm extends DefaultLdapRealm implements GossRealm { + + private static final Logger log = LoggerFactory.getLogger(GossLDAPRealm.class); -@Component(service = GossRealm.class, configurationPid = "pnnl.goss.core.security.ldap") -public class GossLDAPRealm extends JndiLdapRealm implements GossRealm { - private static final String CONFIG_PID = "pnnl.goss.core.security.ldap"; + private static final String PROP_ENABLED = "enabled"; + private static final String PROP_LDAP_URL = "ldap.url"; + private static final String PROP_USER_DN_TEMPLATE = "ldap.userDnTemplate"; + private static final String PROP_SYSTEM_USERNAME = "ldap.systemUsername"; + private static final String PROP_SYSTEM_PASSWORD = "ldap.systemPassword"; + private static final String PROP_CONNECTION_TIMEOUT = "ldap.connectionTimeout"; + + private static final String DEFAULT_USER_DN_TEMPLATE = "uid={0},ou=users,ou=goss,ou=system"; + private static final int DEFAULT_CONNECTION_TIMEOUT = 5000; @Reference - GossPermissionResolver gossPermissionResolver; + private GossPermissionResolver gossPermissionResolver; + + private boolean enabled = false; + private String ldapUrl = null; public GossLDAPRealm() { - // TODO move these to config - setUserDnTemplate("uid={0},ou=users,ou=goss,ou=system"); - JndiLdapContextFactory fac = new JndiLdapContextFactory(); - fac.setUrl("ldap://localhost:10389"); - // fac.setSystemUsername("uid=admin,ou=system"); - // fac.setSystemPassword("SYSTEMPW"); - setContextFactory(fac); + // Don't configure in constructor - wait for configuration } - @Override - public Set getPermissions(String identifier) { - // TODO Auto-generated method stub - System.out.println("LDAP GET PERMISSIONS " + identifier); - // TODO get roles for identifier + @Activate + public void activate(Map properties) { + log.info("Activating GossLDAPRealm"); + configure(properties); + } + + @Deactivate + public void deactivate() { + log.info("Deactivating GossLDAPRealm"); + enabled = false; + } + + private void configure(Map properties) { + if (properties == null) { + log.warn("No configuration provided for LDAP realm"); + enabled = false; + return; + } + + // Check if enabled + String enabledStr = getStringProperty(properties, PROP_ENABLED, "false"); + if (!"true".equalsIgnoreCase(enabledStr)) { + log.info("LDAP realm is disabled by configuration"); + enabled = false; + return; + } + + // Get LDAP URL + ldapUrl = getStringProperty(properties, PROP_LDAP_URL, null); + if (ldapUrl == null || ldapUrl.isEmpty()) { + log.warn("LDAP URL not configured - LDAP realm will not be active"); + enabled = false; + return; + } + + // Get connection timeout + int connectionTimeout = getIntProperty(properties, PROP_CONNECTION_TIMEOUT, + DEFAULT_CONNECTION_TIMEOUT); + + // Test connectivity before enabling + if (!isLdapServerReachable(ldapUrl, connectionTimeout)) { + log.warn("LDAP server at {} is not reachable - LDAP realm will not be active", ldapUrl); + enabled = false; + return; + } + + // Configure the realm + String userDnTemplate = getStringProperty(properties, PROP_USER_DN_TEMPLATE, + DEFAULT_USER_DN_TEMPLATE); + String systemUsername = getStringProperty(properties, PROP_SYSTEM_USERNAME, null); + String systemPassword = getStringProperty(properties, PROP_SYSTEM_PASSWORD, null); + + try { + setUserDnTemplate(userDnTemplate); + + JndiLdapContextFactory contextFactory = new JndiLdapContextFactory(); + contextFactory.setUrl(ldapUrl); + + if (systemUsername != null && !systemUsername.isEmpty()) { + contextFactory.setSystemUsername(systemUsername); + } + if (systemPassword != null && !systemPassword.isEmpty()) { + contextFactory.setSystemPassword(systemPassword); + } - // look up permissions based on roles + setContextFactory(contextFactory); + enabled = true; + log.info("LDAP realm configured: url={}, userDnTemplate={}", ldapUrl, userDnTemplate); - return new HashSet(); + } catch (Exception e) { + log.error("Failed to configure LDAP realm", e); + enabled = false; + } + } + + private boolean isLdapServerReachable(String url, int timeoutMs) { + try { + URI uri = new URI(url); + String host = uri.getHost(); + int port = uri.getPort(); + + if (port == -1) { + port = "ldaps".equalsIgnoreCase(uri.getScheme()) ? 636 : 389; + } + + log.debug("Testing LDAP connectivity to {}:{}", host, port); + + try (Socket socket = new Socket()) { + socket.connect(new InetSocketAddress(host, port), timeoutMs); + log.debug("LDAP server {}:{} is reachable", host, port); + return true; + } + } catch (Exception e) { + log.debug("LDAP server {} is not reachable: {}", url, e.getMessage()); + return false; + } + } + + private String getStringProperty(Map props, String key, String defaultVal) { + Object value = props.get(key); + if (value instanceof String && !((String) value).isEmpty()) { + return (String) value; + } + return defaultVal; + } + + private int getIntProperty(Map props, String key, int defaultVal) { + Object value = props.get(key); + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof String) { + try { + return Integer.parseInt((String) value); + } catch (NumberFormatException e) { + // Fall through + } + } + return defaultVal; + } + + @Override + public Set getPermissions(String identifier) { + if (!enabled) { + return new HashSet<>(); + } + log.debug("LDAP getPermissions for: {}", identifier); + // TODO: Implement LDAP-based permission lookup + return new HashSet<>(); } @Override public boolean hasIdentifier(String identifier) { - // TODO Auto-generated method stub - System.out.println("HAS IDENTIFIER " + identifier); + if (!enabled) { + return false; + } + log.debug("LDAP hasIdentifier: {}", identifier); return false; } @Override - protected AuthorizationInfo doGetAuthorizationInfo( - PrincipalCollection principals) { - // TODO Auto-generated method stub - System.out.println("DO GET AUTH INFO"); - for (Object p : principals.asList()) { - System.out.println(" principal: " + p + " " + p.getClass()); + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + if (!enabled) { + return null; } + + log.debug("LDAP doGetAuthorizationInfo for principals: {}", principals); AuthorizationInfo info = super.doGetAuthorizationInfo(principals); - System.out.println("info " + info); if (info == null) { - // try { - info = new SimpleAuthorizationInfo(); - // at the very least make sure they have the user role and can use the request - // and advisory topic - ((SimpleAuthorizationInfo) info).addRole("user"); - - ((SimpleAuthorizationInfo) info).addStringPermission("queue:*"); - ((SimpleAuthorizationInfo) info).addStringPermission("temp-queue:*"); - ((SimpleAuthorizationInfo) info).addStringPermission("topic:*"); // - - // LdapContext ctx = getContextFactory().getSystemLdapContext(); - // TODO lookup roles for user - - // } catch (NamingException e) { - // // TODO Auto-generated catch block - // e.printStackTrace(); - // } - + // Provide default permissions for authenticated LDAP users + SimpleAuthorizationInfo defaultInfo = new SimpleAuthorizationInfo(); + defaultInfo.addRole("user"); + defaultInfo.addStringPermission("queue:*"); + defaultInfo.addStringPermission("temp-queue:*"); + defaultInfo.addStringPermission("topic:*"); + info = defaultInfo; } return info; } @Override - public void setUserDnTemplate(String arg0) throws IllegalArgumentException { - // TODO Auto-generated method stub - super.setUserDnTemplate(arg0); - } - - @Override - protected AuthenticationInfo doGetAuthenticationInfo( - AuthenticationToken token) throws AuthenticationException { + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) + throws AuthenticationException { + if (!enabled) { + log.debug("LDAP realm not enabled, skipping authentication"); + return null; + } - // TODO Auto-generated method stub - System.out.println("GET AUTH TOKEN " + token); - AuthenticationInfo info = super.doGetAuthenticationInfo(token); - System.out.println("GOT INFO " + info); - return info; + log.debug("LDAP authentication attempt for: {}", token.getPrincipal()); + try { + AuthenticationInfo info = super.doGetAuthenticationInfo(token); + if (info != null) { + log.info("LDAP authentication successful for: {}", token.getPrincipal()); + } + return info; + } catch (AuthenticationException e) { + log.debug("LDAP authentication failed for {}: {}", token.getPrincipal(), e.getMessage()); + throw e; + } } @Override public boolean supports(AuthenticationToken token) { - System.out.println("SUPPORTS " + token); - // TODO Auto-generated method stub + if (!enabled) { + return false; + } boolean supports = super.supports(token); - System.out.println("SUPPORTS " + supports); + log.debug("LDAP supports token {}: {}", token.getClass().getSimpleName(), supports); return supports; } @Modified public synchronized void updated(Map properties) throws SystemException { - - if (properties != null) { - // TODO - // shouldStartBroker = Boolean.parseBoolean(Optional - // .ofNullable((String) properties.get(PROP_START_BROKER)) - // .orElse("true")); - - } + log.info("Updating GossLDAPRealm configuration"); + configure(properties); } @Override public PermissionResolver getPermissionResolver() { - if (gossPermissionResolver != null) + if (gossPermissionResolver != null) { return gossPermissionResolver; - else - return super.getPermissionResolver(); + } + return super.getPermissionResolver(); + } + + public boolean isEnabled() { + return enabled; } + public String getLdapUrl() { + return ldapUrl; + } } diff --git a/pnnl.goss.core/src/pnnl/goss/core/security/propertyfile/PropertyBasedRealm.java b/pnnl.goss.core/src/pnnl/goss/core/security/propertyfile/PropertyBasedRealm.java index fa387a21..c353bae8 100644 --- a/pnnl.goss.core/src/pnnl/goss/core/security/propertyfile/PropertyBasedRealm.java +++ b/pnnl.goss.core/src/pnnl/goss/core/security/propertyfile/PropertyBasedRealm.java @@ -5,9 +5,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.Modified; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; @@ -17,14 +14,19 @@ import org.apache.shiro.authz.permission.PermissionResolver; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.northconcepts.exception.SystemException; + import pnnl.goss.core.security.GossPermissionResolver; import pnnl.goss.core.security.GossRealm; -import com.northconcepts.exception.SystemException; - /** * This class handles property based authentication/authorization. It will only * be started as a component if a pnnl.goss.core.security.properties.cfg file @@ -41,10 +43,9 @@ * @author Craig Allwardt * */ -@Component(service = GossRealm.class, configurationPid = "pnnl.goss.core.security.propertyfile") +@Component(service = GossRealm.class, configurationPid = "pnnl.goss.core.security.propertyfile", configurationPolicy = ConfigurationPolicy.REQUIRE) public class PropertyBasedRealm extends AuthorizingRealm implements GossRealm { - private static final String CONFIG_PID = "pnnl.goss.core.security.propertyfile"; private static final Logger log = LoggerFactory.getLogger(PropertyBasedRealm.class); private final Map userMap = new ConcurrentHashMap<>(); @@ -53,6 +54,12 @@ public class PropertyBasedRealm extends AuthorizingRealm implements GossRealm { @Reference GossPermissionResolver gossPermissionResolver; + @Activate + public void activate(Map properties) { + log.info("Activating PropertyBasedRealm"); + updated(properties); + } + @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { @@ -78,24 +85,39 @@ protected AuthenticationInfo doGetAuthenticationInfo( public synchronized void updated(Map properties) throws SystemException { if (properties != null) { - log.debug("Updating PropertyBasedRealm"); + log.debug("Updating PropertyBasedRealm with {} properties", properties.size()); userMap.clear(); userPermissions.clear(); - Set perms = new HashSet<>(); for (String k : properties.keySet()) { - String v = (String) properties.get(k); + // Skip OSGi/ConfigAdmin metadata properties + if (k.startsWith("service.") || k.startsWith("component.") || + k.startsWith("felix.") || k.equals("osgi.ds.satisfying.condition.target")) { + continue; + } + + Object value = properties.get(k); + // Only process String values (skip Long, Boolean, etc.) + if (!(value instanceof String)) { + log.debug("Skipping non-string property: {} = {} ({})", k, value, + value != null ? value.getClass().getName() : "null"); + continue; + } + + String v = (String) value; String[] credAndPermissions = v.split(","); SimpleAccount acnt = new SimpleAccount(k, credAndPermissions[0], getName()); + Set perms = new HashSet<>(); for (int i = 1; i < credAndPermissions.length; i++) { acnt.addStringPermission(credAndPermissions[i]); perms.add(credAndPermissions[i]); } userMap.put(k, acnt); userPermissions.put(k, perms); - + log.debug("Loaded user: {} with {} permissions", k, perms.size()); } + log.info("PropertyBasedRealm configured with {} users", userMap.size()); } } diff --git a/pnnl.goss.core/src/pnnl/goss/core/server/impl/GridOpticsServer.java b/pnnl.goss.core/src/pnnl/goss/core/server/impl/GridOpticsServer.java index 235ce7f0..b67d38db 100644 --- a/pnnl.goss.core/src/pnnl/goss/core/server/impl/GridOpticsServer.java +++ b/pnnl.goss.core/src/pnnl/goss/core/server/impl/GridOpticsServer.java @@ -53,19 +53,12 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Map; import java.util.List; +import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import jakarta.jms.Connection; -import jakarta.jms.ConnectionFactory; -import jakarta.jms.DeliveryMode; -import jakarta.jms.Destination; -import jakarta.jms.JMSException; -import jakarta.jms.MessageProducer; -import jakarta.jms.Session; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManager; @@ -78,24 +71,31 @@ import org.apache.activemq.broker.SslBrokerService; import org.apache.activemq.shiro.ShiroPlugin; import org.apache.commons.io.FilenameUtils; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.Modified; +import org.apache.shiro.mgt.SecurityManager; import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; -import org.apache.shiro.mgt.SecurityManager; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.northconcepts.exception.ConnectionCode; import com.northconcepts.exception.SystemException; +import jakarta.jms.Connection; +import jakarta.jms.ConnectionFactory; +import jakarta.jms.DeliveryMode; +import jakarta.jms.Destination; +import jakarta.jms.JMSException; +import jakarta.jms.MessageProducer; +import jakarta.jms.Session; import pnnl.goss.core.GossCoreContants; import pnnl.goss.core.security.GossRealm; import pnnl.goss.core.server.RequestHandlerRegistry; import pnnl.goss.core.server.ServerControl; -@Component(service = ServerControl.class, configurationPid = "pnnl.goss.core.server") +@Component(service = ServerControl.class, configurationPid = "pnnl.goss.core.server", configurationPolicy = org.osgi.service.component.annotations.ConfigurationPolicy.REQUIRE) public class GridOpticsServer implements ServerControl { private static final Logger log = LoggerFactory.getLogger(GridOpticsServer.class); @@ -128,10 +128,6 @@ public class GridOpticsServer implements ServerControl { private Session session; private Destination destination; - // System manager username/password (required * privleges on the message bus) - private String systemManager = null; - private String systemManagerPassword = null; - // Should we automatically start the broker? private boolean shouldStartBroker = false; // The connectionUri to create if shouldStartBroker is set to true. @@ -150,13 +146,11 @@ public class GridOpticsServer implements ServerControl { // SSL Parameters private boolean sslEnabled = false; private String sslClientKeyStore = null; - private String sslClientKeyStorePassword = null; private String sslClientTrustStore = null; private String sslClientTrustStorePassword = null; private String sslServerKeyStore = null; private String sslServerKeyStorePassword = null; - private String sslServerTrustStore = null; private String sslServerTrustStorePassword = null; private String gossClockTickTopic = null; @@ -206,9 +200,9 @@ public synchronized void updated(Map properties) throws SystemEx if (properties != null) { - systemManager = getProperty((String) properties.get(PROP_SYSTEM_MANAGER), + getProperty((String) properties.get(PROP_SYSTEM_MANAGER), "system"); - systemManagerPassword = getProperty((String) properties.get(PROP_SYSTEM_MANAGER_PASSWORD), + getProperty((String) properties.get(PROP_SYSTEM_MANAGER_PASSWORD), "manager"); shouldStartBroker = Boolean.parseBoolean( @@ -223,8 +217,9 @@ public synchronized void updated(Map properties) throws SystemEx stompTransport = getProperty((String) properties.get(PROP_STOMP_TRANSPORT), "stomp://localhost:61613"); - wsTransport = getProperty((String) properties.get(PROP_WS_TRANSPORT), - "ws://localhost:61614"); + // WebSocket transport is optional - set to null by default + // since it requires the jetty-websocket-server bundle + wsTransport = getProperty((String) properties.get(PROP_WS_TRANSPORT), null); requestQueue = getProperty((String) properties.get(GossCoreContants.PROP_REQUEST_QUEUE), "Request"); @@ -238,13 +233,13 @@ public synchronized void updated(Map properties) throws SystemEx sslTransport = getProperty((String) properties.get(PROP_SSL_TRANSPORT), "tcp://localhost:61443"); sslClientKeyStore = getProperty((String) properties.get(PROP_SSL_CLIENT_KEYSTORE), null); - sslClientKeyStorePassword = getProperty((String) properties.get(PROP_SSL_CLIENT_KEYSTORE_PASSWORD), null); + getProperty((String) properties.get(PROP_SSL_CLIENT_KEYSTORE_PASSWORD), null); sslClientTrustStore = getProperty((String) properties.get(PROP_SSL_CLIENT_TRUSTSTORE), null); sslClientTrustStorePassword = getProperty((String) properties.get(PROP_SSL_CLIENT_TRUSTSTORE_PASSWORD), null); sslServerKeyStore = getProperty((String) properties.get(PROP_SSL_SERVER_KEYSTORE), null); sslServerKeyStorePassword = getProperty((String) properties.get(PROP_SSL_SERVER_KEYSTORE_PASSWORD), null); - sslServerTrustStore = getProperty((String) properties.get(PROP_SSL_SERVER_TRUSTSTORE), null); + getProperty((String) properties.get(PROP_SSL_SERVER_TRUSTSTORE), null); sslServerTrustStorePassword = getProperty((String) properties.get(PROP_SSL_SERVER_TRUSTSTORE_PASSWORD), null); @@ -312,7 +307,12 @@ private void createBroker() throws Exception { broker = new BrokerService(); broker.addConnector(openwireTransport); broker.addConnector(stompTransport); - broker.addConnector(wsTransport); + // Only add WebSocket connector if configured (requires jetty-websocket-server + // bundle) + if (wsTransport != null && !wsTransport.isEmpty()) { + log.debug("Adding WebSocket connector: " + wsTransport); + broker.addConnector(wsTransport); + } } broker.setPersistent(false); broker.setUseJmx(false); @@ -390,8 +390,17 @@ public void run() { } @Override + public void start() throws SystemException { + // This method satisfies the ServerControl interface + // The actual activation is handled by start(Map) with configuration + throw SystemException.wrap(new UnsupportedOperationException( + "Use start(Map) for DS activation with configuration")); + } + @Activate - public void start() { + public void start(Map properties) { + // Apply configuration from ConfigAdmin before starting + updated(properties); // If goss should have start the broker service then this will be set. // this variable is mapped from goss.start.broker diff --git a/pnnl.goss.core/test/pnnl/goss/core/client/test/DestinationTypeTest.java b/pnnl.goss.core/test/pnnl/goss/core/client/test/DestinationTypeTest.java new file mode 100644 index 00000000..eb534269 --- /dev/null +++ b/pnnl.goss.core/test/pnnl/goss/core/client/test/DestinationTypeTest.java @@ -0,0 +1,104 @@ +package pnnl.goss.core.client.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import pnnl.goss.core.Client.DESTINATION_TYPE; + +/** + * Tests for the DESTINATION_TYPE enum and queue/topic support in the GOSS + * client. + * + * The Java GOSS client now supports both QUEUE and TOPIC destination types, + * matching the Python client's behavior. + * + * Key differences between Queue and Topic: - QUEUE: Point-to-point messaging. + * Each message is consumed by exactly one consumer. This is the default for + * getResponse() to match Python client behavior. - TOPIC: Publish-subscribe + * messaging. Each message is delivered to all subscribers. This is the default + * for subscribe() as topics are typically used for broadcast events. + * + * Usage examples: + * + * // Subscribe to a queue (for request/response patterns) + * client.subscribe("goss.gridappsd.process.request", handler, + * DESTINATION_TYPE.QUEUE); + * + * // Subscribe to a topic (for broadcast events) + * client.subscribe("goss.gridappsd.simulation.output.123", handler, + * DESTINATION_TYPE.TOPIC); + * + * // Publish to a queue client.publish("goss.gridappsd.process.request", + * message, DESTINATION_TYPE.QUEUE); + * + * // Publish to a topic client.publish("goss.gridappsd.platform.log", message, + * DESTINATION_TYPE.TOPIC); + * + * // Send request and get response (defaults to QUEUE) + * client.getResponse(request, "goss.gridappsd.process.request", + * RESPONSE_FORMAT.JSON); + * + * // Send request with explicit destination type client.getResponse(request, + * "my.topic", RESPONSE_FORMAT.JSON, DESTINATION_TYPE.TOPIC); + */ +public class DestinationTypeTest { + + @Test + @DisplayName("DESTINATION_TYPE enum should have QUEUE and TOPIC values") + public void destinationTypeHasQueueAndTopic() { + // Verify enum values exist + assertThat(DESTINATION_TYPE.values()).hasSize(2); + assertThat(DESTINATION_TYPE.valueOf("QUEUE")).isEqualTo(DESTINATION_TYPE.QUEUE); + assertThat(DESTINATION_TYPE.valueOf("TOPIC")).isEqualTo(DESTINATION_TYPE.TOPIC); + } + + @Test + @DisplayName("QUEUE should be the preferred type for request/response patterns") + public void queueIsPreferredForRequestResponse() { + // Document that QUEUE is recommended for request/response + // This matches Python client behavior where get_response uses /queue/ + // prefix + DESTINATION_TYPE requestResponseType = DESTINATION_TYPE.QUEUE; + + assertThat(requestResponseType) + .as("Request/response patterns should use QUEUE for point-to-point delivery") + .isEqualTo(DESTINATION_TYPE.QUEUE); + } + + @Test + @DisplayName("TOPIC should be used for broadcast/event patterns") + public void topicIsPreferredForBroadcast() { + // Document that TOPIC is recommended for events/broadcasts + DESTINATION_TYPE broadcastType = DESTINATION_TYPE.TOPIC; + + assertThat(broadcastType) + .as("Broadcast patterns should use TOPIC for pub/sub delivery") + .isEqualTo(DESTINATION_TYPE.TOPIC); + } + + @Test + @DisplayName("Enum ordinal values should be stable") + public void enumOrdinalsAreStable() { + // Verify ordinal values for serialization stability + assertThat(DESTINATION_TYPE.TOPIC.ordinal()).isEqualTo(0); + assertThat(DESTINATION_TYPE.QUEUE.ordinal()).isEqualTo(1); + } + + @Test + @DisplayName("Enum should support standard operations") + public void enumSupportsStandardOperations() { + // Test enum operations + assertThat(DESTINATION_TYPE.QUEUE.name()).isEqualTo("QUEUE"); + assertThat(DESTINATION_TYPE.TOPIC.name()).isEqualTo("TOPIC"); + + // Test comparison + assertThat(DESTINATION_TYPE.QUEUE).isNotEqualTo(DESTINATION_TYPE.TOPIC); + + // Test valueOf round-trip + for (DESTINATION_TYPE type : DESTINATION_TYPE.values()) { + assertThat(DESTINATION_TYPE.valueOf(type.name())).isEqualTo(type); + } + } +} diff --git a/push-to-local-goss-repository.py b/push-to-local-goss-repository.py new file mode 100755 index 00000000..fe950635 --- /dev/null +++ b/push-to-local-goss-repository.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +""" +Push GOSS artifacts to GOSS-Repository +Copies JARs from build output to the specified GOSS-Repository (release or snapshot) +""" + +import argparse +import hashlib +import os +import re +import shutil +import subprocess +import sys +import zipfile +from pathlib import Path + + +# ANSI Colors +class Colors: + RED = '\033[0;31m' + GREEN = '\033[0;32m' + YELLOW = '\033[1;33m' + BLUE = '\033[0;34m' + CYAN = '\033[0;36m' + NC = '\033[0m' # No Color + + +def log_info(msg: str) -> None: + print(f"{Colors.GREEN}[INFO]{Colors.NC} {msg}") + + +def log_warn(msg: str) -> None: + print(f"{Colors.YELLOW}[WARN]{Colors.NC} {msg}") + + +def log_error(msg: str) -> None: + print(f"{Colors.RED}[ERROR]{Colors.NC} {msg}") + + +def extract_bundle_version(jar_path: Path) -> str | None: + """Extract Bundle-Version from JAR manifest.""" + try: + with zipfile.ZipFile(jar_path, 'r') as zf: + manifest_data = zf.read('META-INF/MANIFEST.MF').decode('utf-8') + except (zipfile.BadZipFile, KeyError, IOError): + return None + + # Parse manifest (handle line continuations) + lines = manifest_data.replace('\r\n ', '').replace('\r\n\t', '').split('\r\n') + if len(lines) == 1: + lines = manifest_data.replace('\n ', '').replace('\n\t', '').split('\n') + + for line in lines: + if line.startswith('Bundle-Version:'): + return line.split(':', 1)[1].strip() + return None + + +def is_snapshot_version(version: str) -> bool: + """Check if a version string indicates a snapshot.""" + return 'SNAPSHOT' in version.upper() + + +def find_built_jars(goss_dir: Path) -> list[Path]: + """Find all built JAR files in GOSS project.""" + jars = [] + + # Look in generated directories for bundle JARs + for generated_dir in goss_dir.rglob('generated'): + for jar in generated_dir.glob('*.jar'): + if jar.is_file(): + jars.append(jar) + + return jars + + +def get_bundle_name_from_jar(jar_path: Path) -> str | None: + """Extract Bundle-SymbolicName from JAR manifest.""" + try: + with zipfile.ZipFile(jar_path, 'r') as zf: + manifest_data = zf.read('META-INF/MANIFEST.MF').decode('utf-8') + except (zipfile.BadZipFile, KeyError, IOError): + return None + + # Parse manifest + lines = manifest_data.replace('\r\n ', '').replace('\r\n\t', '').split('\r\n') + if len(lines) == 1: + lines = manifest_data.replace('\n ', '').replace('\n\t', '').split('\n') + + for line in lines: + if line.startswith('Bundle-SymbolicName:'): + bsn = line.split(':', 1)[1].strip() + # Remove directives + if ';' in bsn: + bsn = bsn.split(';')[0].strip() + return bsn + return None + + +def main() -> int: + parser = argparse.ArgumentParser( + description='Push GOSS artifacts to GOSS-Repository (release or snapshot)', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=''' +Examples: + %(prog)s --snapshot # Push snapshot versions to snapshot/ + %(prog)s --release # Push release versions to release/ + %(prog)s --snapshot --dry-run # Show what would be copied + %(prog)s --repo /path/to/GOSS-Repository --snapshot +''' + ) + parser.add_argument( + '--repo', '-r', + type=Path, + default=None, + help='Path to GOSS-Repository (default: ../GOSS-Repository)' + ) + target_group = parser.add_mutually_exclusive_group(required=True) + target_group.add_argument( + '--snapshot', '-s', + action='store_true', + help='Push to snapshot/ directory' + ) + target_group.add_argument( + '--release', + action='store_true', + help='Push to release/ directory' + ) + parser.add_argument( + '--dry-run', '-n', + action='store_true', + help='Show what would be copied without actually copying' + ) + parser.add_argument( + '--no-index', + action='store_true', + help='Skip generating repository index after copying' + ) + parser.add_argument( + '--force', '-f', + action='store_true', + help='Overwrite existing JARs even if same size' + ) + + args = parser.parse_args() + + script_dir = Path(__file__).parent.resolve() + + # Determine repository path + if args.repo: + goss_repo_dir = args.repo.resolve() + else: + goss_repo_dir = script_dir.parent / 'GOSS-Repository' + + # Determine target directory + if args.snapshot: + target_name = 'snapshot' + else: + target_name = 'release' + + dest_repo_dir = goss_repo_dir / target_name + + # Validate destination repository + if not goss_repo_dir.is_dir(): + log_error(f"GOSS-Repository not found at: {goss_repo_dir}") + print() + print(f" The GOSS-Repository must be cloned locally as a sibling directory.") + print(f" Expected location: {goss_repo_dir}") + print() + print(f" To fix this, clone the repository:") + print(f" cd {script_dir.parent}") + print(f" git clone https://github.com/GridOPTICS/GOSS-Repository.git") + print() + print(f" Or specify a custom path with --repo:") + print(f" {sys.argv[0]} --repo /path/to/GOSS-Repository --{target_name}") + return 1 + + if not dest_repo_dir.is_dir(): + log_error(f"Target directory not found: {dest_repo_dir}") + print() + print(f" The '{target_name}/' directory does not exist in GOSS-Repository.") + print(f" Please create it or check that you have the correct repository.") + return 1 + + log_info(f"GOSS Directory: {script_dir}") + log_info(f"Target: {dest_repo_dir}") + + if args.dry_run: + log_info(f"{Colors.YELLOW}DRY RUN - no files will be copied{Colors.NC}") + + # Find built JARs + built_jars = find_built_jars(script_dir) + + if not built_jars: + log_warn("No built JARs found. Run './gradlew build' first.") + return 1 + + log_info(f"Found {len(built_jars)} built JAR(s)") + + # Track statistics + copied_count = 0 + skipped_count = 0 + updated_count = 0 + version_mismatch_count = 0 + + # Process each JAR + for jar_file in sorted(built_jars): + version = extract_bundle_version(jar_file) + bsn = get_bundle_name_from_jar(jar_file) + + if not version or not bsn: + log_warn(f" Skipping (no OSGi metadata): {jar_file.name}") + continue + + # Check if version matches target type + is_snapshot = is_snapshot_version(version) + if args.snapshot and not is_snapshot: + version_mismatch_count += 1 + continue + if args.release and is_snapshot: + version_mismatch_count += 1 + continue + + # Determine destination path: //-.jar + dest_dir = dest_repo_dir / bsn + dest_filename = f"{bsn}-{version}.jar" + dest_path = dest_dir / dest_filename + + # Check if already exists + if dest_path.exists(): + source_size = jar_file.stat().st_size + dest_size = dest_path.stat().st_size + + if source_size == dest_size and not args.force: + skipped_count += 1 + continue + else: + if not args.dry_run: + shutil.copy2(str(jar_file), str(dest_path)) + log_info(f" Updated: {bsn}/{dest_filename}") + updated_count += 1 + else: + if not args.dry_run: + dest_dir.mkdir(parents=True, exist_ok=True) + shutil.copy2(str(jar_file), str(dest_path)) + log_info(f" Copied: {bsn}/{dest_filename}") + copied_count += 1 + + # Summary + print() + print(f"{Colors.GREEN}========================================{Colors.NC}") + print(f"{Colors.GREEN}Push to GOSS-Repository Complete!{Colors.NC}") + print(f" Target: {Colors.CYAN}{target_name}/{Colors.NC}") + print(f" New JARs copied: {Colors.GREEN}{copied_count}{Colors.NC}") + print(f" JARs updated: {Colors.BLUE}{updated_count}{Colors.NC}") + print(f" JARs skipped: {Colors.YELLOW}{skipped_count}{Colors.NC} (same size, use --force to overwrite)") + if version_mismatch_count > 0: + print(f" Version mismatch: {Colors.YELLOW}{version_mismatch_count}{Colors.NC} (wrong type for target)") + print(f"{Colors.GREEN}========================================{Colors.NC}") + print() + + # Generate repository index + if not args.no_index and not args.dry_run and (copied_count > 0 or updated_count > 0): + log_info(f"Generating repository index for {target_name}/...") + + sh_script = goss_repo_dir / 'generate-repository-index.sh' + + if sh_script.exists(): + result = subprocess.run( + ['bash', str(sh_script), target_name], + cwd=goss_repo_dir + ) + if result.returncode != 0: + log_warn("generate-repository-index.sh failed") + else: + log_warn("generate-repository-index.sh not found, skipping index generation") + + if args.dry_run: + log_info(f"{Colors.YELLOW}DRY RUN complete - no files were modified{Colors.NC}") + else: + log_info(f"{Colors.GREEN}✓ All done!{Colors.NC}") + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/check-api.py b/scripts/check-api.py new file mode 100755 index 00000000..41fcc6af --- /dev/null +++ b/scripts/check-api.py @@ -0,0 +1,449 @@ +#!/usr/bin/env python3 +""" +GOSS API Change Detector + +Analyzes Java class files to detect API changes and suggest appropriate version bumps: +- Major: Interface changes, removed public methods, breaking changes +- Minor: New public methods on classes, new classes +- Patch: Implementation-only changes + +Uses javap to extract public API signatures from JAR files. +""" + +import argparse +import hashlib +import json +import os +import re +import subprocess +import sys +import tempfile +import zipfile +from pathlib import Path +from dataclasses import dataclass, field +from typing import Optional + + +# ANSI Colors +class Colors: + RED = '\033[0;31m' + GREEN = '\033[0;32m' + YELLOW = '\033[1;33m' + BLUE = '\033[0;34m' + CYAN = '\033[0;36m' + MAGENTA = '\033[0;35m' + NC = '\033[0m' # No Color + + +def log_info(msg: str) -> None: + print(f"{Colors.GREEN}[INFO]{Colors.NC} {msg}") + + +def log_warn(msg: str) -> None: + print(f"{Colors.YELLOW}[WARN]{Colors.NC} {msg}") + + +def log_error(msg: str) -> None: + print(f"{Colors.RED}[ERROR]{Colors.NC} {msg}") + + +@dataclass +class ClassInfo: + """Information about a Java class's public API.""" + name: str + is_interface: bool = False + is_abstract: bool = False + is_enum: bool = False + superclass: Optional[str] = None + interfaces: list[str] = field(default_factory=list) + public_methods: list[str] = field(default_factory=list) + public_fields: list[str] = field(default_factory=list) + + def signature_hash(self) -> str: + """Generate a hash of the public API signature.""" + sig = f"{self.name}|{self.is_interface}|{self.superclass}|" + sig += "|".join(sorted(self.interfaces)) + sig += "|".join(sorted(self.public_methods)) + sig += "|".join(sorted(self.public_fields)) + return hashlib.md5(sig.encode()).hexdigest()[:12] + + +def extract_class_info(jar_path: Path, class_name: str) -> Optional[ClassInfo]: + """Extract public API information from a class using javap.""" + try: + result = subprocess.run( + ['javap', '-public', '-classpath', str(jar_path), class_name], + capture_output=True, + text=True, + timeout=10 + ) + if result.returncode != 0: + return None + + output = result.stdout + info = ClassInfo(name=class_name) + + # Parse class declaration + class_match = re.search( + r'(public\s+)?(abstract\s+)?(interface|class|enum)\s+\S+', + output + ) + if class_match: + info.is_interface = 'interface' in class_match.group(0) + info.is_abstract = 'abstract' in (class_match.group(0) or '') + info.is_enum = 'enum' in class_match.group(0) + + # Parse extends + extends_match = re.search(r'extends\s+(\S+)', output) + if extends_match: + info.superclass = extends_match.group(1) + + # Parse implements + implements_match = re.search(r'implements\s+([^{]+)', output) + if implements_match: + info.interfaces = [i.strip() for i in implements_match.group(1).split(',')] + + # Parse public methods (simplified) + for line in output.split('\n'): + line = line.strip() + if line.startswith('public') and '(' in line and ')' in line: + # Extract method signature + method_sig = re.sub(r'\s+', ' ', line.rstrip(';')) + info.public_methods.append(method_sig) + elif line.startswith('public') and '(' not in line and ';' in line: + # Public field + info.public_fields.append(line.rstrip(';')) + + return info + except Exception as e: + return None + + +def list_classes_in_jar(jar_path: Path) -> list[str]: + """List all class files in a JAR.""" + classes = [] + try: + with zipfile.ZipFile(jar_path, 'r') as zf: + for name in zf.namelist(): + if name.endswith('.class') and not name.startswith('META-INF/'): + # Convert path to class name + class_name = name[:-6].replace('/', '.') + # Skip inner classes for now + if '$' not in class_name: + classes.append(class_name) + except Exception: + pass + return sorted(classes) + + +def analyze_jar(jar_path: Path) -> dict[str, ClassInfo]: + """Analyze all public APIs in a JAR file.""" + apis = {} + classes = list_classes_in_jar(jar_path) + + for class_name in classes: + info = extract_class_info(jar_path, class_name) + if info: + apis[class_name] = info + + return apis + + +@dataclass +class ApiChange: + """Represents a single API change.""" + change_type: str # 'major', 'minor', 'patch' + category: str # 'interface', 'class', 'method', 'field' + description: str + class_name: str + + +def compare_apis(old_apis: dict[str, ClassInfo], new_apis: dict[str, ClassInfo]) -> list[ApiChange]: + """Compare two API snapshots and return list of changes.""" + changes = [] + + old_classes = set(old_apis.keys()) + new_classes = set(new_apis.keys()) + + # Removed classes = MAJOR (breaking change) + for removed in old_classes - new_classes: + old_info = old_apis[removed] + change_type = 'major' if old_info.is_interface else 'major' + changes.append(ApiChange( + change_type=change_type, + category='interface' if old_info.is_interface else 'class', + description=f"Removed: {removed}", + class_name=removed + )) + + # Added classes = MINOR (backward compatible addition) + for added in new_classes - old_classes: + new_info = new_apis[added] + changes.append(ApiChange( + change_type='minor', + category='interface' if new_info.is_interface else 'class', + description=f"Added: {added}", + class_name=added + )) + + # Changed classes + for class_name in old_classes & new_classes: + old_info = old_apis[class_name] + new_info = new_apis[class_name] + + # Interface changes are always MAJOR + if old_info.is_interface or new_info.is_interface: + old_methods = set(old_info.public_methods) + new_methods = set(new_info.public_methods) + + # Removed methods from interface = MAJOR + for removed in old_methods - new_methods: + changes.append(ApiChange( + change_type='major', + category='interface', + description=f"Interface method removed: {removed}", + class_name=class_name + )) + + # Added methods to interface = MAJOR (breaks implementors) + for added in new_methods - old_methods: + changes.append(ApiChange( + change_type='major', + category='interface', + description=f"Interface method added: {added}", + class_name=class_name + )) + else: + # Class changes + old_methods = set(old_info.public_methods) + new_methods = set(new_info.public_methods) + + # Removed public methods = MAJOR + for removed in old_methods - new_methods: + changes.append(ApiChange( + change_type='major', + category='method', + description=f"Public method removed: {removed}", + class_name=class_name + )) + + # Added public methods = MINOR + for added in new_methods - old_methods: + changes.append(ApiChange( + change_type='minor', + category='method', + description=f"Public method added: {added}", + class_name=class_name + )) + + # Check superclass changes = MAJOR + if old_info.superclass != new_info.superclass: + changes.append(ApiChange( + change_type='major', + category='class', + description=f"Superclass changed: {old_info.superclass} -> {new_info.superclass}", + class_name=class_name + )) + + # Check interface changes = MAJOR (for classes) + old_interfaces = set(old_info.interfaces) + new_interfaces = set(new_info.interfaces) + + for removed in old_interfaces - new_interfaces: + changes.append(ApiChange( + change_type='major', + category='class', + description=f"Interface removed: {removed}", + class_name=class_name + )) + + return changes + + +def find_baseline_jar(bundle_name: str, release_repo: Path) -> Optional[Path]: + """Find the baseline JAR for a bundle in the release repository.""" + bundle_dir = release_repo / bundle_name + if not bundle_dir.is_dir(): + return None + + # Find the latest JAR + jars = list(bundle_dir.glob('*.jar')) + if not jars: + return None + + # Sort by version (simple string sort works for semver) + jars.sort(key=lambda p: p.name, reverse=True) + return jars[0] + + +def find_current_jar(bundle_name: str, goss_root: Path) -> Optional[Path]: + """Find the current built JAR for a bundle.""" + for generated in goss_root.rglob('generated'): + for jar in generated.glob(f'{bundle_name}*.jar'): + if jar.is_file(): + return jar + return None + + +def get_bundle_name_from_jar(jar_path: Path) -> Optional[str]: + """Extract Bundle-SymbolicName from JAR manifest.""" + try: + with zipfile.ZipFile(jar_path, 'r') as zf: + manifest = zf.read('META-INF/MANIFEST.MF').decode('utf-8') + for line in manifest.replace('\r\n ', '').replace('\n ', '').split('\n'): + if line.startswith('Bundle-SymbolicName:'): + bsn = line.split(':', 1)[1].strip() + if ';' in bsn: + bsn = bsn.split(';')[0].strip() + return bsn + except Exception: + pass + return None + + +def main() -> int: + parser = argparse.ArgumentParser( + description='Analyze API changes and suggest version bump type', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=''' +Version Bump Rules: + MAJOR (X.0.0): Interface changes, removed public methods, breaking changes + MINOR (x.Y.0): New public methods on classes, new classes (backward compatible) + PATCH (x.y.Z): Implementation-only changes, no public API changes + +Examples: + %(prog)s # Analyze all bundles + %(prog)s --bundle pnnl.goss.core.core-api # Analyze specific bundle + %(prog)s --verbose # Show detailed change information +''' + ) + + parser.add_argument('--bundle', '-b', help='Specific bundle to analyze') + parser.add_argument('--verbose', '-v', action='store_true', help='Show detailed changes') + parser.add_argument('--baseline', help='Path to baseline repository (default: cnf/releaserepo)') + + args = parser.parse_args() + + script_dir = Path(__file__).parent.resolve() + goss_root = script_dir.parent + + # Determine baseline repository + if args.baseline: + baseline_repo = Path(args.baseline) + else: + baseline_repo = goss_root / 'cnf' / 'releaserepo' + + if not baseline_repo.is_dir(): + log_warn(f"Baseline repository not found: {baseline_repo}") + log_warn("No baseline to compare against. All changes will be considered MINOR.") + log_warn("Run './gradlew release' to populate the baseline repository.") + print() + + # Find all current JARs + current_jars = [] + for generated in goss_root.rglob('generated'): + for jar in generated.glob('pnnl.goss.*.jar'): + if jar.is_file() and 'runner' not in jar.name: + current_jars.append(jar) + + if not current_jars: + log_error("No built JARs found. Run './gradlew build' first.") + return 1 + + # Filter to specific bundle if requested + if args.bundle: + current_jars = [j for j in current_jars if args.bundle in j.name] + if not current_jars: + log_error(f"Bundle not found: {args.bundle}") + return 1 + + print(f"\n{Colors.CYAN}API Change Analysis{Colors.NC}") + print("=" * 60) + + overall_bump = 'patch' + all_changes: list[ApiChange] = [] + + for current_jar in sorted(current_jars): + bundle_name = get_bundle_name_from_jar(current_jar) + if not bundle_name: + continue + + baseline_jar = find_baseline_jar(bundle_name, baseline_repo) + + print(f"\n{Colors.BLUE}{bundle_name}{Colors.NC}") + + if not baseline_jar: + print(f" {Colors.YELLOW}No baseline found{Colors.NC} - treating as new bundle (MINOR)") + if overall_bump == 'patch': + overall_bump = 'minor' + continue + + # Analyze both JARs + old_apis = analyze_jar(baseline_jar) + new_apis = analyze_jar(current_jar) + + if not old_apis and not new_apis: + print(f" {Colors.YELLOW}Could not analyze APIs{Colors.NC}") + continue + + # Compare + changes = compare_apis(old_apis, new_apis) + all_changes.extend(changes) + + if not changes: + # Check if implementation changed (hash comparison) + old_hashes = {k: v.signature_hash() for k, v in old_apis.items()} + new_hashes = {k: v.signature_hash() for k, v in new_apis.items()} + + if old_hashes == new_hashes: + print(f" {Colors.GREEN}No API changes{Colors.NC}") + else: + print(f" {Colors.GREEN}Implementation changes only{Colors.NC} (PATCH)") + else: + # Categorize changes + major_changes = [c for c in changes if c.change_type == 'major'] + minor_changes = [c for c in changes if c.change_type == 'minor'] + + if major_changes: + print(f" {Colors.RED}MAJOR changes detected:{Colors.NC}") + overall_bump = 'major' + if args.verbose: + for c in major_changes[:5]: + print(f" - {c.description}") + if len(major_changes) > 5: + print(f" ... and {len(major_changes) - 5} more") + else: + print(f" {len(major_changes)} breaking change(s)") + + if minor_changes: + print(f" {Colors.YELLOW}MINOR changes detected:{Colors.NC}") + if overall_bump == 'patch': + overall_bump = 'minor' + if args.verbose: + for c in minor_changes[:5]: + print(f" - {c.description}") + if len(minor_changes) > 5: + print(f" ... and {len(minor_changes) - 5} more") + else: + print(f" {len(minor_changes)} addition(s)") + + # Summary + print("\n" + "=" * 60) + print(f"{Colors.CYAN}Recommended Version Bump:{Colors.NC}") + + if overall_bump == 'major': + print(f" {Colors.RED}MAJOR{Colors.NC} - Breaking API changes detected") + print(f" Run: {Colors.CYAN}make bump-major{Colors.NC}") + elif overall_bump == 'minor': + print(f" {Colors.YELLOW}MINOR{Colors.NC} - New API additions (backward compatible)") + print(f" Run: {Colors.CYAN}make bump-minor{Colors.NC}") + else: + print(f" {Colors.GREEN}PATCH{Colors.NC} - Implementation changes only") + print(f" Run: {Colors.CYAN}make bump-patch{Colors.NC} or {Colors.CYAN}make next-snapshot{Colors.NC}") + + print() + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/version.py b/scripts/version.py new file mode 100755 index 00000000..5076006f --- /dev/null +++ b/scripts/version.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +""" +GOSS Version Management Script + +Commands: + show - Display versions of all bundles + release - Set release version (removes -SNAPSHOT) + snapshot - Set snapshot version (adds -SNAPSHOT) + bump-patch - Bump patch version (x.y.Z) and set as snapshot + bump-minor - Bump minor version (x.Y.0) and set as snapshot + bump-major - Bump major version (X.0.0) and set as snapshot + next-snapshot - Bump patch version after a release +""" + +import argparse +import re +import sys +from pathlib import Path + + +# ANSI Colors +class Colors: + RED = '\033[0;31m' + GREEN = '\033[0;32m' + YELLOW = '\033[1;33m' + BLUE = '\033[0;34m' + CYAN = '\033[0;36m' + NC = '\033[0m' # No Color + + +def log_info(msg: str) -> None: + print(f"{Colors.GREEN}[INFO]{Colors.NC} {msg}") + + +def log_warn(msg: str) -> None: + print(f"{Colors.YELLOW}[WARN]{Colors.NC} {msg}") + + +def log_error(msg: str) -> None: + print(f"{Colors.RED}[ERROR]{Colors.NC} {msg}") + + +def find_bnd_files(root: Path) -> list[Path]: + """Find all .bnd files that contain Bundle-Version.""" + bnd_files = [] + for bnd_file in root.rglob('*.bnd'): + # Skip cnf/ext directory (these are config files, not bundles) + if 'cnf/ext' in str(bnd_file): + continue + # Skip cnf/build.bnd and cnf/bnd.bnd (workspace config) + if bnd_file.parent.name == 'cnf' and bnd_file.name in ('build.bnd', 'bnd.bnd'): + continue + # Check if file contains Bundle-Version + content = bnd_file.read_text() + if 'Bundle-Version:' in content: + bnd_files.append(bnd_file) + return sorted(bnd_files) + + +def extract_bundle_info(bnd_file: Path) -> tuple[str, str] | None: + """Extract bundle name and version from a .bnd file.""" + content = bnd_file.read_text() + + # Extract Bundle-Version + version_match = re.search(r'Bundle-Version:\s*(.+)', content) + if not version_match: + return None + + version = version_match.group(1).strip() + + # Derive bundle name from file path + # e.g., pnnl.goss.core/core-api.bnd -> pnnl.goss.core.core-api + parent_dir = bnd_file.parent.name + bundle_name = bnd_file.stem + + if bundle_name == 'bnd': + # Main bundle file (e.g., pnnl.goss.core/bnd.bnd) + full_name = parent_dir + else: + # Sub-bundle file (e.g., pnnl.goss.core/core-api.bnd) + full_name = f"{parent_dir}.{bundle_name}" + + return (full_name, version) + + +def show_versions(root: Path) -> None: + """Display versions of all bundles.""" + bnd_files = find_bnd_files(root) + + if not bnd_files: + log_warn("No bundle .bnd files found") + return + + print(f"\n{Colors.CYAN}GOSS Bundle Versions{Colors.NC}") + print("=" * 60) + + # Group by version + versions: dict[str, list[str]] = {} + + for bnd_file in bnd_files: + info = extract_bundle_info(bnd_file) + if info: + name, version = info + if version not in versions: + versions[version] = [] + versions[version].append(name) + + # Display grouped by version + for version in sorted(versions.keys()): + is_snapshot = '-SNAPSHOT' in version + version_color = Colors.YELLOW if is_snapshot else Colors.GREEN + print(f"\n{version_color}{version}{Colors.NC}:") + for name in sorted(versions[version]): + print(f" - {name}") + + print("\n" + "=" * 60) + print(f"Total bundles: {sum(len(v) for v in versions.values())}") + + # Summary + snapshot_count = sum(len(v) for ver, v in versions.items() if '-SNAPSHOT' in ver) + release_count = sum(len(v) for ver, v in versions.items() if '-SNAPSHOT' not in ver) + + if snapshot_count > 0: + print(f" {Colors.YELLOW}Snapshot:{Colors.NC} {snapshot_count}") + if release_count > 0: + print(f" {Colors.GREEN}Release:{Colors.NC} {release_count}") + print() + + +def update_version(bnd_file: Path, new_version: str) -> bool: + """Update Bundle-Version in a .bnd file.""" + content = bnd_file.read_text() + + # Replace Bundle-Version line + new_content, count = re.subn( + r'(Bundle-Version:\s*).+', + f'\\g<1>{new_version}', + content + ) + + if count > 0: + bnd_file.write_text(new_content) + return True + return False + + +def get_current_version(root: Path) -> str | None: + """Get the current version from .bnd files (returns base version without -SNAPSHOT).""" + bnd_files = find_bnd_files(root) + + versions: set[str] = set() + for bnd_file in bnd_files: + info = extract_bundle_info(bnd_file) + if info: + _, version = info + # Strip -SNAPSHOT suffix for comparison + base_version = version.replace('-SNAPSHOT', '') + versions.add(base_version) + + if len(versions) == 0: + return None + if len(versions) > 1: + log_warn(f"Multiple versions found: {sorted(versions)}") + # Return the highest version + return sorted(versions, key=lambda v: [int(x) for x in v.split('.')])[-1] + + return versions.pop() + + +def bump_version(version: str, bump_type: str) -> str: + """Bump a version string by the specified type (major, minor, patch).""" + parts = [int(x) for x in version.split('.')] + + if bump_type == 'major': + parts[0] += 1 + parts[1] = 0 + parts[2] = 0 + elif bump_type == 'minor': + parts[1] += 1 + parts[2] = 0 + elif bump_type == 'patch': + parts[2] += 1 + + return '.'.join(str(p) for p in parts) + + +def set_version(root: Path, version: str, snapshot: bool = False) -> None: + """Set version for all bundles.""" + # Validate version format + if not re.match(r'^\d+\.\d+\.\d+$', version): + log_error(f"Invalid version format: {version}") + log_error("Expected format: x.y.z (e.g., 11.0.0)") + sys.exit(1) + + # Add or remove -SNAPSHOT suffix + if snapshot: + full_version = f"{version}-SNAPSHOT" + else: + full_version = version + + bnd_files = find_bnd_files(root) + + if not bnd_files: + log_warn("No bundle .bnd files found") + return + + action = "snapshot" if snapshot else "release" + log_info(f"Setting {action} version: {full_version}") + print() + + updated_count = 0 + for bnd_file in bnd_files: + info = extract_bundle_info(bnd_file) + if info: + name, old_version = info + if update_version(bnd_file, full_version): + rel_path = bnd_file.relative_to(root) + print(f" {Colors.GREEN}✓{Colors.NC} {name}: {old_version} -> {full_version}") + updated_count += 1 + + print() + log_info(f"Updated {updated_count} bundle(s) to version {full_version}") + + if not snapshot: + print() + log_info("Next steps for release:") + print(f" 1. Build: ./gradlew build") + print(f" 2. Test: ./gradlew check") + print(f" 3. Commit: git commit -am 'Release version {version}'") + print(f" 4. Tag: git tag -a v{version} -m 'Version {version}'") + print(f" 5. Push: git push && git push --tags") + print() + + +def do_bump(root: Path, bump_type: str) -> int: + """Bump version and set as snapshot.""" + current = get_current_version(root) + if not current: + log_error("Could not determine current version") + return 1 + + new_version = bump_version(current, bump_type) + log_info(f"Bumping {bump_type} version: {current} -> {new_version}-SNAPSHOT") + set_version(root, new_version, snapshot=True) + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser( + description='GOSS Version Management', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=''' +Examples: + %(prog)s show # Show all bundle versions + %(prog)s release 11.0.0 # Set release version 11.0.0 + %(prog)s snapshot 11.1.0 # Set snapshot version 11.1.0-SNAPSHOT + %(prog)s bump-patch # 11.0.0 -> 11.0.1-SNAPSHOT + %(prog)s bump-minor # 11.0.0 -> 11.1.0-SNAPSHOT + %(prog)s bump-major # 11.0.0 -> 12.0.0-SNAPSHOT + %(prog)s next-snapshot # After release: bump patch to next snapshot + +Typical release workflow: + 1. %(prog)s show # Verify current version (e.g., 11.0.0-SNAPSHOT) + 2. %(prog)s release 11.0.0 # Remove -SNAPSHOT for release + 3. make build && make test # Build and test + 4. make push-release # Push to GOSS-Repository + 5. git tag v11.0.0 && git push # Tag and push + 6. %(prog)s next-snapshot # Bump to 11.0.1-SNAPSHOT for next development +''' + ) + + subparsers = parser.add_subparsers(dest='command', help='Command to run') + + # show command + subparsers.add_parser('show', help='Show versions of all bundles') + + # release command + release_parser = subparsers.add_parser('release', help='Set release version (removes -SNAPSHOT)') + release_parser.add_argument('version', help='Version number (e.g., 11.0.0)') + + # snapshot command + snapshot_parser = subparsers.add_parser('snapshot', help='Set snapshot version (adds -SNAPSHOT)') + snapshot_parser.add_argument('version', help='Version number (e.g., 11.1.0)') + + # bump commands + subparsers.add_parser('bump-patch', help='Bump patch version (x.y.Z) and set as snapshot') + subparsers.add_parser('bump-minor', help='Bump minor version (x.Y.0) and set as snapshot') + subparsers.add_parser('bump-major', help='Bump major version (X.0.0) and set as snapshot') + subparsers.add_parser('next-snapshot', help='Bump patch version after a release (alias for bump-patch)') + + args = parser.parse_args() + + # Find root directory (where this script's parent's parent is) + script_dir = Path(__file__).parent.resolve() + root = script_dir.parent + + if not args.command: + parser.print_help() + return 1 + + if args.command == 'show': + show_versions(root) + elif args.command == 'release': + set_version(root, args.version, snapshot=False) + elif args.command == 'snapshot': + set_version(root, args.version, snapshot=True) + elif args.command in ('bump-patch', 'next-snapshot'): + return do_bump(root, 'patch') + elif args.command == 'bump-minor': + return do_bump(root, 'minor') + elif args.command == 'bump-major': + return do_bump(root, 'major') + + return 0 + + +if __name__ == '__main__': + sys.exit(main())