Mostrando entradas con la etiqueta Jakarta EE. Mostrar todas las entradas
Mostrando entradas con la etiqueta Jakarta EE. Mostrar todas las entradas

jueves, 21 de octubre de 2021

Desarrollo y despliegue de un servicio en Weblogic 14c

Hoy vamos a ver como crear un web service (tanto SOAP como REST), que debe desplegarse en un contenedor de aplicaciones empresariales. Concretamente en Weblogic. En apariencia es algo sencillo, pero también es verdad que al ser un servidor propiedad de Oracle y no Open Source, a veces es un poco difícil encontrar información. Así que esperamos aclarar un poco su funcionamiento básico a través de este post. 

Debemos recordar que este tipo de servidores vienen con un conjunto de librerías que implementan las especificaciones de Java y Jakarta EE. Esta es una de las razones por los que son más pesados, que un simple contenedor de servlet. Aparte de que incluyen bastante más funcionalidades asociadas a dichas especificaciones. 

Por tanto a la hora de crear una aplicación que vayamos a desplegar en Weblogic o otro tipo de contenedor de aplicaciones empresariales, debemos indicar cuales son las API que implementa. Con el beneficio de que además no es necesario añadir dichas implementaciones, puesto que ya se encuentran en el servidor. Debido a eso, simplemente deberemos indicar en el fichero pom.xml cuales son dichas interfaces y que todas tienen de ámbito provided, puesto que se encuentran en el servidor. 

<!-- weblogic dependencies -->
<dependency>    <!-- Java EE -->
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>8.0</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Java EE EJB -->
    <groupId>javax.ejb</groupId>
    <artifactId>javax.ejb-api</artifactId>
    <version>3.2.2</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Java EE Servlet -->
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.0</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Java EE JSP -->
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.0</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Java EE JSF -->
    <groupId>javax.faces</groupId>
    <artifactId>javax.faces-api</artifactId>
    <version>2.3</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Java EE Common Annotationss -->
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Java EE Bean Validation -->
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.0.Final</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Java API for JSON Processing -->
    <groupId>javax.json</groupId>
    <artifactId>javax.json-api</artifactId>
    <version>1.1</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Java API for JSON Binding -->
    <groupId>javax.json.bind</groupId>
    <artifactId>javax.json.bind-api</artifactId>
    <version>1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Java API for XML-Based Web Services -->
    <groupId>javax.xml.ws</groupId>
    <artifactId>jaxws-api</artifactId>
    <version>2.3.0</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Java API for RESTful Web Services (JAX-RS) -->
    <groupId>javax.ws.rs</groupId>
    <artifactId>javax.ws.rs-api</artifactId>
    <version>2.1</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Contexts and Dependency Injection for Java EE -->
    <groupId>javax.enterprise</groupId>
    <artifactId>cdi-api</artifactId>
    <version>2.0</version>
    <scope>provided</scope>
</dependency>
<dependency>    <!-- Expression Language (EL) -->
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.0</version>
</dependency>

Para la creación de los web services en verdad no necesitamos tantas librerías, pero este listado nos puede servir de referencia. Para el servicio SOAP requeriremos de la librería jaxws-api y para el servicio REST de la librería javax.ws.rs-api. 

La idea principal es que ambos servicios sean muy básicos y funcionen prácticamente igual. Con la única diferencia de las anotaciones que permitirán que un servicio se genere como SOAP o REST. 

Empezaremos primero por el más antiguo, el servicio SOAP. La clase que implemente nuestro servicio debe tener la anotación @WebService. A la cual le podemos indicar el nombre de dicho servicio. Y cada uno de los métodos que queramos exponer deberán llevar la anotación @WebMethod, a los cuales también le podemos indicar los nombres asociados. Ejemplo:

@WebService(serviceName = "BookSoapService")
public class BookServiceImpl implements BookService {

    private static Map<Integer, Book> books = new HashMap<>();

    static {
	books.put(1, new Book(1, "Dune", "Frank Herbert"));
	books.put(2, new Book(2, "The stars my destination", "Alfred Bester"));
	books.put(3, new Book(3, "Ender's game", "Orson S. Card"));
    }

    @Override
    @WebMethod(operationName = "getById")
    public Book getById(final Integer id) {
	return books.get(id);
    }
}

