Tuesday, March 19, 2019

Smaller Wildfly image with Galeon

First lets create Docker image in standard way:
FROM jboss/wildfly
RUN curl -Ls https://github.com/andrzejszywala/hellojee/raw/master/hellojee.war -o /opt/jboss/wildfly/standalone/deployments/hellojee.war
Build image
docker build -t hello_wildfly .
docker image ls
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
hello_wildfly            latest              cc8926d06bf0        25 minutes ago      745MB
Now lets use Galeon to create smaller wildfly
# In first stage prepare wildfly 
FROM adoptopenjdk/openjdk11:alpine-slim as WILDFLY_BUILD
ENV GALEON_VERSION 3.0.2.Final 
RUN apk --update add --no-cache --virtual .build-deps unzip curl bash
RUN curl -Ls https://github.com/wildfly/galleon/releases/download/$GALEON_VERSION/galleon-$GALEON_VERSION.zip -o /tmp/galeon.zip
RUN unzip /tmp/galeon.zip -d /tmp
RUN /tmp/galleon*/bin/galleon.sh install wildfly:current --layers=cdi,jaxrs,jpa,h2-database --dir=/tmp/wildfly 
RUN curl -Ls https://github.com/andrzejszywala/hellojee/raw/master/hellojee.war -o /tmp/wildfly/standalone/deployments/hellojee.war

# Second stage builds final image 
FROM adoptopenjdk/openjdk11:alpine-slim 
ENV LAUNCH_JBOSS_IN_BACKGROUND true
EXPOSE 8080
# Copy wildfly with application from temporary image
COPY --from=WILDFLY_BUILD /tmp/wildfly /opt/wildfy
CMD ["/opt/wildfy/bin/standalone.sh", "-b", "0.0.0.0"]
Build image
docker build -t hello_galeon .
docker image ls
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
hello_galeon             latest              a719b9633749        13 minutes ago      365MB
As you can see image is half the size.

Sunday, November 25, 2018

List to JsonArray

someObjects.stream()
    .map(this::createJson)
    .collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add)
    .build();

Thursday, March 22, 2018

Prywatne repozytorium Dockera

Utworzenie pliku z użytkownikiem dla rejestru:
sudo docker run --entrypoint htpasswd registry:2 -Bbn uzytkownik haslo > /opt/docker/auth/htpasswd
Uruchomienie rejestru:
sudo docker run -d \
--restart=always \
--name registry \
-v /opt/docker/certs:/certs \
-v /opt/docker/auth:/auth \
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.unencrypted.key \
-p 5000:443 \
registry:2
Dodanie certyfikatu do rejestru:
/etc/docker/certs.d/privatehub.domain.com:5000/ca.crt
Logowanie do rejestru:
sudo docker login -u uzytkownik -h haslo -e email privatehub.domain.com:5000
Wrzucenie obrazu do rejestru:
docker build -t privatehub.domain.com:5000/apka:1 .
docker push privatehub.domain.com:5000/apka:1
Utworzenie serwisu w swarmie z przekazaniem informacji logowania do repozytorium
docker service create --replicas 1 --with-registry-auth --name apka privatehub.domain.com:5000/apka:1

Tuesday, September 5, 2017

Reverse proxy in Wildfly

In this post I'll show how to configure reverse proxy in Wildfly so we can redirect some resources to other server.


  • Create two Wildfly instances in separate folders wf1 and wf2
  • Copy sample application to Wildfly deployments folder. You can download sample application from github
  • Start first Wildfly
    e:\programowanie\wf-reverse-proxy\wf1\bin>standalone.bat
  • Start second Wildfly
    e:\programowanie\wf-reverse-proxy\wf2\bin>standalone.bat -Djboss.socket.binding.port-offset=100
  • Open first appliation http://localhost:8080/hellojee/resources/hello
  • Open second appliation http://localhost:8180/hellojee/resources/hello
  • In Wildfly console you should see log from HelloResource
  • Stop wf1 and edit standalone.xml file.
  • In socket-binding-group section add
    <outbound-socket-binding name="wf2-binding">
        <remote-destination host="localhost" port="8180"/>
    </outbound-socket-binding>
  • Go to subsystem xmlns="urn:jboss:domain:undertow:3.1" add in handlers section
    <reverse-proxy name="wf2-proxy">
        <host name="wf2" scheme="http" outbound-socket-binding="wf2-binding" instance-id="wf2-route" path="/hellojee/resources"/>
    </reverse-proxy>
  • In <host name="default-host" alias="localhost"> section add
    <location name="/hellojee/resources" handler="wf2-proxy"/>
  • Now after refreshing wf1 application at http://localhost:8080/hellojee/resources/hello your request will be redirected to wf2. Notice that log from HelloResource appeared in wf2 console.
