Prev
Next
Microservices
This tutorial is Maven and command-line based; the reader may follow this verbatim or use their favorite Java IDE.
Introduction
Using the enRoute Archetypes this tutorial walks through the creation of a REST Microservice comprised of the following structural elements:
An API module
A DAO Implementation module
A REST Service Implementation module
The Composite Application module
with each module having a POM that describes its dependencies.
We start by creating the required project skeleton.
For this tutorial we put the project in the ~
(AKA /home/user
) directory. If you put your project in a different directory, be sure to replace the ~
with your directory path when it appears in shell snippets in the tutorial.
Creating the Project
Using the bare-project Archetype , in your project root directory create the microservice project:
~ $ mvn archetype:generate \
-DarchetypeGroupId=org.osgi.enroute.archetype \
-DarchetypeArtifactId=project-bare \
-DarchetypeVersion=7.0.0
with the following values:
Define value for property 'groupId': org.osgi.enroute.examples
Define value for property 'artifactId': microservice
Define value for property 'version' 1.0-SNAPSHOT: : 0.0.1-SNAPSHOT
Define value for property 'package' org.osgi.enroute.examples: :
Confirm properties configuration:
groupId: org.osgi.enroute.examples
artifactId: microservice
version: 0.0.1-SNAPSHOT
package: org.osgi.enroute.examples
Y: :
Note - if you use alternative groupId
, artifactId
values remember to update the package-info.java
and import
statements in the files used throughout the rest of this tutorial.
We now create the required modules.
The DAO API
Change directory into the newly created microservice
project directory; then create the api
module using the api Archetype as shown:
~ $ cd microservice
~/microservice $ mvn archetype:generate \
-DarchetypeGroupId=org.osgi.enroute.archetype \
-DarchetypeArtifactId=api \
-DarchetypeVersion=7.0.0
with the following values:
Define value for property 'groupId': org.osgi.enroute.examples.microservice
Define value for property 'artifactId': dao-api
Define value for property 'version' 1.0-SNAPSHOT: : 0.0.1-SNAPSHOT
Define value for property 'package' org.osgi.enroute.examples.microservice.dao.api: :org.osgi.enroute.examples.microservice.dao
Confirm properties configuration:
groupId: org.osgi.enroute.examples.microservice
artifactId: dao-api
version: 0.0.1-SNAPSHOT
package: org.osgi.enroute.examples.microservice.dao
Y: :
Now create the following two files:
~/microservice/dao-api/src/main/java/org/osgi/enroute/examples/microservice/dao/PersonDao.java
PersonDAO.java
package org.osgi.enroute.examples.microservice.dao ;
import java.util.List ;
import org.osgi.annotation.versioning.ProviderType ;
import org.osgi.enroute.examples.microservice.dao.dto.PersonDTO ;
@ProviderType
public interface PersonDao {
public List < PersonDTO > select ();
public PersonDTO findByPK ( Long pk ) ;
public Long save ( PersonDTO data );
public void update ( PersonDTO data );
public void delete ( Long pk ) ;
}
~/microservice/dao-api/src/main/java/org/osgi/enroute/examples/microservice/dao/AddressDao.java
AddressDAO.java
package org.osgi.enroute.examples.microservice.dao ;
import java.util.List ;
import org.osgi.annotation.versioning.ProviderType ;
import org.osgi.enroute.examples.microservice.dao.dto.AddressDTO ;
@ProviderType
public interface AddressDao {
public List < AddressDTO > select ( Long personId );
public AddressDTO findByPK ( String emailAddress );
public void save ( Long personId , AddressDTO data );
public void update ( Long personId , AddressDTO data );
public void delete ( Long personId ) ;
}
Dependencies
dao-api
has no dependencies.
Visibility
dao-api
is an API package which is imported by RestComponentImpl
, PersonDaoImpl
& AddressDaoImpl
; hence it must must be exported. This is indicated by the automatically generated file ~/microservice/dao-api/src/main/java/org/osgi/enroute/examples/microservice/dao/package-info.java
:
package-info.java
@org . osgi . annotation . bundle . Export
@org . osgi . annotation . versioning . Version ( "1.0.0" )
package org.osgi.enroute.examples.microservice.dao ;
For further detail see Semantic Versioning .
Defining the DTO
Data transfer between the components is achieved via the use of Data Transfer Objects (DTOs) .
To achieve this create the following two files:
~/microservice/dao-api/src/main/java/org/osgi/enroute/examples/microservice/dao/dto/PersonDTO.java
PersonDTO.java
package org.osgi.enroute.examples.microservice.dao.dto ;
import java.util.ArrayList ;
import java.util.List ;
public class PersonDTO {
public long personId ;
public String firstName ;
public String lastName ;
public List < AddressDTO > addresses = new ArrayList <>();
}
~/microservice/dao-api/src/main/java/org/osgi/enroute/examples/microservice/dao/dto/AddressDTO.java
AddressDTO.java
package org.osgi.enroute.examples.microservice.dao.dto ;
public class AddressDTO {
public long personId ;
public String emailAddress ;
public String city ;
public String country ;
}
and again, we advertise this Capability by creating the following package-info.java
file:
~/microservice/dao-api/src/main/java/org/osgi/enroute/examples/microservice/dao/dto/package-info.java
package-info.java
@org . osgi . annotation . bundle . Export
@org . osgi . annotation . versioning . Version ( "1.0.0" )
package org.osgi.enroute.examples.microservice.dao.dto ;
The DAO Implementation
Now, in the microservice
project directory, create the impl
module using the ds-component Archetype :
~/microservice $ mvn archetype:generate \
-DarchetypeGroupId=org.osgi.enroute.archetype \
-DarchetypeArtifactId=ds-component \
-DarchetypeVersion=7.0.0
with the following values:
Define value for property 'groupId': org.osgi.enroute.examples.microservice
Define value for property 'artifactId': dao-impl
Define value for property 'version' 1.0-SNAPSHOT: : 0.0.1-SNAPSHOT
Define value for property 'package' org.osgi.enroute.examples.microservice.dao.impl: :
Confirm properties configuration:
groupId: org.osgi.enroute.examples.microservice
artifactId: dao-impl
version: 0.0.1-SNAPSHOT
package: org.osgi.enroute.examples.microservice.dao.impl
Y: :
Now create the following four files:
~/microservice/dao-impl/src/main/java/org/osgi/enroute/examples/microservice/dao/impl/PersonTable.java
PersonTable.java
package org.osgi.enroute.examples.microservice.dao.impl ;
public interface PersonTable {
String TABLE_NAME = "PERSONS" ;
String SQL_SELECT_ALL_PERSONS = "SELECT * FROM " + TABLE_NAME ;
String SQL_DELETE_PERSON_BY_PK = "DELETE FROM " + TABLE_NAME + " where PERSON_ID=?" ;
String SQL_SELECT_PERSON_BY_PK = "SELECT * FROM " + TABLE_NAME + " where PERSON_ID=?" ;
String SQL_INSERT_PERSON = "INSERT INTO " + TABLE_NAME + "(FIRST_NAME,LAST_NAME) VALUES(?,?)" ;
String SQL_UPDATE_PERSON_BY_PK = "UPDATE " + TABLE_NAME + " SET FIRST_NAME=?, LAST_NAME=? WHERE PERSON_ID=?" ;
String PERSON_ID = "person_id" ;
String FIRST_NAME = "first_name" ;
String LAST_NAME = "last_name" ;
String INIT = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" //
+ "person_id bigint GENERATED BY DEFAULT AS IDENTITY," //
+ "first_name varchar(255) NOT NULL," //
+ "last_name varchar(255) NOT NULL," //
+ "PRIMARY KEY (person_id)" //
+ ") ;" ;
}
~/microservice/dao-impl/src/main/java/org/osgi/enroute/examples/microservice/dao/impl/PersonDaoImpl.java
PersonDaoImpl.java
package org.osgi.enroute.examples.microservice.dao.impl ;
import static java . sql . Statement . RETURN_GENERATED_KEYS ;
import static org . osgi . enroute . examples . microservice . dao . impl . PersonTable . FIRST_NAME ;
import static org . osgi . enroute . examples . microservice . dao . impl . PersonTable . INIT ;
import static org . osgi . enroute . examples . microservice . dao . impl . PersonTable . LAST_NAME ;
import static org . osgi . enroute . examples . microservice . dao . impl . PersonTable . PERSON_ID ;
import static org . osgi . enroute . examples . microservice . dao . impl . PersonTable . SQL_DELETE_PERSON_BY_PK ;
import static org . osgi . enroute . examples . microservice . dao . impl . PersonTable . SQL_INSERT_PERSON ;
import static org . osgi . enroute . examples . microservice . dao . impl . PersonTable . SQL_SELECT_ALL_PERSONS ;
import static org . osgi . enroute . examples . microservice . dao . impl . PersonTable . SQL_SELECT_PERSON_BY_PK ;
import static org . osgi . enroute . examples . microservice . dao . impl . PersonTable . SQL_UPDATE_PERSON_BY_PK ;
import java.sql.Connection ;
import java.sql.PreparedStatement ;
import java.sql.ResultSet ;
import java.sql.SQLException ;
import java.util.ArrayList ;
import java.util.List ;
import java.util.Map ;
import java.util.concurrent.atomic.AtomicLong ;
import org.osgi.enroute.examples.microservice.dao.AddressDao ;
import org.osgi.enroute.examples.microservice.dao.PersonDao ;
import org.osgi.enroute.examples.microservice.dao.dto.PersonDTO ;
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.transaction.control.TransactionControl ;
import org.osgi.service.transaction.control.jdbc.JDBCConnectionProvider ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
@Component
public class PersonDaoImpl implements PersonDao {
private static final Logger logger = LoggerFactory . getLogger ( PersonDaoImpl . class );
@Reference
TransactionControl transactionControl ;
@Reference ( name = "provider" )
JDBCConnectionProvider jdbcConnectionProvider ;
@Reference
AddressDao addressDao ;
Connection connection ;
@Activate
void start ( Map < String , Object > props ) throws SQLException {
connection = jdbcConnectionProvider . getResource ( transactionControl );
transactionControl . supports (() -> connection . prepareStatement ( INIT ). execute ());
}
@Override
public List < PersonDTO > select () {
return transactionControl . notSupported (() -> {
List < PersonDTO > dbResults = new ArrayList <>();
ResultSet rs = connection . createStatement (). executeQuery ( SQL_SELECT_ALL_PERSONS );
while ( rs . next ()) {
PersonDTO personDTO = mapRecordToPerson ( rs );
personDTO . addresses = addressDao . select ( personDTO . personId );
dbResults . add ( personDTO );
}
return dbResults ;
});
}
@Override
public void delete ( Long primaryKey ) {
transactionControl . required (() -> {
PreparedStatement pst = connection . prepareStatement ( SQL_DELETE_PERSON_BY_PK );
pst . setLong ( 1 , primaryKey );
pst . executeUpdate ();
addressDao . delete ( primaryKey );
logger . info ( "Deleted Person with ID : {}" , primaryKey );
return null ;
});
}
@Override
public PersonDTO findByPK ( Long pk ) {
return transactionControl . supports (() -> {
PersonDTO personDTO = null ;
PreparedStatement pst = connection . prepareStatement ( SQL_SELECT_PERSON_BY_PK );
pst . setLong ( 1 , pk );
ResultSet rs = pst . executeQuery ();
if ( rs . next ()) {
personDTO = mapRecordToPerson ( rs );
personDTO . addresses = addressDao . select ( pk );
}
return personDTO ;
});
}
@Override
public Long save ( PersonDTO data ) {
return transactionControl . required (() -> {
PreparedStatement pst = connection . prepareStatement ( SQL_INSERT_PERSON , RETURN_GENERATED_KEYS );
pst . setString ( 1 , data . firstName );
pst . setString ( 2 , data . lastName );
pst . executeUpdate ();
AtomicLong genPersonId = new AtomicLong ( data . personId );
if ( genPersonId . get () <= 0 ) {
ResultSet genKeys = pst . getGeneratedKeys ();
if ( genKeys . next ()) {
genPersonId . set ( genKeys . getLong ( 1 ));
}
}
logger . info ( "Saved Person with ID : {}" , genPersonId . get ());
if ( genPersonId . get () > 0 ) {
data . addresses . stream (). forEach ( address -> {
address . personId = genPersonId . get ();
addressDao . save ( genPersonId . get (), address );
});
}
return genPersonId . get ();
});
}
@Override
public void update ( PersonDTO data ) {
transactionControl . required (() -> {
PreparedStatement pst = connection . prepareStatement ( SQL_UPDATE_PERSON_BY_PK );
pst . setString ( 1 , data . firstName );
pst . setString ( 2 , data . lastName );
pst . setLong ( 3 , data . personId );
pst . executeUpdate ();
logger . info ( "Updated person : {}" , data );
data . addresses . stream (). forEach ( address -> addressDao . update ( data . personId , address ));
return null ;
});
}
protected PersonDTO mapRecordToPerson ( ResultSet rs ) throws SQLException {
PersonDTO personDTO = new PersonDTO ();
personDTO . personId = rs . getLong ( PERSON_ID );
personDTO . firstName = rs . getString ( FIRST_NAME );
personDTO . lastName = rs . getString ( LAST_NAME );
return personDTO ;
}
}
~/microservice/dao-impl/src/main/java/org/osgi/enroute/examples/microservice/dao/impl/AddressTable.java
AddressTable.java
package org.osgi.enroute.examples.microservice.dao.impl ;
public interface AddressTable {
String TABLE_NAME = "ADDRESSES" ;
String SQL_SELECT_ADDRESS_BY_PERSON = "SELECT * FROM " + TABLE_NAME + " WHERE PERSON_ID = ? " ;
String SQL_DELETE_ADDRESS = "DELETE FROM " + TABLE_NAME + " WHERE EMAIL_ADDRESS = ? AND PERSON_ID=?" ;
String SQL_DELETE_ALL_ADDRESS_BY_PERSON_ID = "DELETE FROM " + TABLE_NAME + " WHERE PERSON_ID=?" ;
String SQL_SELECT_ADDRESS_BY_PK = "SELECT * FROM " + TABLE_NAME + " where EMAIL_ADDRESS=?" ;
String SQL_ADD_ADDRESS = "INSERT INTO " + TABLE_NAME + "(EMAIL_ADDRESS,PERSON_ID,CITY,COUNTRY) VALUES(?,?,?,?)" ;
String SQL_UPDATE_ADDRESS_BY_PK_AND_PERSON_ID = "UPDATE " + TABLE_NAME + " SET CITY=?, COUNTRY=? "
+ "WHERE EMAIL_ADDRESS = ? AND PERSON_ID=?" ;
String PERSON_ID = "person_id" ;
String EMAIL_ADDRESS = "email_address" ;
String CITY = "city" ;
String COUNTRY = "country" ;
String INIT = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" //
+ "email_address varchar(255) NOT NULL," //
+ "person_id bigint NOT NULL," //
+ "city varchar(100) NOT NULL," //
+ "country varchar(2) NOT NULL," //
+ "PRIMARY KEY (email_address)" + ") ;" ;
}
~/microservice/dao-impl/src/main/java/org/osgi/enroute/examples/microservice/dao/impl/AddressDaoImpl.java
AddressDaoImpl.java
package org.osgi.enroute.examples.microservice.dao.impl ;
import static org . osgi . enroute . examples . microservice . dao . impl . AddressTable . SQL_ADD_ADDRESS ;
import static org . osgi . enroute . examples . microservice . dao . impl . AddressTable . SQL_DELETE_ALL_ADDRESS_BY_PERSON_ID ;
import static org . osgi . enroute . examples . microservice . dao . impl . AddressTable . SQL_SELECT_ADDRESS_BY_PERSON ;
import static org . osgi . enroute . examples . microservice . dao . impl . AddressTable . SQL_SELECT_ADDRESS_BY_PK ;
import static org . osgi . enroute . examples . microservice . dao . impl . AddressTable . SQL_UPDATE_ADDRESS_BY_PK_AND_PERSON_ID ;
import java.sql.Connection ;
import java.sql.PreparedStatement ;
import java.sql.ResultSet ;
import java.sql.SQLException ;
import java.util.ArrayList ;
import java.util.List ;
import java.util.Map ;
import org.osgi.enroute.examples.microservice.dao.AddressDao ;
import org.osgi.enroute.examples.microservice.dao.dto.AddressDTO ;
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.transaction.control.TransactionControl ;
import org.osgi.service.transaction.control.jdbc.JDBCConnectionProvider ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
@Component
public class AddressDaoImpl implements AddressDao {
private static final Logger logger = LoggerFactory . getLogger ( AddressDaoImpl . class );
@Reference
TransactionControl transactionControl ;
@Reference ( name = "provider" )
JDBCConnectionProvider jdbcConnectionProvider ;
Connection connection ;
@Activate
void activate ( Map < String , Object > props ) throws SQLException {
connection = jdbcConnectionProvider . getResource ( transactionControl );
transactionControl . supports ( () -> connection . prepareStatement ( AddressTable . INIT ). execute ());
}
@Override
public List < AddressDTO > select ( Long personId ) {
return transactionControl . notSupported (() -> {
List < AddressDTO > dbResults = new ArrayList <>();
PreparedStatement pst = connection . prepareStatement ( SQL_SELECT_ADDRESS_BY_PERSON );
pst . setLong ( 1 , personId );
ResultSet rs = pst . executeQuery ();
while ( rs . next ()) {
AddressDTO addressDTO = mapRecordToAddress ( rs );
dbResults . add ( addressDTO );
}
return dbResults ;
});
}
@Override
public AddressDTO findByPK ( String pk ) {
return transactionControl . supports (() -> {
AddressDTO addressDTO = null ;
PreparedStatement pst = connection . prepareStatement ( SQL_SELECT_ADDRESS_BY_PK );
pst . setString ( 1 , pk );
ResultSet rs = pst . executeQuery ();
if ( rs . next ()) {
addressDTO = mapRecordToAddress ( rs );
}
return addressDTO ;
});
}
@Override
public void save ( Long personId , AddressDTO data ) {
transactionControl . required (() -> {
PreparedStatement pst = connection . prepareStatement ( SQL_ADD_ADDRESS );
pst . setString ( 1 , data . emailAddress );
pst . setLong ( 2 , data . personId );
pst . setString ( 3 , data . city );
pst . setString ( 4 , data . country );
logger . info ( "Saved Person with id {} and Address : {}" , personId , data );
pst . executeUpdate ();
return null ;
});
}
@Override
public void update ( Long personId , AddressDTO data ) {
transactionControl . required (() -> {
PreparedStatement pst = connection . prepareStatement ( SQL_UPDATE_ADDRESS_BY_PK_AND_PERSON_ID );
pst . setString ( 1 , data . city );
pst . setString ( 2 , data . country );
pst . setString ( 3 , data . emailAddress );
pst . setLong ( 4 , data . personId );
logger . info ( "Updated Person Address : {}" , data );
pst . executeUpdate ();
return null ;
});
}
@Override
public void delete ( Long personId ) {
transactionControl . required (() -> {
PreparedStatement pst = connection . prepareStatement ( SQL_DELETE_ALL_ADDRESS_BY_PERSON_ID );
pst . setLong ( 1 , personId );
logger . info ( "Deleted Person {} Addresses" , personId );
pst . executeUpdate ();
return null ;
});
}
protected AddressDTO mapRecordToAddress ( ResultSet rs ) throws SQLException {
AddressDTO addressDTO = new AddressDTO ();
addressDTO . personId = rs . getLong ( AddressTable . PERSON_ID );
addressDTO . emailAddress = rs . getString ( AddressTable . EMAIL_ADDRESS );
addressDTO . city = rs . getString ( AddressTable . CITY );
addressDTO . country = rs . getString ( AddressTable . COUNTRY );
return addressDTO ;
}
}
Dependencies
The dao-impl
has a dependency on dao-api
. Also PersonalDaoImpl.java
and AddressDaoImpl.java
implementations (see below) have dependencies on the slf4j
logging API. This dependency information is added to the <dependencies>
section of dao-impl/pom.xml
: i.e. dao-impl
’s repository .
<dependency>
<groupId> org.osgi.enroute.examples.microservice</groupId>
<artifactId> dao-api</artifactId>
<version> 0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId> org.slf4j</groupId>
<artifactId> slf4j-api</artifactId>
<version> 1.7.25</version>
</dependency>
Visibility
Implementations should NOT be shared; hence no package-info.java
file.
The REST Service
In the microservice
project directory now create the rest-component
module using the rest-component Archetype :
~/microservice $ mvn archetype:generate \
-DarchetypeGroupId=org.osgi.enroute.archetype \
-DarchetypeArtifactId=rest-component \
-DarchetypeVersion=7.0.0
with the following values:
Define value for property 'groupId': org.osgi.enroute.examples.microservice
Define value for property 'artifactId': rest-service
Define value for property 'version' 1.0-SNAPSHOT: : 0.0.1-SNAPSHOT
Define value for property 'package' org.osgi.enroute.examples.microservice.rest.service: : org.osgi.enroute.examples.microservice.rest
Confirm properties configuration:
groupId: org.osgi.enroute.examples.microservice
artifactId: rest-service
version: 0.0.1-SNAPSHOT
package: org.osgi.enroute.examples.microservice.rest
Y: :
Now create the following two files:
~/microservice/rest-service/src/main/java/org/osgi/enroute/examples/microservice/rest/RestComponentImpl.java
RestComponentImpl.java
package org.osgi.enroute.examples.microservice.rest ;
import java.util.List ;
import javax.ws.rs.DELETE ;
import javax.ws.rs.GET ;
import javax.ws.rs.POST ;
import javax.ws.rs.Path ;
import javax.ws.rs.PathParam ;
import javax.ws.rs.Produces ;
import javax.ws.rs.core.MediaType ;
import org.osgi.enroute.examples.microservice.dao.PersonDao ;
import org.osgi.enroute.examples.microservice.dao.dto.PersonDTO ;
import org.osgi.service.component.annotations.Component ;
import org.osgi.service.component.annotations.Reference ;
import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardResource ;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired ;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource ;
@Component ( service = RestComponentImpl . class )
@JaxrsResource
@Path ( "person" )
@Produces ( MediaType . APPLICATION_JSON )
@JSONRequired
@HttpWhiteboardResource ( pattern = "/microservice/*" , prefix = "static" )
public class RestComponentImpl {
@Reference
private PersonDao personDao ;
@GET
@Path ( "{person}" )
public PersonDTO getPerson ( @PathParam ( "person" ) Long personId ) {
return personDao . findByPK ( personId );
}
@GET
public List < PersonDTO > getPerson () {
return personDao . select ();
}
@DELETE
@Path ( "{person}" )
public boolean deletePerson ( @PathParam ( "person" ) long personId ) {
personDao . delete ( personId );
return true ;
}
@POST
public PersonDTO postPerson ( PersonDTO person ) {
if ( person . personId > 0 ) {
personDao . update ( person );
return person ;
}
else {
long id = personDao . save ( person );
person . personId = id ;
return person ;
}
}
}
~/microservice/rest-service/src/main/java/org/osgi/enroute/examples/microservice/rest/JsonpConvertingPlugin.java
JsonpConvertingPlugin.java
package org.osgi.enroute.examples.microservice.rest ;
import static javax . ws . rs . core . MediaType . APPLICATION_JSON ;
import static javax . ws . rs . core . MediaType . APPLICATION_JSON_TYPE ;
import static org . osgi . service . component . annotations . ServiceScope . PROTOTYPE ;
import static org . osgi . util . converter . ConverterFunction . CANNOT_HANDLE ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.OutputStream ;
import java.lang.annotation.Annotation ;
import java.lang.reflect.Type ;
import java.math.BigDecimal ;
import java.math.BigInteger ;
import java.util.Collection ;
import java.util.List ;
import java.util.Map ;
import javax.json.Json ;
import javax.json.JsonArray ;
import javax.json.JsonArrayBuilder ;
import javax.json.JsonNumber ;
import javax.json.JsonObject ;
import javax.json.JsonObjectBuilder ;
import javax.json.JsonReader ;
import javax.json.JsonString ;
import javax.json.JsonStructure ;
import javax.json.JsonValue ;
import javax.json.JsonValue.ValueType ;
import javax.json.JsonWriter ;
import javax.ws.rs.WebApplicationException ;
import javax.ws.rs.core.MediaType ;
import javax.ws.rs.core.MultivaluedMap ;
import javax.ws.rs.ext.MessageBodyReader ;
import javax.ws.rs.ext.MessageBodyWriter ;
import org.osgi.service.component.annotations.Component ;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsExtension ;
import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsMediaType ;
import org.osgi.util.converter.Converter ;
import org.osgi.util.converter.Converters ;
import org.osgi.util.converter.TypeReference ;
@Component ( scope = PROTOTYPE )
@JaxrsExtension
@JaxrsMediaType ( APPLICATION_JSON )
public class JsonpConvertingPlugin < T > implements MessageBodyReader < T >, MessageBodyWriter < T > {
private final Converter converter = Converters . newConverterBuilder ()
. rule ( JsonValue . class , this :: toJsonValue )
. rule ( this :: toScalar )
. build ();
private JsonValue toJsonValue ( Object value , Type targetType ) {
if ( value == null ) {
return JsonValue . NULL ;
} else if ( value instanceof String ) {
return Json . createValue ( value . toString ());
} else if ( value instanceof Boolean ) {
return (( Boolean ) value ) ? JsonValue . TRUE : JsonValue . FALSE ;
} else if ( value instanceof Number ) {
Number n = ( Number ) value ;
if ( value instanceof Float || value instanceof Double ) {
return Json . createValue ( n . doubleValue ());
} else if ( value instanceof BigDecimal ) {
return Json . createValue (( BigDecimal ) value );
} else if ( value instanceof BigInteger ) {
return Json . createValue (( BigInteger ) value );
} else {
return Json . createValue ( n . longValue ());
}
} else if ( value instanceof Collection || value . getClass (). isArray ()) {
return toJsonArray ( value );
} else {
return toJsonObject ( value );
}
}
private JsonArray toJsonArray ( Object o ) {
List <?> l = converter . convert ( o ). to ( List . class );
JsonArrayBuilder builder = Json . createArrayBuilder ();
l . forEach ( v -> builder . add ( toJsonValue ( v , JsonValue . class )));
return builder . build ();
}
private JsonObject toJsonObject ( Object o ) {
Map < String , Object > m = converter . convert ( o ). to ( new TypeReference < Map < String , Object >>(){});
JsonObjectBuilder jsonBuilder = Json . createObjectBuilder ();
m . entrySet (). stream (). forEach ( e -> jsonBuilder . add ( e . getKey (), toJsonValue ( e . getValue (), JsonValue . class )));
return jsonBuilder . build ();
}
private Object toScalar ( Object o , Type t ) {
if ( o instanceof JsonNumber ) {
JsonNumber jn = ( JsonNumber ) o ;
return converter . convert ( jn . bigDecimalValue ()). to ( t );
} else if ( o instanceof JsonString ) {
JsonString js = ( JsonString ) o ;
return converter . convert ( js . getString ()). to ( t );
} else if ( o instanceof JsonValue ) {
JsonValue jv = ( JsonValue ) o ;
if ( jv . getValueType () == ValueType . NULL ) {
return null ;
} else if ( jv . getValueType () == ValueType . TRUE ) {
return converter . convert ( Boolean . TRUE ). to ( t );
} else if ( jv . getValueType () == ValueType . FALSE ) {
return converter . convert ( Boolean . FALSE ). to ( t );
}
}
return CANNOT_HANDLE ;
}
@Override
public boolean isWriteable ( Class <?> c , Type t , Annotation [] a , MediaType mediaType ) {
return APPLICATION_JSON_TYPE . isCompatible ( mediaType ) || mediaType . getSubtype (). endsWith ( "+json" );
}
@Override
public boolean isReadable ( Class <?> c , Type t , Annotation [] a , MediaType mediaType ) {
return APPLICATION_JSON_TYPE . isCompatible ( mediaType ) || mediaType . getSubtype (). endsWith ( "+json" );
}
@Override
public void writeTo ( T o , Class <?> arg1 , Type arg2 , Annotation [] arg3 , MediaType arg4 ,
MultivaluedMap < String , java . lang . Object > arg5 , OutputStream out )
throws IOException , WebApplicationException {
JsonValue jv = converter . convert ( o ). to ( JsonValue . class );
try ( JsonWriter jw = Json . createWriter ( out )) {
jw . write ( jv );
}
}
@SuppressWarnings ( "unchecked" )
@Override
public T readFrom ( Class < T > arg0 , Type arg1 , Annotation [] arg2 , MediaType arg3 , MultivaluedMap < String , String > arg4 ,
InputStream in ) throws IOException , WebApplicationException {
try ( JsonReader jr = Json . createReader ( in )) {
JsonStructure read = jr . read ();
return ( T ) converter . convert ( read ). to ( arg1 );
}
}
}
Create the directory ~/microservice/rest-service/src/main/resources/static/main/html
and added the following file:
~/microservice/rest-service/src/main/resources/static/main/html/person.html
person.html
<link rel= "import" href= "https://cdn.rawgit.com/download/polymer-cdn/2.6.0.2/lib/iron-ajax/iron-ajax.html" >
<link rel= "import" href= "https://cdn.rawgit.com/download/polymer-cdn/2.6.0.2/lib/iron-input/iron-input.html" >
<link rel= "import" href= "https://cdn.rawgit.com/download/polymer-cdn/2.6.0.2/lib/polymer/polymer-element.html" >
<dom-module id= "person-app" >
<template>
<link rel= "stylesheet" href= "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity= "sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin= "anonymous" >
<iron-ajax id= "xhr" handle-as= "json" content-type= "application/json" ></iron-ajax>
<div>
<div class= "list-group" >
<template is= "dom-repeat" items= "[[data]]" >
<div class= "list-group-item" >
<a href= "#" on-click= "selectPerson" class= "[[item == selected ? 'active' : '']]" >
<div> First name: <span> [[item.firstName]]</span></div>
<div> Last name: <span> [[item.lastName]]</span></div>
</a>
<button type= "button" class= "btn btn-danger" on-click= "deletePerson" > Delete</button>
</div>
</template>
</div>
<div>
<template is= "dom-if" if= "[[selected]]" >
<h4> Addresses for [[selected.firstName]] [[selected.lastName]]:</h4>
<ul class= "list-group" >
<template is= "dom-repeat" items= "[[selected.addresses]]" as= "address" >
<li class= "list-group-item" >
<ul>
<li> Email: <span> [[address.emailAddress]]</span></li>
<li> City: <span> [[address.city]]</span></li>
<li> Country: <span> [[address.country]]</span></li>
</ul>
</li>
</template>
</ul>
</template>
</div>
</div>
<div>
<h4> Add a Person</h4>
<input type= "text" class= "form-control" id= "fName" placeholder= "First Name" >
<input type= "text" class= "form-control" id= "lName" placeholder= "Last Name" >
<h5> Addresses</h5>
<button class= "btn btn-default" type= "button" on-click= "addAddress" > +</button>
<template is= "dom-repeat" items= "[[addresses]]" as= "address" >
<div class= "input-group" >
<input type= "email" class= "form-control" id= "[[address]]-email" placeholder= "Email" >
<input type= "text" class= "form-control" id= "[[address]]-city" placeholder= "City" >
<input type= "text" class= "form-control" id= "[[address]]-country" placeholder= "Country" >
</div>
</template>
<button type= "submit" class= "btn btn-default" on-click= "addPerson" > Add</button>
</form>
</div>
</template>
<script>
class Person extends Polymer . Element {
static get is () {
return ' person-app ' ;
}
static get properties () {
return {
selected : {
type : Object
},
addresses : {
type : Array ,
value : []
},
data : {
type : Array ,
value : []
}
}
}
ready () {
super . ready ();
this . getData ( this );
}
getData ( self ) {
this . $ . xhr . url = this . ownerDocument . location . origin + ' /person ' ;
this . $ . xhr . method = ' GET ' ;
this . $ . xhr . body = null ;
this . $ . xhr . generateRequest (). completes . then ( function ( request ) { self . data = request . response ;});
}
selectPerson ( event ) {
this . selected = event . model . item ;
}
deletePerson ( event ) {
if ( this . selected && this . selected . personId == event . model . item . personId ) {
selected = null ;
}
var self = this ;
this . $ . xhr . url = this . ownerDocument . location . origin + ' /person/ ' + event . model . item . personId ;
this . $ . xhr . method = ' DELETE ' ;
this . $ . xhr . body = null ;
this . $ . xhr . generateRequest (). completes . then ( function () { self . getData ( self ); });
}
addAddress () {
this . push ( ' addresses ' , ' address ' + this . addresses . length );
}
addPerson () {
var person = { firstName : this . $ . fName . value , lastName : this . $ . lName . value , addresses : []};
var shadowRoot = this . shadowRoot ;
this . addresses . forEach ( function ( id ) {
person . addresses . push (
{
emailAddress : shadowRoot . getElementById ( id + ' -email ' ). value ,
city : shadowRoot . getElementById ( id + ' -city ' ). value ,
country : shadowRoot . getElementById ( id + ' -country ' ). value
});
});
this . addresses = [];
this . $ . fName . value = null ;
this . $ . lName . value = null ;
var self = this ;
this . $ . xhr . url = this . ownerDocument . location . origin + ' /person/ ' ;
this . $ . xhr . method = ' POST ' ;
this . $ . xhr . body = person ;
this . $ . xhr . generateRequest (). completes . then ( function () { self . getData ( self ); });
}
}
customElements . define ( Person . is , Person );
</script>
</dom-module>
And also the ~/microservice/rest-service/src/main/resources/static/css
directory for the following style.css
file
~/microservice/rest-service/src/main/resources/static/css/style.css
style.css
/*
osgi.enroute.examples.component Style Sheet
*/
/* Space out content a bit */
body {
padding-top : 20px ;
padding-bottom : 20px ;
}
/* Everything but the jumbotron gets side spacing for mobile first views */
.header ,
.marketing ,
.footer {
padding-right : 15px ;
padding-left : 15px ;
}
/* Custom page header */
.header {
border-bottom : 1px solid #e5e5e5 ;
}
/* Make the masthead heading the same height as the navigation */
.header h3 {
padding-bottom : 19px ;
margin-top : 0 ;
margin-bottom : 0 ;
line-height : 40px ;
}
/* Custom page footer */
.footer {
padding-top : 19px ;
color : #777 ;
border-top : 1px solid #e5e5e5 ;
}
/* Customize container */
@media ( min-width : 768px ) {
.container {
max-width : 730px ;
}
}
.container-narrow > hr {
margin : 30px 0 ;
}
/* Main marketing message and sign up button */
.jumbotron {
text-align : center ;
border-bottom : 1px solid #e5e5e5 ;
}
.jumbotron .btn {
padding : 14px 24px ;
font-size : 21px ;
}
/* Supporting marketing content */
.marketing {
margin : 40px 0 ;
}
.marketing p + h4 {
margin-top : 28px ;
}
/* Responsive: Portrait tablets and up */
@media screen and ( min-width : 768px ) {
/* Remove the padding we set earlier */
.header ,
.marketing ,
.footer {
padding-right : 0 ;
padding-left : 0 ;
}
/* Space out the masthead */
.header {
margin-bottom : 30px ;
}
/* Remove the bottom border on the jumbotron for visual effect */
.jumbotron {
border-bottom : 0 ;
}
}
Finally, place the following index.html
file in directory ~/microservice/rest-service/src/main/resources/static
~/microservice/rest-service/src/main/resources/static/index.html
index.html
<!DOCTYPE html>
<html lang= "en" >
<head>
<meta charset= "utf-8" >
<meta http-equiv= "X-UA-Compatible" content= "IE=edge" >
<meta name= "viewport" content= "width=device-width, initial-scale=1" >
<title> OSGI enRoute quickstart example</title>
<link rel= "stylesheet" href= "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity= "sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin= "anonymous" >
<link rel= "import" href= "main/html/person.html" >
</head>
<body>
<div class= "container" >
<div class= "row" >
<div class= "col-md-1" >
<img src= "main/img/enroute-logo-64.png" class= "img-responsive" />
</div>
<div class= "col-md-3" >
<h2> OSGi enRoute</h2>
</div>
</div>
<div class= "row center-block" >
<h3 class= "text-muted" > The Microservice Example</h3>
</div>
<div class= "row center-block" >
<person-app></person-app>
</div>
<div class= "row" >
<p> © OSGi Alliance 2017</p>
</div>
</div>
<script src= "https://cdn.rawgit.com/download/polymer-cdn/2.6.0.2/lib/webcomponentsjs/webcomponents-loader.js" ></script>
</body>
</html>
and create the directory ~/microservice/rest-service/src/main/resources/static/main/img
into which save the following icon with the name enroute-logo-64.png
.
Dependencies
As the rest-service
module has dependencies on the dao-api
and json-api
these dependencies are added to the <dependencies>
section in ~/microservice/rest-service/pom.xml
. A JSON-P
implementation dependency is also included so that the rest-service
can be unit tested.
<dependency>
<groupId> org.apache.servicemix.specs</groupId>
<artifactId> org.apache.servicemix.specs.json-api-1.1</artifactId>
<version> 2.9.0</version>
</dependency>
<dependency>
<groupId> org.osgi.enroute.examples.microservice</groupId>
<artifactId> dao-api</artifactId>
<version> 0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId> org.apache.johnzon</groupId>
<artifactId> johnzon-core</artifactId>
<version> 1.1.0</version>
</dependency>
Visibility
Implementation types should NOT be shared; hence we have no package-info.java
file in the REST component.
The Composite Application
We now pull these Modules together to create the Composite Application.
In the microservice
project directory create the application
module using the application Archetype :
~/microservice $ mvn archetype:generate \
-DarchetypeGroupId=org.osgi.enroute.archetype \
-DarchetypeArtifactId=application \
-DarchetypeVersion=7.0.0
with the following values:
Define value for property 'groupId': org.osgi.enroute.examples.microservice
Define value for property 'artifactId': rest-app
Define value for property 'version' 1.0-SNAPSHOT: : 0.0.1-SNAPSHOT
Define value for property 'package' org.osgi.enroute.examples.microservice: :
Define value for property 'impl-artifactId': rest-service
Define value for property 'impl-groupId' org.osgi.enroute.examples.microservice: :
Define value for property 'impl-version' 0.0.1-SNAPSHOT: :
Define value for property 'app-target-java-version' 8: :
Confirm properties configuration:
groupId: org.osgi.enroute.examples.microservice
artifactId: rest-app
version: 0.0.1-SNAPSHOT
package: org.osgi.enroute.examples.microservice
impl-artifactId: rest-service
impl-groupId: org.osgi.enroute.examples.microservice
impl-version: 0.0.1-SNAPSHOT
app-target-java-version: 8
Y: :
Define Runtime Entity
Our Microservice is composed of the following elements:
A REST Service
An implementation of JSON-P (org.apache.johnzon.core)
An in memory database (H2).
These dependencies are expressed as runtime Requirements in the ~/microservice/rest-app/rest-app.bndrun
file:
index: target/index.xml
-standalone: $ { index}
-resolve.effective: active
-runrequires: \
osgi.identity; filter:= '(osgi.identity=org.osgi.enroute.examples.microservice.rest-service)' ,\
osgi.identity; filter:= '(osgi.identity=org.apache.johnzon.core)' ,\
osgi.identity; filter:= '(osgi.identity=org.h2)' ,\
osgi.identity; filter:= '(osgi.identity=org.osgi.enroute.examples.microservice.rest-app)'
-runfw: org.eclipse.osgi
-runee: JavaSE-1.8
Dependencies
By adding the following dependencies inside the <dependencies>
section of the file ~/microservice/rest-app/pom.xml
, we add the necessary Capabilities to the rest-app
’s repository.
<dependency>
<groupId> org.osgi.enroute.examples.microservice</groupId>
<artifactId> dao-impl</artifactId>
<version> 0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId> org.apache.johnzon</groupId>
<artifactId> johnzon-core</artifactId>
<version> 1.1.0</version>
</dependency>
<dependency>
<groupId> com.h2database</groupId>
<artifactId> h2</artifactId>
<version> 1.4.196</version>
<scope> runtime</scope>
</dependency>
Runtime Configuration
Finally, our Microservice will be configured using the new R7 Configurator mechanism.
The application Archetype enables this via ~/microservice/rest-app/src/main/java/config/package-info.java
.
package-info.java
@RequireConfigurator
package config ;
import org.osgi.service.configurator.annotations.RequireConfigurator ;
To pass in the appropriate configuration, overwrite the contents of ~/microservice/rest-app/src/main/resources/OSGI-INF/configurator/configuration.json
with the following:
configuration.json
{
// Global Settings
":configurator:resource-version" : 1 ,
":configurator:symbolic-name" : "org.osgi.enroute.examples.microservice.config" ,
":configurator:version" : "0.0.1.SNAPSHOT" ,
// Configure a JDBC resource provider
"org.apache.aries.tx.control.jdbc.xa~microservice" : {
"name" : "microservice.database" ,
"osgi.jdbc.driver.class" : "org.h2.Driver" ,
"url" : "jdbc:h2:./data/database" },
// Target the Dao impls at the provider we configured
"org.osgi.enroute.examples.microservice.dao.impl.PersonDaoImpl" : {
"provider.target" : "(name=microservice.database)" },
"org.osgi.enroute.examples.microservice.dao.impl.AddressDaoImpl" : {
"provider.target" : "(name=microservice.database)" }
}
Build
Check the modules that make up your application build cleanly from the top level project directory
~/microservice $ mvn -pl !rest-app verify
Note - If you are a linux/unix/*nix user, you might need to escape the exclamation point in the above command.
Now build the rest-app bundle using package command.
~/microservice $ mvn -pl rest-app package
Note - if this build fails then check your code and pom dependencies and try again.
We now generate the required OSGi indexes from the project dependencies, and resolve our application.
~/microservice $ mvn -pl rest-app -am bnd-indexer:index \
bnd-indexer:index@test-index bnd-resolver:resolve
Note Don’t do a clean before running this step, it’s building the indexes and resolution from the bundles you made in the previous step. Also, you don’t need to run this step every time, just if your dependency graph needs to be recalculated.
And finally generate the runnable jar from the top level project directory.
~/microservice $ mvn verify
Re-inspecting ~/microservice/rest-app/rest-app.bndrun
we can see that this now explicitly references the acceptable version range for each required OSGi bundle. At runtime the OSGi framework resolves these requirements against the capabilities in the specified target repository: i.e. target/index.xml
.
index: target/index.xml; name = "rest-app"
-standalone: $ { index}
-resolve.effective: active
-runproperties: \
logback.configurationFile=file:$ { .} /logback.xml
-runrequires: \
osgi.identity; filter:= '(osgi.identity=org.osgi.enroute.examples.microservice.rest-service)' ,\
osgi.identity; filter:= '(osgi.identity=org.osgi.enroute.examples.microservice.rest-app)' ,\
osgi.identity; filter:= '(osgi.identity=org.apache.johnzon.core)' ,\
osgi.identity; filter:= '(osgi.identity=org.h2)'
-runfw: org.eclipse.osgi
-runee: JavaSE-1.8
-runbundles: \
ch.qos.logback.classic; version = '[1.2.3,1.2.4)' ,\
ch.qos.logback.core; version = '[1.2.3,1.2.4)' ,\
org.apache.aries.javax.jax.rs-api; version = '[1.0.4,1.0.5)' ,\
org.apache.felix.http.servlet-api; version = '[1.1.2,1.1.3)' ,\
org.osgi.service.jaxrs; version = '[1.0.0,1.0.1)' ,\
org.apache.felix.converter; version = '[1.0.10,1.0.11)' ,\
org.osgi.util.function; version = '[1.1.0,1.1.1)' ,\
org.osgi.util.promise; version = '[1.1.0,1.1.1)' ,\
slf4j.api; version = '[1.7.25,1.7.26)' ,\
tx-control-provider-jdbc-xa; version = '[1.0.0,1.0.1)' ,\
tx-control-service-xa; version = '[1.0.0,1.0.1)' ,\
org.h2; version = '[1.4.196,1.4.197)' ,\
org.apache.servicemix.specs.json-api-1.1; version = '[2.9.0,2.9.1)' ,\
org.apache.johnzon.core; version = '[1.1.0,1.1.1)' ,\
org.apache.geronimo.specs.geronimo-annotation_1.3_spec; version = '[1.1.0,1.1.1)' ,\
org.apache.aries.jax.rs.whiteboard; version = '[1.0.6,1.0.7)' ,\
org.apache.felix.configadmin; version = '[1.9.16,1.9.17)' ,\
org.apache.felix.configurator; version = '[1.0.10,1.0.11)' ,\
org.apache.felix.http.jetty; version = '[4.0.14,4.0.15)' ,\
org.apache.felix.scr; version = '[2.1.16,2.1.17)' ,\
org.osgi.enroute.examples.microservice.dao-api; version = '[0.0.2,0.0.3)' ,\
org.osgi.enroute.examples.microservice.dao-impl; version = '[0.0.2,0.0.3)' ,\
org.osgi.enroute.examples.microservice.rest-app; version = '[0.0.2,0.0.3)' ,\
org.osgi.enroute.examples.microservice.rest-service; version = '[0.0.2,0.0.3)'
Run
To dynamically assemble and run the resultant REST Microservice simply change back to the top level project directory and type the command:
~/microservice $ java -jar rest-app/target/rest-app.jar
The REST service can be seen by pointing a browser to http://localhost:8080/microservice/index.html
Stop the application using Ctrl+C in the console.
Finally, if we create and run the debug version of the Microservice we can see all of the OSGi bundles used in the actual runtime assembly.
g! lb
START LEVEL 1
ID|State |Level|Name
0|Active | 0|System Bundle (5.7.0.SNAPSHOT)|5.7.0.SNAPSHOT
1|Active | 1|Logback Classic Module (1.2.3)|1.2.3
2|Active | 1|Logback Core Module (1.2.3)|1.2.3
3|Active | 1|Apache Aries Javax Annotation API (0.0.1.201711291743)|0.0.1.201711291743
4|Active | 1|Apache Aries JAX-RS Specification API (0.0.1.201803231639)|0.0.1.201803231639
5|Active | 1|Apache Aries JAX-RS Whiteboard (0.0.1.201803231640)|0.0.1.201803231640
6|Active | 1|Apache Commons FileUpload (1.3.2)|1.3.2
7|Active | 1|Apache Commons IO (2.5.0)|2.5.0
8|Active | 1|Apache Felix Configuration Admin Service (1.9.0.SNAPSHOT)|1.9.0.SNAPSHOT
9|Active | 1|Apache Felix Configurer Service (0.0.1.SNAPSHOT)|0.0.1.SNAPSHOT
10|Active | 1|Apache Felix Gogo Command (1.0.2)|1.0.2
11|Active | 1|Apache Felix Gogo Runtime (1.0.10)|1.0.10
12|Active | 1|Apache Felix Gogo Shell (1.0.0)|1.0.0
13|Active | 1|Apache Felix Http Jetty (3.4.7.R7-SNAPSHOT)|3.4.7.R7-SNAPSHOT
14|Active | 1|Apache Felix Servlet API (1.1.2)|1.1.2
15|Active | 1|Apache Felix Inventory (1.0.4)|1.0.4
16|Active | 1|Apache Felix Declarative Services (2.1.0.SNAPSHOT)|2.1.0.SNAPSHOT
17|Active | 1|Apache Felix Web Management Console (4.3.4)|4.3.4
18|Active | 1|Apache Felix Web Console Service Component Runtime/Declarative Services Plugin (2.0.8)|2.0.8
19|Active | 1|Johnzon :: Core (1.1.0)|1.1.0
20|Active | 1|Apache ServiceMix :: Specs :: JSon API 1.1 (2.9.0)|2.9.0
21|Active | 1|H2 Database Engine (1.4.196)|1.4.196
22|Active | 1|dao-api (0.0.1.201803251221)|0.0.1.201803251221
23|Active | 1|dao-impl (0.0.1.201803251221)|0.0.1.201803251221
24|Active | 1|rest-app (0.0.1.201803251650)|0.0.1.201803251650
25|Active | 1|rest-service (0.0.1.201803251221)|0.0.1.201803251221
26|Active | 1|org.osgi:org.osgi.service.jaxrs (1.0.0.201803131808-SNAPSHOT)|1.0.0.201803131808-SNAPSHOT
27|Active | 1|org.osgi:org.osgi.util.converter (1.0.0.201803131810-SNAPSHOT)|1.0.0.201803131810-SNAPSHOT
28|Active | 1|org.osgi:org.osgi.util.function (1.1.0.201803131808-SNAPSHOT)|1.1.0.201803131808-SNAPSHOT
29|Active | 1|org.osgi:org.osgi.util.promise (1.1.0.201803131808-SNAPSHOT)|1.1.0.201803131808-SNAPSHOT
30|Active | 1|osgi.cmpn (4.3.1.201210102024)|4.3.1.201210102024
31|Active | 1|slf4j-api (1.7.25)|1.7.25
32|Active | 1|OSGi Transaction Control JDBC Resource Provider - XA Transactions (1.0.0.201801251821)|1.0.0.201801251821
33|Active | 1|Apache Aries OSGi Transaction Control Service - XA Transactions (1.0.0.201801251821)|1.0.0.201801251821