Una vez desplegado el servicio, podremos acceder a su WSDL, generado automáticamente, a través de la siguiente URL: http://localhost:7001/weblogicWS/BookSoapService?wsdl.

Ahora veremos como hacer el servicio REST. Para ello deberemos añadir a nivel de clase la anotación @Path e indicar la ruta principal de acceso a dicho servicio. Este paso no es del todo necesario, puesto que podemos poner directamente dicha anotación a cada método, pero poniéndolo a nivel de clase evitamos repetir parte de dicha ruta. Luego en cada método, y en función de la operación que vaya a realizar, deberemos indicar la anotación asociada al método HTTP a utilizar. Puede ser @DELETE, @GET, etc. Hay otras anotaciones que nos pueden ayudar a obtener la información que viaja en la llamada. Como pueden ser @CookieParam, @HeaderParam, @PathParam o @QueryParam. 

@Path("/book")
public class BookRestServiceImpl implements BookService {

    private static Map<Integer, Book> books = new HashMap<>();

    static {
	books.put(1, new Book(1, "Dune", "Frank Herbert"));
	books.put(2, new Book(2, "The stars my destination", "Alfred Bester"));
	books.put(3, new Book(3, "Ender's game", "Orson S. Card"));
    }

    @Override
    @GET
    @Path("/{id}")
    @Produces("application/json")
    public Book getById(@PathParam("id") final Integer id) {
	return books.get(id);
    }
}

Una vez desplegado el servicio, podremos invocarlo a través de la siguiente URL: http://localhost:7001/weblogicWS/resources/book/1.

Y una vez que tenemos nuestros web services, vamos a desplegarlos. Para ello lo primero será poner en marcha el servidor. Esto podemos hacerlo descargándonoslo e instalándolo en nuestra máquina, a través de este enlace. O como es habitual hoy en día y para pruebas en desarrollo, levantarlo como una instancia de Docker. Este último será el método que veremos. 

Pero para poder utilizar una instancia de docker del servidor necesitaremos realizar los siguientes pasos:

  • Crear una cuenta en en Oracle.com Network
  • Acceder a la página de Oracle Container Registry, lógate con la cuenta creada en el paso anterior y acepta la licencia de usuario. Si no ves dicha licencia directamente, accede a una de las imágenes y aparecerá en la parte superior derecha. 
  • Logate en el Hub de Oracle a través del comando docker login container-registry.oracle.com.
  • Descarga la imagen deseada a través del comando docker pull container-registry.oracle.com/middleware/weblogic:${TAG}. Puedes ver los distintos tags aquí. 
Ahora que tenemos la imagen descargada necesitamos montar un volumen que nos permita indicar donde se encuentra nuestro fichero domain.properties. Este fichero de configuración nos permitirá principalmente el usuario y contraseña. A través de un comando similar a este:

docker run -p 7001:7001 -p 9002:9002 --name wl14c \
-v ${PATH_TO_DOMAIN_PROPERTIES}:/u01/oracle/properties \
container-registry.oracle.com/middleware/weblogic:14.1.1.0-dev-8

Una vez arrancado, podemos acceder a la consola de administración a través de la URL: https://localhost:9002/console/ y acceder a ella con el usuario y contraseña indicado en el fichero de configuración. 

Ya solo quedaría desplegar el servicio. Lo podemos hacer de varias formas, incluso a través de una API REST. Pero las más sencillas pasan por copiar el war o ear a una carpeta del servidor dentro de la instancia de Docker. Lo cual podemos realizar con un comando similar a este:

docker cp weblogicWS.war wl14c:/tmp/weblogicWS.war

 Como comentábamos anteriormente, lo podemos hacer de varias formas:
  1. Si el fichero lo pegamos en la carpeta /u01/oracle/user_projects/domains/base_domain/autodeploy el fichero se desplegará automáticamente. 
  2. Si lo pegamos en otra carpeta a través de la consola de Administración y la opción Despliegues tras pulsar el botón Instalar. Podremos seleccionar la ruta y fichero que hemos copiado anteriormente e instalarlo tras seleccionar unas opciones de despliegues. 
  3. También podemos desplegarlo a través de la utilidad weblogic.Deployer. Tener en cuenta que esta opción no esta disponible en la versión para desarrollo.  

docker exec -it wl14c sh
$ cd /u01/wlsdomain/base_domain/bin
$ . setDomainEnv.sh
$ java weblogic.Deployer -adminurl t3://localhost:7001 -user user1 -password welcome1 \
-name weblogicWS -targets base_domain -deploy /tmp/weblogicWS.war