Full content of modified standalone.xml
<?xml version="1.0" ?>
<server xmlns="urn:jboss:domain:4.2">
    <extensions>
        <extension module="org.jboss.as.clustering.infinispan"/>
        <extension module="org.jboss.as.connector"/>
        <extension module="org.jboss.as.deployment-scanner"/>
        <extension module="org.jboss.as.ee"/>
        <extension module="org.jboss.as.ejb3"/>
        <extension module="org.jboss.as.jaxrs"/>
        <extension module="org.jboss.as.jdr"/>
        <extension module="org.jboss.as.jmx"/>
        <extension module="org.jboss.as.jpa"/>
        <extension module="org.jboss.as.jsf"/>
        <extension module="org.jboss.as.logging"/>
        <extension module="org.jboss.as.mail"/>
        <extension module="org.jboss.as.naming"/>
        <extension module="org.jboss.as.pojo"/>
        <extension module="org.jboss.as.remoting"/>
        <extension module="org.jboss.as.sar"/>
        <extension module="org.jboss.as.security"/>
        <extension module="org.jboss.as.transactions"/>
        <extension module="org.jboss.as.webservices"/>
        <extension module="org.jboss.as.weld"/>
        <extension module="org.wildfly.extension.batch.jberet"/>
        <extension module="org.wildfly.extension.bean-validation"/>
        <extension module="org.wildfly.extension.io"/>
        <extension module="org.wildfly.extension.request-controller"/>
        <extension module="org.wildfly.extension.security.manager"/>
        <extension module="org.wildfly.extension.undertow"/>
    </extensions>
    <management>
        <security-realms>
            <security-realm name="ManagementRealm">
                <authentication>
                    <local default-user="$local" skip-group-loading="true"/>
                    <properties path="mgmt-users.properties" relative-to="jboss.server.config.dir"/>
                </authentication>
                <authorization map-groups-to-roles="false">
                    <properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
                </authorization>
            </security-realm>
            <security-realm name="ApplicationRealm">
                <server-identities>
                    <ssl>
                        <keystore path="application.keystore" relative-to="jboss.server.config.dir" keystore-password="password" alias="server" key-password="password" generate-self-signed-certificate-host="localhost"/>
                    </ssl>
                </server-identities>
                <authentication>
                    <local default-user="$local" allowed-users="*" skip-group-loading="true"/>
                    <properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
                </authentication>
                <authorization>
                    <properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
                </authorization>
            </security-realm>
        </security-realms>
        <audit-log>
            <formatters>
                <json-formatter name="json-formatter"/>
            </formatters>
            <handlers>
                <file-handler name="file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.server.data.dir"/>
            </handlers>
            <logger log-boot="true" log-read-only="false" enabled="false">
                <handlers>
                    <handler name="file"/>
                </handlers>
            </logger>
        </audit-log>
        <management-interfaces>
            <http-interface security-realm="ManagementRealm" http-upgrade-enabled="true">
                <socket-binding http="management-http"/>
            </http-interface>
        </management-interfaces>
        <access-control provider="simple">
            <role-mapping>
                <role name="SuperUser">
                    <include>
                        <user name="$local"/>
                    </include>
                </role>
            </role-mapping>
        </access-control>
    </management>
    <profile>
        <subsystem xmlns="urn:jboss:domain:logging:3.0">
            <console-handler name="CONSOLE">
                <level name="INFO"/>
                <formatter>
                    <named-formatter name="COLOR-PATTERN"/>
                </formatter>
            </console-handler>
            <periodic-rotating-file-handler name="FILE" autoflush="true">
                <formatter>
                    <named-formatter name="PATTERN"/>
                </formatter>
                <file relative-to="jboss.server.log.dir" path="server.log"/>
                <suffix value=".yyyy-MM-dd"/>
                <append value="true"/>
            </periodic-rotating-file-handler>
            <logger category="com.arjuna">
                <level name="WARN"/>
            </logger>
            <logger category="org.jboss.as.config">
                <level name="DEBUG"/>
            </logger>
            <logger category="sun.rmi">
                <level name="WARN"/>
            </logger>
            <root-logger>
                <level name="INFO"/>
                <handlers>
                    <handler name="CONSOLE"/>
                    <handler name="FILE"/>
                </handlers>
            </root-logger>
            <formatter name="PATTERN">
                <pattern-formatter pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
            </formatter>
            <formatter name="COLOR-PATTERN">
                <pattern-formatter pattern="%K{level}%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
            </formatter>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:batch-jberet:1.0">
            <default-job-repository name="in-memory"/>
            <default-thread-pool name="batch"/>
            <job-repository name="in-memory">
                <in-memory/>
            </job-repository>
            <thread-pool name="batch">
                <max-threads count="10"/>
                <keepalive-time time="30" unit="seconds"/>
            </thread-pool>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:bean-validation:1.0"/>
        <subsystem xmlns="urn:jboss:domain:datasources:4.0">
            <datasources>
                <datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
                    <connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url>
                    <driver>h2</driver>
                    <security>
                        <user-name>sa</user-name>
                        <password>sa</password>
                    </security>
                </datasource>
                <drivers>
                    <driver name="h2" module="com.h2database.h2">
                        <xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
                    </driver>
                </drivers>
            </datasources>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:deployment-scanner:2.0">
            <deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" runtime-failure-causes-rollback="${jboss.deployment.scanner.rollback.on.failure:false}"/>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:ee:4.0">
            <spec-descriptor-property-replacement>false</spec-descriptor-property-replacement>
            <concurrent>
                <context-services>
                    <context-service name="default" jndi-name="java:jboss/ee/concurrency/context/default" use-transaction-setup-provider="true"/>
                </context-services>
                <managed-thread-factories>
                    <managed-thread-factory name="default" jndi-name="java:jboss/ee/concurrency/factory/default" context-service="default"/>
                </managed-thread-factories>
                <managed-executor-services>
                    <managed-executor-service name="default" jndi-name="java:jboss/ee/concurrency/executor/default" context-service="default" hung-task-threshold="60000" keepalive-time="5000"/>
                </managed-executor-services>
                <managed-scheduled-executor-services>
                    <managed-scheduled-executor-service name="default" jndi-name="java:jboss/ee/concurrency/scheduler/default" context-service="default" hung-task-threshold="60000" keepalive-time="3000"/>
                </managed-scheduled-executor-services>
            </concurrent>
            <default-bindings context-service="java:jboss/ee/concurrency/context/default" datasource="java:jboss/datasources/ExampleDS" managed-executor-service="java:jboss/ee/concurrency/executor/default" managed-scheduled-executor-service="java:jboss/ee/concurrency/scheduler/default" managed-thread-factory="java:jboss/ee/concurrency/factory/default"/>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:ejb3:4.0">
            <session-bean>
                <stateless>
                    <bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
                </stateless>
                <stateful default-access-timeout="5000" cache-ref="simple" passivation-disabled-cache-ref="simple"/>
                <singleton default-access-timeout="5000"/>
            </session-bean>
            <pools>
                <bean-instance-pools>
                    <strict-max-pool name="slsb-strict-max-pool" derive-size="from-worker-pools" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
                    <strict-max-pool name="mdb-strict-max-pool" derive-size="from-cpu-count" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
                </bean-instance-pools>
            </pools>
            <caches>
                <cache name="simple"/>
                <cache name="distributable" passivation-store-ref="infinispan" aliases="passivating clustered"/>
            </caches>
            <passivation-stores>
                <passivation-store name="infinispan" cache-container="ejb" max-size="10000"/>
            </passivation-stores>
            <async thread-pool-name="default"/>
            <timer-service thread-pool-name="default" default-data-store="default-file-store">
                <data-stores>
                    <file-data-store name="default-file-store" path="timer-service-data" relative-to="jboss.server.data.dir"/>
                </data-stores>
            </timer-service>
            <remote connector-ref="http-remoting-connector" thread-pool-name="default"/>
            <thread-pools>
                <thread-pool name="default">
                    <max-threads count="10"/>
                    <keepalive-time time="100" unit="milliseconds"/>
                </thread-pool>
            </thread-pools>
            <default-security-domain value="other"/>
            <default-missing-method-permissions-deny-access value="true"/>
            <log-system-exceptions value="true"/>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:io:1.1">
            <worker name="default"/>
            <buffer-pool name="default"/>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:infinispan:4.0">
            <cache-container name="server" default-cache="default" module="org.wildfly.clustering.server">
                <local-cache name="default">
                    <transaction mode="BATCH"/>
                </local-cache>
            </cache-container>
            <cache-container name="web" default-cache="passivation" module="org.wildfly.clustering.web.infinispan">
                <local-cache name="passivation">
                    <locking isolation="REPEATABLE_READ"/>
                    <transaction mode="BATCH"/>
                    <file-store passivation="true" purge="false"/>
                </local-cache>
                <local-cache name="persistent">
                    <locking isolation="REPEATABLE_READ"/>
                    <transaction mode="BATCH"/>
                    <file-store passivation="false" purge="false"/>
                </local-cache>
                <local-cache name="concurrent">
                    <file-store passivation="true" purge="false"/>
                </local-cache>
            </cache-container>
            <cache-container name="ejb" aliases="sfsb" default-cache="passivation" module="org.wildfly.clustering.ejb.infinispan">
                <local-cache name="passivation">
                    <locking isolation="REPEATABLE_READ"/>
                    <transaction mode="BATCH"/>
                    <file-store passivation="true" purge="false"/>
                </local-cache>
                <local-cache name="persistent">
                    <locking isolation="REPEATABLE_READ"/>
                    <transaction mode="BATCH"/>
                    <file-store passivation="false" purge="false"/>
                </local-cache>
            </cache-container>
            <cache-container name="hibernate" default-cache="local-query" module="org.hibernate.infinispan">
                <local-cache name="entity">
                    <transaction mode="NON_XA"/>
                    <eviction strategy="LRU" max-entries="10000"/>
                    <expiration max-idle="100000"/>
                </local-cache>
                <local-cache name="local-query">
                    <eviction strategy="LRU" max-entries="10000"/>
                    <expiration max-idle="100000"/>
                </local-cache>
                <local-cache name="timestamps"/>
            </cache-container>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:jaxrs:1.0"/>
        <subsystem xmlns="urn:jboss:domain:jca:4.0">
            <archive-validation enabled="true" fail-on-error="true" fail-on-warn="false"/>
            <bean-validation enabled="true"/>
            <default-workmanager>
                <short-running-threads>
                    <core-threads count="50"/>
                    <queue-length count="50"/>
                    <max-threads count="50"/>
                    <keepalive-time time="10" unit="seconds"/>
                </short-running-threads>
                <long-running-threads>
                    <core-threads count="50"/>
                    <queue-length count="50"/>
                    <max-threads count="50"/>
                    <keepalive-time time="10" unit="seconds"/>
                </long-running-threads>
            </default-workmanager>
            <cached-connection-manager/>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:jdr:1.0"/>
        <subsystem xmlns="urn:jboss:domain:jmx:1.3">
            <expose-resolved-model/>
            <expose-expression-model/>
            <remoting-connector/>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:jpa:1.1">
            <jpa default-datasource="" default-extended-persistence-inheritance="DEEP"/>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:jsf:1.0"/>
        <subsystem xmlns="urn:jboss:domain:mail:2.0">
            <mail-session name="default" jndi-name="java:jboss/mail/Default">
                <smtp-server outbound-socket-binding-ref="mail-smtp"/>
            </mail-session>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:naming:2.0">
            <remote-naming/>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:pojo:1.0"/>
        <subsystem xmlns="urn:jboss:domain:remoting:3.0">
            <endpoint/>
            <http-connector name="http-remoting-connector" connector-ref="default" security-realm="ApplicationRealm"/>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:resource-adapters:4.0"/>
        <subsystem xmlns="urn:jboss:domain:request-controller:1.0"/>
        <subsystem xmlns="urn:jboss:domain:sar:1.0"/>
        <subsystem xmlns="urn:jboss:domain:security-manager:1.0">
            <deployment-permissions>
                <maximum-set>
                    <permission class="java.security.AllPermission"/>
                </maximum-set>
            </deployment-permissions>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:security:1.2">
            <security-domains>
                <security-domain name="other" cache-type="default">
                    <authentication>
                        <login-module code="Remoting" flag="optional">
                            <module-option name="password-stacking" value="useFirstPass"/>
                        </login-module>
                        <login-module code="RealmDirect" flag="required">
                            <module-option name="password-stacking" value="useFirstPass"/>
                        </login-module>
                    </authentication>
                </security-domain>
                <security-domain name="jboss-web-policy" cache-type="default">
                    <authorization>
                        <policy-module code="Delegating" flag="required"/>
                    </authorization>
                </security-domain>
                <security-domain name="jboss-ejb-policy" cache-type="default">
                    <authorization>
                        <policy-module code="Delegating" flag="required"/>
                    </authorization>
                </security-domain>
                <security-domain name="jaspitest" cache-type="default">
                    <authentication-jaspi>
                        <login-module-stack name="dummy">
                            <login-module code="Dummy" flag="optional"/>
                        </login-module-stack>
                        <auth-module code="Dummy"/>
                    </authentication-jaspi>
                </security-domain>
            </security-domains>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:transactions:3.0">
            <core-environment>
                <process-id>
                    <uuid/>
                </process-id>
            </core-environment>
            <recovery-environment socket-binding="txn-recovery-environment" status-socket-binding="txn-status-manager"/>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:undertow:3.1">
            <buffer-cache name="default"/>
            <server name="default-server">
                <http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true"/>
                <https-listener name="https" socket-binding="https" security-realm="ApplicationRealm" enable-http2="true"/>
                <host name="default-host" alias="localhost">
                    <location name="/" handler="welcome-content"/>
                    <location name="/hellojee/resources" handler="wf2-proxy"/>
                    <filter-ref name="server-header"/>
                    <filter-ref name="x-powered-by-header"/>
                </host>
            </server>
            <servlet-container name="default">
                <jsp-config/>
                <websockets/>
            </servlet-container>
            <handlers>
                <file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
                <reverse-proxy name="wf2-proxy">
                 <host name="wf2" scheme="http" outbound-socket-binding="wf2-binding" instance-id="wf2-route" path="/hellojee/resources"/>
                </reverse-proxy>
            </handlers>
            <filters>
                <response-header name="server-header" header-name="Server" header-value="WildFly/10"/>
                <response-header name="x-powered-by-header" header-name="X-Powered-By" header-value="Undertow/1"/>
            </filters>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:webservices:2.0">
            <wsdl-host>${jboss.bind.address:127.0.0.1}</wsdl-host>
            <endpoint-config name="Standard-Endpoint-Config"/>
            <endpoint-config name="Recording-Endpoint-Config">
                <pre-handler-chain name="recording-handlers" protocol-bindings="##SOAP11_HTTP ##SOAP11_HTTP_MTOM ##SOAP12_HTTP ##SOAP12_HTTP_MTOM">
                    <handler name="RecordingHandler" class="org.jboss.ws.common.invocation.RecordingServerHandler"/>
                </pre-handler-chain>
            </endpoint-config>
            <client-config name="Standard-Client-Config"/>
        </subsystem>
        <subsystem xmlns="urn:jboss:domain:weld:3.0"/>
    </profile>
    <interfaces>
        <interface name="management">
            <inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
        </interface>
        <interface name="public">
            <inet-address value="${jboss.bind.address:127.0.0.1}"/>
        </interface>
    </interfaces>
    <socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
        <socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/>
        <socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>
        <socket-binding name="ajp" port="${jboss.ajp.port:8009}"/>
        <socket-binding name="http" port="${jboss.http.port:8080}"/>
        <socket-binding name="https" port="${jboss.https.port:8443}"/>
        <socket-binding name="txn-recovery-environment" port="4712"/>
        <socket-binding name="txn-status-manager" port="4713"/>
        <outbound-socket-binding name="mail-smtp">
            <remote-destination host="localhost" port="25"/>
        </outbound-socket-binding>
        <outbound-socket-binding name="wf2-binding">
            <remote-destination host="localhost" port="8180"/>
        </outbound-socket-binding>
    </socket-binding-group>