Por último, vamos a indicar como debemos configurar el repositorio de Oracle si deseamos utilizarlo. Para ello primero debemos tener un usuario de acceso a Oracle.com Network, puede ser el mismo que utilizamos para descargas las imágenes del hub de Oracle. 

Ese usuario debemos configurarlo asociado al servidor en el fichero settings.xml que se encuentra en la ruta: ~/.m2/settings.xml. Tal y como mostramos a continuación:

<!-- https://docs.oracle.com/middleware/1213/core/MAVEN/config_maven_repo.htm#MAVEN9017 -->
<server>
    <id>maven.oracle.com</id>
    <username>user@mail.com</username>
    <password>{Z8Gn4wUfjN0Hsiu44T+FwfAjpRYY62ycDxKU9V7u/ps=}</password>   
        <configuration>
        <basicAuthScope>
            <host>ANY</host>
            <port>ANY</port>
            <realm>OAM 11g</realm>
        </basicAuthScope>
        <httpConfiguration>
            <all>
                <params>
                    <property>
                        <name>http.protocol.allow-circular-redirects</name>
                        <value>%b,true</value>
                    </property>
                </params>
            </all>
        </httpConfiguration>
    </configuration>
</server>

Y luego deberemos añadir los repositorios de Oracle. Estos podemos indicarlos también de forma global en el fichero settings.xml o de forma específica en cada uno de los pom.xml de los proyectos que vayan a usarlo. 

<repositories>
	<repository>
		<id>maven.oracle.com</id>
		<releases>
			<enabled>true</enabled>
		</releases>
		<snapshots>
			<enabled>false</enabled>
		</snapshots>
		<url>https://maven.oracle.com</url>
		<layout>default</layout>
	</repository>
</repositories>
<pluginRepositories>
	<pluginRepository>
		<id>maven.oracle.com</id>
		<url>https://maven.oracle.com</url>
	</pluginRepository>
</pluginRepositories>

Y esto es todo. Aunque no hemos hecho gran cosa, hemos tenido que autenticarnos en Oracle, y configurar varios aspectos para el correcto desarrollo y despliegue de nuestra aplicación, además hemos recopilado como realizar un proceso completo para hacerlo en un servidor Weblogic. Si queréis ver el código de la aplicación, lo tenéis aquí. 

domingo, 22 de agosto de 2021

Tests unitarios y de integración para JPA DAO

Hoy vamos a ver principalmente como hacer pruebas unitarias y de integración para la capa de acceso a datos, DAO, en una aplicación de JPA. Pero también veremos otros aspectos interesantes sobre pruebas que impliquen BBDD, como  puede ser el uso de librerías para la creación de un contexto JNDI hasta incluso BBDD para pruebas. 

Para empezar debemos aclarar un poco que son los test unitarios y que son los test de integración, con la ayuda de estas versiones resumidas de su definición: 

  • Test Unitario: Es aquel destinado a probar un único componente del código. El cual se puede ejecutar sin necesidad de otras dependencias.
  • Test de Integración: Es aquel destinado a probar una funcionalidad compleja del código. Implicará  la iteración entre distintos componentes e inclusos con sistemas externos al código. 

Otro de los puntos a tener en cuenta, es que los test serán sobre tecnología de Jakarta EE y no con Spring. Por lo que serán un poco más elaborados. Además cuando queramos realizar los tests de integración diferenciaremos entre si queremos utilizar una conexión JDBC o acceder a la BBDD a través de JNDI. Esta última solución será aplicable a otros tipos de tests que necesiten del acceso a través de JNDI, de hay que hagamos un ejemplo diferente para probar lo mismo.

Empezaremos creando la clase Entity que será un reflejo de una tabla que tenemos en la BBDD. 

@Data
@Entity
@Table(name = "BOOK")
@NamedQuery(name = "Book.findAll", query = "SELECT b FROM Book b")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
    private String author;
}

El siguiente paso será crear nuestra clase de acceso a datos. Esta parte la realizaremos en dos pasos. Primero crearemos una clase abstracta y genérica que contenga los métodos comunes. Esta clase implementará una interfaz que es la que declarará los métodos que tiene. 

public abstract class GenericDaoImpl<K, E> implements GenericDao<K, E> {

    private Class<E> persistentClass;

    @SuppressWarnings("unchecked")
    public GenericDaoImpl() {
	Type genericSuperClass = getClass().getGenericSuperclass();

	ParameterizedType parametrizedType = null;
	while (parametrizedType == null) {
	    if (genericSuperClass instanceof ParameterizedType) {
		parametrizedType = (ParameterizedType) genericSuperClass;
	    } else {
		genericSuperClass = ((Class<?>) genericSuperClass).getGenericSuperclass();
	    }
	}
	this.persistentClass = (Class<E>) parametrizedType.getActualTypeArguments()[1];
    }
    @Override
    public List<E> findAll() {
	return getEntityManager().createNamedQuery(
			String.format("%s.findAll", persistentClass.getSimpleName()), 
		persistentClass).getResultList();
    }
    @Override
    public E getByField(final String field, final String value) {
	return getEntityManager()
.createQuery(
			String.format("SELECT t FROM %s t where t.%s = :value", 
			persistentClass.getSimpleName(), field), persistentClass)
		.setParameter("value", value).getResultStream().findFirst().orElse(null);
    }
    @Override
    public E getById(final K key) {
	return getEntityManager().find(persistentClass, key);
    }
}

A continuación crearemos la clase de acceso a datos asociada a nuestra entidad. La cual extenderá la clase anterior y además se le incluirá la dependencia de la clase EntityManager, la cual nos permitirá acceder propiamente a los datos. 

@Stateless
public class BookDaoImpl extends GenericDaoImpl<Integer, Book> {
    @PersistenceContext(unitName = "persistence-unit")
    protected EntityManager em;

    @Override
    public EntityManager getEntityManager() {
	return em;
    }
}

Esta clase será identificada como un componente sin estado a ser gestionado por el servidor a través de la anotación @Stateless, la cual también nos permitirá inyectarla en otras clases. Aparte le será inyectada la clase EntityManager, cuyo comportamiento es definido en el fichero META-INF/persistence.xml y que nos dará el acceso a la BBDD y la gestión de transacciones. 

Empecemos ya con los ejemplos. Primero irá el test unitario, donde no necesitaremos ninguna BBDD o proveedor de persistencia. Verificaremos el funcionamiento de los métodos a través de mocks. Por tanto, estas serán las únicas librerías que necesitemos: 

  • La especificación de Java EE: javax.javaee-api.
  • Librerías propias de Testing: Junit para los tests, Hamcrest para las verificaciones y Mockito para la simulación de objetos. 
@ExtendWith(MockitoExtension.class)
public class GenericDaoImplUnitTest {
    BookDaoImpl dao;
    @Mock
    EntityManager eManager;
    @Mock
    TypedQuery<Book> mockQuery;

    private List<Book> _getBooks() {
	List<Book> lst = new ArrayList<Book>();
	lst.add(new Book());
	return lst;
    }
    @Test
    public void findAll() {
	when(dao.em.createNamedQuery(ArgumentMatchers.anyString(), ArgumentMatchers.eq(Book.class)))
		.thenReturn(mockQuery);
	when(mockQuery.getResultList()).thenReturn(_getBooks());

	List<Book> lst = dao.findAll();
	assertEquals(1, lst.size());
	verify(dao.em).createNamedQuery("Book.findAll", Book.class);
    }
    @BeforeEach
    public void setUp() {
	dao = new BookDaoImpl();
	dao.em = eManager;
    }
}

Estos métodos verificarán el correcto funcionamiento. Pero no podremos testear el funcionamiento del propio EntityManager, su configuración, o en caso de consultas más complejas si están correctamente desarrolladas. Por todo ello realizaremos a continuación un ejemplo de test de integración, en cual nos conectaremos a una BBDD, probando de esta forma la configuración y funcionamiento real de las consultas. 

Para poder llevar a cabo este tipo de pruebas, necesitaremos las siguientes librerías:

  • Un proveedor de persistencia, es decir la implementación de la API de JPA. En este caso utilizaremos org.eclipse.persistence.eclipselink.
  • Una base de datos. Para nuestro ejemplo usaremos com.h2database.h2.