</server>

Saturday, February 4, 2017

Deploy application on Wildfly using Java and REST

Prerequisite

  • Download Wildfly 10.1.0
  • Add management user
  • Run Wildfly
  • Download sample appliaction hellojee.war
First let's create shell script which redeploys application on Wildfly
#!/bin/bash
echo "Undeploy old war"
curl -k -S -H "content-Type: application/json" -d '{"operation":"undeploy", "address":[{"deployment":"hellojee.war"}]}' --digest http://admin:admin@localhost:9990/management
echo ""

echo "Remove old war"
curl -k -S -H "content-Type: application/json" -d '{"operation":"remove", "address":[{"deployment":"hellojee.war"}]}' --digest http://admin:admin@localhost:9990/management
echo ""

echo "Upload new war"
bytes_value=`curl -k -F "file=@hellojee.war" --digest http://admin:admin@localhost:9990/management/add-content | perl -pe 's/^.*"BYTES_VALUE"\s*:\s*"(.*)".*$/$1/'`
echo $bytes_value

json_string_start='{"content":[{"hash": {"BYTES_VALUE" : "'
json_string_end='"}}], "address": [{"deployment":"hellojee.war"}], "operation":"add", "enabled":"true"}'
json_string="$json_string_start$bytes_value$json_string_end"