El punto a favor de utilizar una BBDD, que podemos almacenar en memoria, como H2, es que podremos crear un test hibrido entre prueba unitaria y prueba de integración. Esto es debido a que evita que tengamos conexión con un sistema real, lo cual a veces puede ser problemático por que no exista entorno de pruebas o conexión directa. Además, los tests mantienen su aislamiento y al utilizar un sistema externo sin estado, verificando así el mismo comportamiento en cada ejecución.

En este caso, al arrancar la BBDD queremos que sea poblada con determinada información que nos permita realizar los tests. Para ello le indicaremos en la URL JDBC que ejecute el fichero schema-generator.sql. Por lo demás la configuración es bastante estándar.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="persistence-unit-test" transaction-type="RESOURCE_LOCAL">
        <class>com.home.example.dbjunittest.entity.Book</class>
        <properties>
            <property name="javax.persistence.jdbc.url" 
           	value="jdbc:h2:mem:test:sample;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:schema-generator.sql';" />
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
            <property name="javax.persistence.schema-generation.database.action" value="none" />
            
            <property name="eclipselink.logging.level.sql" value="FINE"/>
            <property name="eclipselink.logging.parameters" value="true"/>
</properties> </persistence-unit> </persistence>

Y a continuación elaboraremos nuestro test de integración que hará uso de esta configuración.

public class BookDaoImplITTest {
    private static BookDaoImpl dao;

    @BeforeAll
    public static void before() {
	dao = new BookDaoImpl();
	EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistence-unit-test");
	dao.em = emf.createEntityManager();
    }
    @Test
    public void findAll() {
	List<Book> lst = dao.findAll();
	assertThat(lst.size(), equalTo(1));
    }
}

Ahora veremos una modificación de este último ejemplo, utilizando en vez de una conexión JDBC, una conexión a través de JNDI. Pero para ello tendremos que añadir una nueva librería: com.github.h-thurow.simple-jndi. Esta librería nos permitirá poblar al objeto InitialContext con un Datasource que nosotros indiquemos, para que así pueda ser utilizado por el Entity Manager. 

El primer paso en este nuevo ejemplo es modificar el fichero persistence.xml, para indicar que dejamos de utilizar una conexión JDBC y queremos usar una JNDI. 

<persistence-unit name="persistence-unit-test" transaction-type="RESOURCE_LOCAL">
	<non-jta-data-source>java:comp/env/jdbc/myds</non-jta-data-source>
    <class>com.home.example.jpajndiinttest.entity.Book</class>
    <properties>
        <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
        <property name="javax.persistence.schema-generation.database.action" value="none" />
        
        <property name="eclipselink.logging.level.sql" value="FINE"/>
		<property name="eclipselink.logging.parameters" value="true"/>
    </properties>
</persistence-unit>

Para que la carga del Datasource en el contexto funcione correctamente debemos configurar dos ficheros. Uno será el encargado de configurar la librería que hemos indicado a través de un fichero denominado jndi.properties. En el aparte de configuración propia de la librería, indicaremos los caracteres de separación para la nomenclatura, el espacio de almacenaje de dicha información, y donde se ubicarán los ficheros para los distintos objetos a crear.  

java.naming.factory.initial=org.osjava.sj.SimpleContextFactory
org.osjava.sj.jndi.shared=true
org.osjava.sj.delimiter=.
jndi.syntax.separator=/
org.osjava.sj.space=java:comp/env
org.osjava.sj.root=src/test/resources/jndi

El segundo fichero, será el que configure el Datasource y en base a lo indicado anteriormente debe cumplir las siguientes reglas y contenido:

  • Debe estar almacenado en la carpeta src/test/resources/jndi
  • Debe llamarse jdbc.properties
  • Cada una de las propiedades del Datasource deben tener el sufijo myds. 

myds.type=javax.sql.DataSource
myds.driver=org.h2.Driver
myds.url=jdbc:h2:mem:test:sample;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:schema-generator.sql';
myds.user=sa
myds.password=password

Ya solo nos quedará realizar el test de la siguiente forma:

public class BookDaoImplIT {

    static BookDaoImpl dao;
    static InitialContext initContext;

    @BeforeAll
    public static void setup() throws Exception {
	initContext = new InitialContext();
	dao = new BookDaoImpl();
	EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistence-unit-test");
	dao.em = emf.createEntityManager();
    }
    @Test
    public void findAll() throws InterruptedException {
	List<Book> lst = dao.findAll();
	assertThat(lst.size(), equalTo(1));
    }
}

Debemos tener en cuenta que ambos ejemplos no gestionan las transacciones al ser de tipo local. Por lo que si queremos hacer ejemplos más complejos, deberemos gestionar las transacciones manualmente. Esto es debido a que si utilizamos conexiones JTA, la clase EntityManagerFactory no funcionará. 

Indicar también que si queremos ejecutar los este tipo en una fase posterior o de forma independiente, también lo podemos hacer. Para ello tendremos que utilizar maven-failsafe-plugin. Y deberemos seguir las siguientes instrucciones:

  • El test debe seguir el siguiente patrón: "**/IT*.java, **/*IT.java o **/*ITCase.java
  • Debemos asociar su ejecución a una de las fases del ciclo de vida de Maven.

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-failsafe-plugin</artifactId>
	<version>2.22.2</version>
	<executions>
		<execution>
			<goals>
				<goal>integration-test</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Como veis no solo hemos aprendido a hacer tests a la capa de acceso a datos sino que por el camino hemos aprendido un par detalles interesantes. Además, en el enlace al código fuente, podréis ver un test de cada uno los métodos para los distintos ejemplos, aquí. 

miércoles, 13 de mayo de 2020

Apache HttpClient: Configuración

Hoy vamos a ver un breve ejemplo de como configurar nuestro cliente de Apache HttpClient, básicamente veremos como crear correctamente el cliente para evitar diferentes problemas de conexión. 
  • Ignorar certificado SSL
Es normal cuando hagamos clientes de servicios web que nos conectemos con servidores que aunque tengan certificados SSL estos no esten firmados por una entidad certificadora de confianza. Para ello podemos usar TrustSelfSignedStrategy.
  • Ignorar verificación de Hostname
También es posible que el certificado no este correctamente generado y no se encuentre el hostname del servidor dentro de su propio certificado. Para ello podemos usar  SSLConnectionSocketFactory ALLOW_ALL_HOSTNAME_VERIFIER.
  • Redirección
En otros servicios es posible que cuando los vayamos a invocar, nos redirigan hacia otro servidor. Normalmente podemos identificarlos a través del código HTTP de estado 302. Para ello podemos usar LaxRedirectStrategy.

Si hablamos de Apache HttpClient debemos conocer que hay un antes y despues de la versión 4.3 y que en función de la misma, dicho cliente lo crearemos de una forma u otra.
  • <= 4.3
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build(),
                SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
CloseableHttpClient client = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .setRedirectStrategy(new LaxRedirectStrategy())
                .build();
  • > 4.3
TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
SSLContext sslContext = SSLContextBuilder.create()
        .loadTrustMaterial(acceptingTrustStrategy).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
        org.apache.http.conn.ssl.NoopHostnameVerifier.INSTANCE);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
        .<ConnectionSocketFactory> create().register("https", sslsf)
        .register("http", new PlainConnectionSocketFactory()).build();
BasicHttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(
socketFactoryRegistry);
CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslsf)
        .setConnectionManager(connectionManager)
        .setRedirectStrategy(new LaxRedirectStrategy()).build();

jueves, 18 de octubre de 2018

Como crear e inicializar con datos una BBDD de H2

Estaba haciendo un ejemplo de JPA, Hibernate y H2. Y estaba intentando que al arrancar el ejemplo se crease la BBDD en base a las entidades JPA y a la misma vez ejecutase un script para añadir datos a la misma. 

Mi configuración inicial era la siguiente: 

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
 version="2.0">
 <persistence-unit name="es.home.example.jpa.config">
  <class>es.home.example.pojo.Book</class>
  <properties>
   <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
   <property name="hibernate.show_sql" value="true" />
   <property name="hibernate.hbm2ddl.auto" value="create" />
   <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
   <property name="javax.persistence.jdbc.url"
    value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;
    INIT=create schema if not exists test\;
    RUNSCRIPT FROM 'classpath:script/data.sql'\;" />
   <property name="javax.persistence.jdbc.user" value="sa" />
   <property name="javax.persistence.jdbc.password" value="" />
  </properties>
 </persistence-unit>
</persistence>

Estaba usando la propiedad 'hibernate.hbm2ddl.auto' a 'true', por lo que me crearía la BBDD al iniciarla en base a las entidades creadas (en este caso la entidad 'Book'). Y además en la cadena de conexión de H2 habiamos indicado que al iniciar se crease el esquema 'test' si no existiese y que se ejecutase un script (data.sql) almacenado en la carpeta 'script' del classpath (src/main/resources).