echo "Deploy new war"
result=`curl -k -S -H "Content-Type: application/json" -d "$json_string" --digest http://admin:admin@localhost:9990/management | perl -pe 's/^.*"outcome"\s*:\s*"(.*)".*$/$1/'`
echo $result

if [ "$result" != "success" ]; then
  exit -1
fi
Now let's do it in Java. Create maven project and add jersey dependencies
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>pl.andrzejszywala</groupId>
 <artifactId>wfdep</artifactId>
 <version>0.0.1-SNAPSHOT</version>

 <packaging>jar</packaging>

 <name>wfdep</name>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>1.8</maven.compiler.target>
 </properties>

 <dependencies>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.12</version>
  </dependency>

  <dependency>
   <groupId>org.glassfish.jersey.core</groupId>
   <artifactId>jersey-client</artifactId>
   <version>2.23</version>
  </dependency>
  <dependency>
   <groupId>org.glassfish</groupId>
   <artifactId>javax.json</artifactId>
   <version>1.0.4</version>
  </dependency>
  <dependency>
   <groupId>org.glassfish.jersey.media</groupId>
   <artifactId>jersey-media-json-processing</artifactId>
   <version>2.23</version>
  </dependency>
  <dependency>
   <groupId>org.glassfish.jersey.media</groupId>
   <artifactId>jersey-media-multipart</artifactId>
   <version>2.23</version>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>1.8</source>
     <target>1.8</target>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>
Create new rest client
package pl.andrzejszywala.wfdep;

import static javax.json.Json.createArrayBuilder;
import static javax.json.Json.createObjectBuilder;
import static javax.ws.rs.client.Entity.json;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.logging.Logger;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonValue;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.MultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.media.multipart.file.FileDataBodyPart;

public class DeploymentEngine {
  private static final Logger logger = Logger.getLogger(DeploymentEngine.class.getName());
  private String host;
  private String path;
  private String user;
  private String password;
  private String file;

  public DeploymentEngine(String host, String path, String user, String password) {
    super();
    this.host = host;
    this.path = path;
    this.file = new File(this.path).getName();
    this.user = user;
    this.password = password;
  }
  
  public String deploy() throws IOException  {
    undeploy();
    remove();
    return enable(add());
  }
  
  private String enable(JsonValue hash)  {
    String result = null;
    logger.info("Starting");
    Response post = newClient()
        .target(host + "/management")
        .request()
          .post(json(createObjectBuilder()
              .add("operation", "add")
              .add("enabled", "true")
              .add("content", createArrayBuilder()
                        .add(createObjectBuilder()
                            .add("hash", createObjectBuilder().add("BYTES_VALUE", hash))))
              .add("address", createArrayBuilder()
                              .add(createObjectBuilder().add("deployment", file))).build()));
    result = post.readEntity(String.class);
    logger.info(result);
    return result;
  }

  private  void undeploy() {
    logger.info("Undeploying");
    Response post = newClient()
      .target(host + "/management")
      .request()
        .post(json(createObjectBuilder()
            .add("operation", "undeploy")
            .add("address", createArrayBuilder()
                .add(createObjectBuilder().add("deployment", file))).build()));
    logger.info(post.readEntity(String.class));
  }
  
  private  void remove() {
    logger.info("Removeing");
    Response post = newClient()
      .target(host + "/management")
      .request()
        .post(json(createObjectBuilder()
            .add("operation", "remove")
            .add("address", createArrayBuilder()
                .add(createObjectBuilder().add("deployment", file))).build()));
    logger.info(post.readEntity(String.class));
  }
  
  private JsonValue add() throws IOException  {
    logger.info("Adding");
    final FileDataBodyPart filePart = new FileDataBodyPart("file", new File(path));
     
    try (MultiPart multiPart = new FormDataMultiPart()) {
      multiPart.bodyPart(filePart);
      Response post = newClient()
        .target(host + "/management/add-content")
        .request(APPLICATION_JSON_TYPE)
          .post(Entity.entity(multiPart, multiPart.getMediaType()));
      String entity = post.readEntity(String.class);
      logger.info(entity);
      JsonReader reader = Json.createReader(new StringReader(entity));
      JsonObject jo = (JsonObject) reader.read();
      logger.info("" + jo);
      return ((JsonObject)jo.get("result")).get("BYTES_VALUE");
    }
  }

  public Client newClient()  {    
    ClientConfig clientConfig = new ClientConfig();
    clientConfig.register(HttpAuthenticationFeature.digest(user, password.getBytes()));
    clientConfig.register(MultiPartFeature.class);
    return ClientBuilder.newBuilder().withConfig(clientConfig).build();
  }
}
Source code for this blog post is available on github

Sunday, November 13, 2016

Programmatically created report with custom datasource in JasperServer

Sometimes there is need to create report programmatically. So let's do this in JasperServer.
Create empty jrxml file and save as dynamic.jrxml
<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" 
name="dynamic" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="aa4ecc63-a8e6-409f-a877-1caf24b98220"/>
Create ant build.xml file
<project name="dynamic" default="deploy" basedir=".">
    <!-- Points to where jasperserver is installed on tomcat or another webapp server.
         Used to find JARs for compiling as well as deploying
    -->
    <property name="webAppDir" value="c:\\Jaspersoft\\jasperreports-server-cp-6.3.0\\apache-tomcat\\webapps\\jasperserver"/>

    <target name="clean">
        <delete dir="classes"/>
    </target>
    <target name="compile">
        <mkdir dir="classes"/>
        <javac srcdir="src" destdir="classes" debug="true">
            <classpath>
                <fileset dir="${webAppDir}/WEB-INF/lib">
                    <include name="*.jar"/>
                </fileset>
                <fileset dir="webapp/WEB-INF/lib">
                    <include name="*.jar"/>
                </fileset>
            </classpath>
        </javac>
    </target>
 <target name="deploy" depends="compile">
        <echo message="webapp src..."/>
  <copy todir="${webAppDir}">
   <fileset dir="webapp"/>
  </copy>
        <!-- copy classes dir -->
        <echo message="classes..."/>
  <copy todir="${webAppDir}/WEB-INF/classes">
   <fileset dir="classes"/>
  </copy>
 </target>
</project>
Create DynamicEngineServiceImpl.java file

package pl.aszywala.dynamic;

import java.awt.Color;
import java.io.InputStream;
import java.util.Random;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.jaspersoft.jasperserver.api.JSExceptionWrapper;
import com.jaspersoft.jasperserver.api.common.domain.ExecutionContext;
import com.jaspersoft.jasperserver.api.engine.jasperreports.service.impl.EngineServiceImpl;
import com.jaspersoft.jasperserver.api.metadata.common.domain.FileResource;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.design.JRDesignBand;
import net.sf.jasperreports.engine.design.JRDesignExpression;
import net.sf.jasperreports.engine.design.JRDesignField;
import net.sf.jasperreports.engine.design.JRDesignFrame;
import net.sf.jasperreports.engine.design.JRDesignGroup;
import net.sf.jasperreports.engine.design.JRDesignLine;
import net.sf.jasperreports.engine.design.JRDesignParameter;
import net.sf.jasperreports.engine.design.JRDesignSection;
import net.sf.jasperreports.engine.design.JRDesignStaticText;
import net.sf.jasperreports.engine.design.JRDesignStyle;
import net.sf.jasperreports.engine.design.JRDesignTextField;
import net.sf.jasperreports.engine.design.JRDesignVariable;
import net.sf.jasperreports.engine.design.JasperDesign;
import net.sf.jasperreports.engine.type.CalculationEnum;
import net.sf.jasperreports.engine.type.HorizontalAlignEnum;
import net.sf.jasperreports.engine.type.ModeEnum;
import net.sf.jasperreports.engine.type.PositionTypeEnum;
import net.sf.jasperreports.engine.type.ResetTypeEnum;
import net.sf.jasperreports.repo.JasperDesignCache;

public class DynamicEngineServiceImpl extends EngineServiceImpl {

 protected static final Log log = LogFactory.getLog(DynamicEngineServiceImpl.class);

 protected JasperReport getJasperReport(ExecutionContext context, JasperDesignCache cache, FileResource reportRes,
   String location, boolean inMemoryUnit) {
  if ("/reports/dynamic".equals(location)) {
   return getDynamicReport(context, reportRes);
  } else {
   return super.getJasperReport(context, cache, reportRes, location, inMemoryUnit);
  }
 }

 private JasperReport getDynamicReport(ExecutionContext context, FileResource reportRes) {
  try {
   return compileReport(getJasperDesign(loadReportDesign(getFileResourceDataStream(context, reportRes))));
  } catch (JRException e) {
   log.error(e, e);
   throw new JSExceptionWrapper(e);
  }
 }