Pero al arrancar la aplicación, me daba error debido a que estaba intentando ejecutar el script 'data.sql' (con inserts en la tabla 'Book') antes de crear la propia tabla.

La solución: Pasa por hacer ambas cosas a través de hibernate. Es decir, que sea la propia Hibernate la que se encargue de crear la BBDD (cosa que ya tenemos configurada) y la encargada de ejecutar un fichero que nos permita iniciar con datos dicha BBDD. ¿Pero como?.

Podemos hacerlo de dos formas, pero en ambas partimos de que debemos quitar la parte de la configuración de H2 para ejecutar el script.
  • La primera es creando un fichero 'import.sql' en el classpath y almacenando en el todas las sentencias SQL que queremos que se ejecuten.
  • Indicando los scripts y el orden de los mismos que queremos que se ejecuten mediante otra propiedad de hibernate 'hibernate.hbm2ddl.import_files'. Antentos que esta solo se puede usar si la propiedad 'hibernate.hbm2ddl.auto' esta puesta a 'create' o 'create-drop'
He aquí la solución:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
 version="2.0">
 <persistence-unit name="es.home.example.jpa.config">
  <class>es.home.example.pojo.Book</class>
  <properties>
   <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
   <property name="hibernate.show_sql" value="true" />
   <property name="hibernate.hbm2ddl.auto" value="create" />
   <property name="hibernate.hbm2ddl.import_files" value="script/data.sql" />   
   <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
   <property name="javax.persistence.jdbc.url"
    value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;" />
   <property name="javax.persistence.jdbc.user" value="sa" />
   <property name="javax.persistence.jdbc.password" value="" />
  </properties>
 </persistence-unit>
</persistence>

lunes, 3 de septiembre de 2018

Java 8 : las interfaces funcionales, lambda y su generación

Ya hicimos post sobre Java 8 y los steam o las interfaces funcionales. Ahora vamos a intentar hacer uno sobre lambda y su asociación con las intefaces funcioanles.

Cuando hablamos de Lambda, hablamos de expresiones lambda, generadas a través del operador '->'. Estas expresiones lambda a fin de cuenta nos permiten crear funciones anónimas. Y estas funciones a la vez, generan o instancian lo que ya conocemos como interfaces funcionales.

En base a la expresión lambda, generaremos uno u otro tipo de interfaz funcional, pudiendo ser interfaces de características diferentes. Hasta tal punto que en Java 8 se han creado distintos tipo de interfaces funcionales, las cuales puedes ver aquí y vamos a explicar una a una.
  • Consumer
Interfaz funcional que admiten un o dos parámetros y de diferentes tipos primitivos. Estas interfaces devuelven un valor en basé a la implementación interna.

Consumer<String> lambdaObject = (x) -> System.out.println(x.toLowerCase());
lambdaObject.accept("LAMBDA");
//produces : lambda
BiConsumer<String, String> lambdaBiConsumer = (x,y) -> System.out.println(x.toLowerCase() + " " + y.toUpperCase());
lambdaBiConsumer.accept("LAMBDA","lambda");
//produces : lambda, LAMBDA
DoubleConsumer lambdaDoubleConsumer = (x) -> System.out.println(Double.toString(x));
lambdaDoubleConsumer.accept(Double.MIN_VALUE);
//produces : 4.9E-324
  • Functions
Admiten un valor y el retorno no tien porque ser del mismo tipo.

Function<Double, String> lambdaFunction = (d) -> Double.toString(d);
System.out.println(lambdaFunction.apply(1.2));
//produces : 1.2
  • Provider
Aceptan un parámetro y devuelven un valor lógico

Predicate<String> lambdaPredicate = (s) -> "Lambda".equals(s);
System.out.println(lambdaPredicate.test("Lambda"));
// produces : true
  • Supplier
Interfaces que devuelven un valor pero no admiten parámetros

Supplier<String> lambdaSupplier = () ->  Double.toString(1.1);
System.out.println(lambdaSupplier.get());
//produces : 1.1

Con todo esto ya hemos visto un ejemplo básico de que es lambda, para que se usa y que produce. Más adelante haremos otro post con ejemplos más prácticos que le saquen el parte a las expresiones lambda.