 private static JasperDesign getJasperDesign(JasperDesign jasperDesign) throws JRException {
  // JasperDesign
  jasperDesign.setName("NoXmlDesignReport");
  jasperDesign.setPageWidth(595);
  jasperDesign.setPageHeight(842);
  jasperDesign.setColumnWidth(515);
  jasperDesign.setColumnSpacing(0);
  jasperDesign.setLeftMargin(40);
  jasperDesign.setRightMargin(40);
  jasperDesign.setTopMargin(50);
  jasperDesign.setBottomMargin(50);

  // Fonts
  JRDesignStyle normalStyle = new JRDesignStyle();
  normalStyle.setName("Sans_Normal");
  normalStyle
    .setForecolor(new Color(new Random().nextFloat(), new Random().nextFloat(), new Random().nextFloat()));
  normalStyle.setDefault(true);
  normalStyle.setFontName("DejaVu Sans");
  normalStyle.setPdfFontName("Helvetica");
  normalStyle.setPdfEncoding("Cp1252");
  normalStyle.setPdfEmbedded(false);
  jasperDesign.addStyle(normalStyle);

  JRDesignStyle boldStyle = new JRDesignStyle();
  boldStyle.setName("Sans_Bold");
  boldStyle.setFontName("DejaVu Sans");
  boldStyle.setBold(true);
  boldStyle.setPdfFontName("Helvetica-Bold");
  boldStyle.setPdfEncoding("Cp1252");
  boldStyle.setPdfEmbedded(false);
  jasperDesign.addStyle(boldStyle);

  JRDesignStyle italicStyle = new JRDesignStyle();
  italicStyle.setName("Sans_Italic");
  italicStyle.setFontName("DejaVu Sans");
  italicStyle.setItalic(true);
  italicStyle.setPdfFontName("Helvetica-Oblique");
  italicStyle.setPdfEncoding("Cp1252");
  italicStyle.setPdfEmbedded(false);
  jasperDesign.addStyle(italicStyle);

  // Parameters
  JRDesignParameter parameter = new JRDesignParameter();
  parameter.setName("ReportTitle");
  parameter.setValueClass(java.lang.String.class);
  jasperDesign.addParameter(parameter);

  parameter = new JRDesignParameter();
  parameter.setName("OrderByClause");
  parameter.setValueClass(java.lang.String.class);
  jasperDesign.addParameter(parameter);

  JRDesignField field = new JRDesignField();
  field.setName("FirstName");
  field.setValueClass(java.lang.String.class);
  jasperDesign.addField(field);

  field = new JRDesignField();
  field.setName("LastName");
  field.setValueClass(java.lang.String.class);
  jasperDesign.addField(field);

  field = new JRDesignField();
  field.setName("Street");
  field.setValueClass(java.lang.String.class);
  jasperDesign.addField(field);

  field = new JRDesignField();
  field.setName("City");
  field.setValueClass(java.lang.String.class);
  jasperDesign.addField(field);

  // Variables
  JRDesignVariable variable = new JRDesignVariable();
  variable.setName("CityNumber");
  variable.setValueClass(java.lang.Integer.class);
  variable.setResetType(ResetTypeEnum.GROUP);
  JRDesignGroup group = new JRDesignGroup();
  group.setName("CityGroup");
  variable.setResetGroup(group);
  variable.setCalculation(CalculationEnum.SYSTEM);
  JRDesignExpression expression = new JRDesignExpression();
  expression.setValueClass(java.lang.Integer.class);
  expression.setText("($V{CityNumber} != null)?(new Integer($V{CityNumber}.intValue() + 1)):(new Integer(1))");
  variable.setInitialValueExpression(expression);
  jasperDesign.addVariable(variable);

  variable = new JRDesignVariable();
  variable.setName("AllCities");
  variable.setValueClass(java.lang.String.class);
  variable.setResetType(ResetTypeEnum.REPORT);
  variable.setCalculation(CalculationEnum.SYSTEM);
  jasperDesign.addVariable(variable);

  // Groups
  group.setMinHeightToStartNewPage(60);
  expression = new JRDesignExpression();
  expression.setValueClass(java.lang.String.class);
  expression.setText("$F{City}");
  group.setExpression(expression);

  JRDesignBand band = new JRDesignBand();
  band.setHeight(20);
  JRDesignTextField textField = new JRDesignTextField();
  textField.setX(0);
  textField.setY(4);
  textField.setWidth(515);
  textField.setHeight(15);
  textField.setBackcolor(new Color(0xC0, 0xC0, 0xC0));
  textField.setMode(ModeEnum.OPAQUE);
  textField.setHorizontalAlignment(HorizontalAlignEnum.LEFT);
  textField.setStyle(boldStyle);
  expression = new JRDesignExpression();
  expression.setValueClass(java.lang.String.class);
  expression.setText("\"  \" + String.valueOf($V{CityNumber}) + \". \" + String.valueOf($F{City})");
  textField.setExpression(expression);
  band.addElement(textField);
  JRDesignLine line = new JRDesignLine();
  line.setX(0);
  line.setY(19);
  line.setWidth(515);
  line.setHeight(0);
  band.addElement(line);
  ((JRDesignSection) group.getGroupHeaderSection()).addBand(band);

  band = new JRDesignBand();
  band.setHeight(20);
  line = new JRDesignLine();
  line.setX(0);
  line.setY(-1);
  line.setWidth(515);
  line.setHeight(0);
  band.addElement(line);
  JRDesignStaticText staticText = new JRDesignStaticText();
  staticText.setX(400);
  staticText.setY(0);
  staticText.setWidth(60);
  staticText.setHeight(15);
  staticText.setHorizontalAlignment(HorizontalAlignEnum.RIGHT);
  staticText.setStyle(boldStyle);
  staticText.setText("Count : ");
  band.addElement(staticText);
  textField = new JRDesignTextField();
  textField.setX(460);
  textField.setY(0);
  textField.setWidth(30);
  textField.setHeight(15);
  textField.setHorizontalAlignment(HorizontalAlignEnum.RIGHT);
  textField.setStyle(boldStyle);
  expression = new JRDesignExpression();
  expression.setValueClass(java.lang.Integer.class);
  expression.setText("$V{CityGroup_COUNT}");
  textField.setExpression(expression);
  band.addElement(textField);
  ((JRDesignSection) group.getGroupFooterSection()).addBand(band);

  jasperDesign.addGroup(group);

  // Title
  band = new JRDesignBand();
  band.setHeight(50);
  line = new JRDesignLine();
  line.setX(0);
  line.setY(0);
  line.setWidth(515);
  line.setHeight(0);
  band.addElement(line);
  textField = new JRDesignTextField();
  textField.setBlankWhenNull(true);
  textField.setX(0);
  textField.setY(10);
  textField.setWidth(515);
  textField.setHeight(30);
  textField.setHorizontalAlignment(HorizontalAlignEnum.CENTER);
  textField.setStyle(normalStyle);
  textField.setFontSize(22);
  expression = new JRDesignExpression();
  expression.setValueClass(java.lang.String.class);
  expression.setText("$P{ReportTitle}");
  textField.setExpression(expression);
  band.addElement(textField);
  jasperDesign.setTitle(band);

  // Page header
  band = new JRDesignBand();
  band.setHeight(20);
  JRDesignFrame frame = new JRDesignFrame();
  frame.setX(0);
  frame.setY(5);
  frame.setWidth(515);
  frame.setHeight(15);
  frame.setForecolor(new Color(0x33, 0x33, 0x33));
  frame.setBackcolor(new Color(0x33, 0x33, 0x33));
  frame.setMode(ModeEnum.OPAQUE);
  band.addElement(frame);
  staticText = new JRDesignStaticText();
  staticText.setX(0);
  staticText.setY(0);
  staticText.setWidth(55);
  staticText.setHeight(15);
  staticText.setForecolor(Color.white);
  staticText.setBackcolor(new Color(0x33, 0x33, 0x33));
  staticText.setMode(ModeEnum.OPAQUE);
  staticText.setHorizontalAlignment(HorizontalAlignEnum.CENTER);
  staticText.setStyle(boldStyle);
  staticText.setText("First Name");
  frame.addElement(staticText);
  staticText = new JRDesignStaticText();
  staticText.setX(55);
  staticText.setY(0);
  staticText.setWidth(205);
  staticText.setHeight(15);
  staticText.setForecolor(Color.white);
  staticText.setBackcolor(new Color(0x33, 0x33, 0x33));
  staticText.setMode(ModeEnum.OPAQUE);
  staticText.setStyle(boldStyle);
  staticText.setText("Last Name");
  frame.addElement(staticText);
  staticText = new JRDesignStaticText();
  staticText.setX(260);
  staticText.setY(0);
  staticText.setWidth(255);
  staticText.setHeight(15);
  staticText.setForecolor(Color.white);
  staticText.setBackcolor(new Color(0x33, 0x33, 0x33));
  staticText.setMode(ModeEnum.OPAQUE);
  staticText.setStyle(boldStyle);
  staticText.setText("Street");
  frame.addElement(staticText);
  jasperDesign.setPageHeader(band);

  // Column header
  band = new JRDesignBand();
  jasperDesign.setColumnHeader(band);

  // Detail
  band = new JRDesignBand();
  band.setHeight(20);
  textField = new JRDesignTextField();
  textField.setX(0);
  textField.setY(4);
  textField.setWidth(50);
  textField.setHeight(15);
  textField.setHorizontalAlignment(HorizontalAlignEnum.RIGHT);
  textField.setStyle(normalStyle);
  expression = new JRDesignExpression();
  expression.setValueClass(java.lang.Integer.class);
  expression.setText("$F{FirstName}");
  textField.setExpression(expression);
  band.addElement(textField);
  textField = new JRDesignTextField();
  textField.setStretchWithOverflow(true);
  textField.setX(55);
  textField.setY(4);
  textField.setWidth(200);
  textField.setHeight(15);
  textField.setPositionType(PositionTypeEnum.FLOAT);
  textField.setStyle(normalStyle);
  expression = new JRDesignExpression();
  expression.setValueClass(java.lang.String.class);
  expression.setText("$F{LastName}");
  textField.setExpression(expression);
  band.addElement(textField);
  textField = new JRDesignTextField();
  textField.setStretchWithOverflow(true);
  textField.setX(260);
  textField.setY(4);
  textField.setWidth(255);
  textField.setHeight(15);
  textField.setPositionType(PositionTypeEnum.FLOAT);
  textField.setStyle(normalStyle);
  expression = new JRDesignExpression();
  expression.setValueClass(java.lang.String.class);
  expression.setText("$F{Street}");
  textField.setExpression(expression);
  band.addElement(textField);
  line = new JRDesignLine();
  line.setX(0);
  line.setY(19);
  line.setWidth(515);
  line.setHeight(0);
  line.setForecolor(new Color(0x80, 0x80, 0x80));
  line.setPositionType(PositionTypeEnum.FLOAT);
  band.addElement(line);
  ((JRDesignSection) jasperDesign.getDetailSection()).addBand(band);

  // Column footer
  band = new JRDesignBand();
  jasperDesign.setColumnFooter(band);

  // Page footer
  band = new JRDesignBand();
  jasperDesign.setPageFooter(band);

  // Summary
  band = new JRDesignBand();
  jasperDesign.setSummary(band);

  return jasperDesign;
 }
}
When report location equals to /reports/dynamic then report is generated programmatically. Every time when report is generated font is changed to random color so you can see that report is changing. </ br> Open js.spring.properties file. It is located in WEB-INF folder of the JasperServer web application directory in my case is c:\Jaspersoft\jasperreports-server-cp-6.3.0\apache-tomcat\webapps\jasperserver\WEB-INF\ and change bean.class.engineService to:
bean.class.engineService=pl.aszywala.dynamic.DynamicEngineServiceImpl
Let's create custom datasource for the report. Create DynamicDataSource class
package pl.aszywala.dynamic;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRField;

public class DynamicDataSource implements JRDataSource {

 private Object[][] data = { { "Katowice", "Jan", "Kowalski", "Staszica 1" },
   { "Katowice", "Piotr", "Nowak", "Warszawska 11" },
   { "New York", "Andrew", "May", "172 Seventh Av." },
   { "New York", "Sylvia", "Ott", "361 College Av." },
   { "New York", "Bill", "King", "546 College Av." },
   { "Paris", "Sylvia", "Steel", "269 College Av." },
   { "Paris", "Sylvia", "Fuller", "158 - 20th Ave." },
   { "Paris", "Laura", "Miller", "294 Seventh Av." },
   { "San Francisco","Robert", "White", "549 Seventh Av." },
   { "San Francisco", "James", "Peterson", "231 Upland Pl." } };

 private int index = -1;

 public DynamicDataSource() {
 }

 public boolean next() throws JRException {
  return (++index < data.length);
 }

 public Object getFieldValue(JRField field) throws JRException {
  Object value = null;

  String fieldName = field.getName();

  if ("City".equals(fieldName)) {
   value = data[index][0];
  } else if ("FirstName".equals(fieldName)) {
   value = data[index][1];
  } else if ("LastName".equals(fieldName)) {
   value = data[index][2];
  } else if ("Street".equals(fieldName)) {
   value = data[index][3];
  }

  return value;
 }

}
Create DynamicDataSourceService class
package pl.aszywala.dynamic;

import java.util.Map;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRParameter;

import com.jaspersoft.jasperserver.api.metadata.common.service.RepositoryService;
import com.jaspersoft.jasperserver.api.metadata.jasperreports.service.ReportDataSourceService;

public class DynamicDataSourceService implements ReportDataSourceService {

 JRDataSource ds;
 private RepositoryService repository;
 private Map propertyMap;
 
 public Map getPropertyMap() {
  return propertyMap;
 }

 public void setPropertyMap(Map propertyMap) {
  this.propertyMap = propertyMap;
 }

 public DynamicDataSourceService() {
  this.ds = new DynamicDataSource();
 }
 
 public DynamicDataSourceService(JRDataSource ds) {
  this.ds = ds;
 }
 
 public void closeConnection() {
  // Do nothing
 }

 public void setReportParameterValues(Map parameterValues) {
  parameterValues.put(JRParameter.REPORT_DATA_SOURCE, ds);
 }

 public RepositoryService getRepository() {
  return repository;
 }

 public void setRepository(RepositoryService repository) {
  this.repository = repository;
 }

}
In WEB-INF/bundles directory create dynamic.properties file
dynamicDataSource.name=Dynamic Data Source
SEARCH_dynamicDataSource=Dynamic Data Source
resource.dynamicDataSource.label=Dynamic Data Source
The last thing is to create applicationContext-dynamic.xml file
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
       
 <bean id="dynamicDataSource" class="com.jaspersoft.jasperserver.api.engine.jasperreports.util.CustomDataSourceDefinition">
  <property name="factory" ref="customDataSourceServiceFactory"/>
  <property name="name" value="dynamicDataSource"/>
  <property name="serviceClassName" value="pl.aszywala.dynamic.DynamicDataSourceService"/> 
  <property name="propertyDefinitions">
   <list/>    
  </property>
  <property name="queryExecuterMap">
   <map/>
  </property> 
 </bean>

    <bean class="com.jaspersoft.jasperserver.api.common.util.spring.GenericBeanUpdater">
        <property name="definition" ref="addMessageCatalog"/>
        <property name="value">
            <list>
                <value>WEB-INF/bundles/dynamic</value>
            </list>
        </property>
        <property name="valueType" value="stringList"/>
    </bean>
</beans>
Run ant command in directory where your build.xml file is placed. It should build the project and copy files to the JasperServer webapp directory. Restart JasperServer and login to the dashboard.
Add new datasource
Add new report
And connect it to the dynamicDS datasource
Run the raport
Source code for the blog post is available on the GitHub

Saturday, November 5, 2016

mod_cluster as a Service Discovery with Wildfly and Docker

In this blog post I'll show how to deploy microservices which uses mod_cluster instead Service Discovery. First let's create Dockerfile with mod_cluster as load balancer:
FROM fedora
RUN dnf -y update
RUN dnf -y install httpd
RUN dnf -y install mod_cluster
RUN dnf -y install mod_ssl
RUN dnf clean all

RUN sed -i "s|LoadModule proxy_balancer_module|#LoadModule proxy_balancer_module|" /etc/httpd/conf.modules.d/00-proxy.conf

ADD mod_cluster.conf /etc/httpd/conf.d/mod_cluster.conf
RUN mkdir /etc/httpd/conf/certs
RUN openssl req \
    -new \
    -newkey rsa:4096 \
    -days 365 \
    -nodes \
    -x509 \
    -subj "/C=PL/ST=Slaskie/L=Katowice/O=aszywala/OU=aszywala/CN=Andrzej Szywala" \
    -keyout www.aszywala.key \
    -out www.aszywala.crt
EXPOSE 80
EXPOSE 443
CMD ["/sbin/httpd", "-DFOREGROUND"]

Next I'll create mod_cluster.conf
LoadModule proxy_cluster_module modules/mod_proxy_cluster.so
LoadModule cluster_slotmem_module modules/mod_cluster_slotmem.so
LoadModule manager_module modules/mod_manager.so
LoadModule advertise_module modules/mod_advertise.so

<IfModule manager_module>

  Maxhost 100

  <VirtualHost *:80>
    <Directory />
        Require all granted
    </Directory>
    <Location /mod_cluster_manager>
        SetHandler mod_cluster-manager
        Require all granted
    </Location>
    <Location /server-status>
        SetHandler server-status
    </Location>

    KeepAliveTimeout 60
    ManagerBalancerName mycluster
    ServerAdvertise On
    EnableMCPMReceive On
  </VirtualHost>
</IfModule>

Build Dockerfile
docker build -t lb .
Create docker network
docker network create mynet
Run container with load balancer
docker run --name lb -it --rm --net mynet -p 80:80 -p 443:443 lb
You can open web browser and check if mod_cluster works http://localhost/mod_cluster_manager
Create Dockerfile with micro service application.
FROM jboss/wildfly
MAINTAINER Andrzej Szywala 
ADD customization $JBOSS_HOME
RUN /opt/jboss/wildfly/bin/jboss-cli.sh --file=/opt/jboss/wildfly/wildfly_conf.txt
# FIX for Error: WFLYCTL0056: Could not rename /opt/jboss/wildfly/standalone/configuration/standalone_xml_history/current...
RUN rm -rf /opt/jboss/wildfly/standalone/configuration/standalone_xml_history/current/*
ADD hellojee.war $JBOSS_HOME/standalone/deployments/hellojee.war
wildfly_conf.txt file configures mod_cluster and ajp
embed-server --server-config=standalone.xml
batch
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=lb:add(host="lb",port="80")
/extension=org.jboss.as.modcluster:add
/subsystem=undertow/server=default-server/ajp-listener=ajp:add(socket-binding=ajp, scheme="http")
/:composite(steps=[{"operation" => "add", "address" => [ ("subsystem" => "modcluster") ]},{ "operation" => "add", "address" => [ ("subsystem" => "modcluster"), ("mod-cluster-config" => "configuration") ], "connector" => "ajp", "balancer" => "hellojee", "advertise" => "false", "proxies" => ["lb"] }])
# Execute the batch
run-batch
reload
stop-embedded-server
quit
Build Dockerfile
docker build -t hellojee_cluster .
Run container
docker run --name hellojee_cluster -it --rm --net mynet hellojee_cluster
Check if application registers in loadbalancer http://localhost/mod_cluster_manager

Open browser http://localhost/hellojee/resources/hello. You should see response from application
{"database":"H2 - 1.3.173 (2013-07-28)","host":"0461b41cff7f (172.20.0.3)","hello":["Hello","Witaj","Hallo","Hej","Ahoj","Bonjour","Hola"]}
When you stop the container application will be automatically deregistered from mod_cluster. When you kill the container mod_cluster will notice that and change application status to NOTOK.

Sources for this post can be found on Github https://github.com/andrzejszywala/docker-images/tree/master/hellojee/mod_cluster