Spring Boot. Personalizar el json de error 404 y Swagger

En el proyecto en el que estoy trabajando ahora tenemos una API de servicios Rest hecha con Spring Boot y documentada usando Swagger. Existe una clase anotada con @RestControllerAdvice para personalizar la respuesta de error. He observado que cuando sucede un error 404 no se está ejecutando el método correspondiente por lo que, el JSON de error retornado, tiene el formato por defecto de Spring. Para que esto no suceda.

En el fichero application.properties

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

Implementar @RestControllerAdvice

@RestControllerAdvice
public class MyControllerAdvice {

    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<?> handleControllerException(HttpServletRequest request,
                                                       NoHandlerFoundException ex) {
        LOGGER.debug("handleControllerException. message: {}", ex.getMessage());
        // Return custom response entity
    }
}

Con esto ya responde con el formato de JSON que esperábamos.

Hacer funcionar Swagger UI

Al añadir la propiedad spring.resources.add-mappings esta solución vemos que deja de funciona Swagger UI. Da este error:

org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation

El problema es que el autoconfigurador mvc de spring mapea recursos por defecto que son los que utiliza Swagger para mostrar su página web.

Finalmente copio el código del autoconfigurador pero hago que en lugar de aplicar a todas las url "/**" aplique sólo a "/swagger-ui.hml" tal y como se puede ver en el siguiente código:

@Configuration
@EnableWebMvc
public class MyWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");

        registry.addResourceHandler("/swagger-ui.html")
                .addResourceLocations(getStaticLocations());
    }

    private String[] getStaticLocations() {
        return new String[]{
                "/",
                "classpath:/META-INF/resources/",
                "classpath:/resources/",
                "classpath:/static/",
                "classpath:/public/"
        };
    }
}

Fuente

Error al arrancar docker: Failed with result ‘protocol’

Ejecutamos systemctl status docker.service para mostrar la información del estado del servicio.

docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: failed (Result: protocol) since Fri 2017-12-29 10:05:40 CET; 2min 14s ago
     Docs: https://docs.docker.com
  Process: 14862 ExecStart=/usr/bin/dockerd -H fd:// (code=exited, status=1/FAILURE)
 Main PID: 14862 (code=exited, status=1/FAILURE)
      CPU: 43ms

dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Unit entered failed state.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Failed with result 'protocol'.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Service hold-off time over, scheduling restart.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: Stopped Docker Application Container Engine.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Start request repeated too quickly.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: Failed to start Docker Application Container Engine.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Unit entered failed state.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Failed with result 'protocol'.

A continuación Ejecutamos journalctl -xe para ver los logs del sistema.

dic 29 10:05:40 Dell-XPS-8900 systemd[1]: Starting Docker Application Container Engine...
-- Subject: Unit docker.service has begun start-up
-- Defined-By: systemd
-- Support: http://www.ubuntu.com/support
-- 
-- Unit docker.service has begun starting up.
dic 29 10:05:40 Dell-XPS-8900 dockerd[14862]: ERROR: The 'disable-legacy-registry' configuration option has been removed. Interacting with legacy (v1) registries is no longer supported
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Main process exited, code=exited, status=1/FAILURE
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: Failed to start Docker Application Container Engine.
-- Subject: Unit docker.service has failed
-- Defined-By: systemd
-- Support: http://www.ubuntu.com/support
-- 
-- Unit docker.service has failed.
-- 
-- The result is failed.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Unit entered failed state.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Failed with result 'protocol'.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Service hold-off time over, scheduling restart.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: Stopped Docker Application Container Engine.

Aquí se puede ver el motivo real del problema:

ERROR: The 'disable-legacy-registry' configuration option has been removed. Interacting with legacy (v1) registries is no longer supported

En el post Conectar a un Docker Registry inseguro en Ubuntu con Docker v17.06+ explicaba precisamente que una solución para acceder a un registry no securizado era modificar el fichero /etc/docker/daemon.json y añadir la línea:

"disable-legacy-registry": false,

Pues por lo visto esta opción de configuración ha sido deprecada en la versión actual de docker (17.12).

Se puede comprobar en el siguiente enlace.

Saludos

.ignore IntelliJ IDEA plugin

En mi opinión uno de los plugins esenciales cuando trabajas con IntelliJ IDEA.

Este me permite ocultar, en la vista “project”, aquellos ficheros que se encuentren ignorados por el gestor de versiones (.gitignore).

https://plugins.jetbrains.com/plugin/7495–ignore

Para activar/desactivar la ocultación de los ficheros ignorados sólo hay que abrir la paleta de comandos (control + shift + a) y buscar “show/hide ignored files”. Otra opción es desde el menú contextual (botón derecho sobre el nombre del proyecto).

Saludos

Obtener información de un APK

Necesitamos utilizar la herramienta aapt (Android Asset Packaging Tool).

  • Entrar en el directorio donde se encuentra el SDK de Android:

$ cd $ANDROID_SDK_HOME

  • Localizar el comando “aapt”:

$ find . -name 'aapt'
./build-tools/26.0.2/aapt
./build-tools/25.0.2/aapt
./build-tools/25.0.3/aapt
./build-tools/23.0.3/aapt
./build-tools/26.0.1/aapt
./build-tools/24.0.3/aapt
./build-tools/24.0.1/aapt
./build-tools/24.0.2/aapt

  • Obtener información del APK:

$ ./build-tools/26.0.2/aapt dump badging /path/to/some.apk

La salida de este comando nos da diferente información (nombre del package, versiones de Android, permisos requeridos, …).

package: name='com.example.android.contactmanager' versionCode='1' versionName='1.0' platformBuildVersionName=''
sdkVersion:'5'
targetSdkVersion:'5'
uses-permission: name='android.permission.GET_ACCOUNTS'
uses-permission: name='android.permission.READ_CONTACTS'
uses-permission: name='android.permission.WRITE_CONTACTS'
application-label:'Contact Manager'
application-icon-120:'res/drawable-ldpi/icon.png'
application-icon-160:'res/drawable-mdpi/icon.png'
application-icon-240:'res/drawable-hdpi/icon.png'
application: label='Contact Manager' icon='res/drawable-mdpi/icon.png'
application-debuggable
launchable-activity: name='com.example.android.contactmanager.ContactManager' label='Contact Manager' icon=''
uses-permission: name='android.permission.READ_CALL_LOG'
uses-implied-permission: name='android.permission.READ_CALL_LOG' reason='targetSdkVersion < 16 and requested READ_CONTACTS' uses-permission: name='android.permission.WRITE_CALL_LOG' uses-implied-permission: name='android.permission.WRITE_CALL_LOG' reason='targetSdkVersion < 16 and requested WRITE_CONTACTS' feature-group: label='' uses-feature: name='android.hardware.faketouch' uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps' main other-activities supports-screens: 'small' 'normal' 'large' supports-any-density: 'true' locales: '--_--' densities: '120' '160' '240'

Diferentes configuracion de logback en función del Spring profile

Para una aplicación que estoy desarrollando, cuando arranco el profile “dev” me interesa que la salida del log sea por la cónsola. En un entorno de test (profile “test”) me interesa que almacene el log en un fichero. Es importante que el fichero de configuración se llame src/main/resources/logback-spring.xml.

Ejemplo:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/base.xml" />
	<springProfile name="dev">
		<logger name="com.sourcerebels" level="debug" additivity="false">
			<appender-ref ref="CONSOLE" />
		</logger>
		<root level="warn">
			<appender-ref ref="CONSOLE" />
		</root>
	</springProfile>
	<springProfile name="test">
		<appender name="FILE" class="ch.qos.logback.core.FileAppender">
			<file>/path/to/log/logfile.log</file>
			<encoder>
				<pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg%n</pattern>
			</encoder>
		</appender>
		<logger name="com.sourcerebels" level="info" additivity="false">
			<appender-ref ref="FILE" />
		</logger>
		<root level="warn">
			<appender-ref ref="FILE" />
		</root>
	</springProfile>
</configuration>

Más info: Logging docs.

Instalar Node.js y Npm

Para gestionar las diferentes versiones de Node.js y Npm me gusta utilizar el script Node Version Manager (de la misma forma que rvm para ruby o sdkman para java).

La página del proyecto nvm es:

https://github.com/creationix/nvm

Este script permitirá que diferentes versiones de Node.js y Npm convivan en el mismo equipo de forma aislada. También nos permitirá cambiar de versión fácilmente.

En el momento de escribir este texto, la última versión de nvm es la 0.33.6. Para instalarla, es suficiente con ejecutar este script:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash

Una vez instalado podemos comprobar que todo ha ido bien cerrando y volviendo a abrir el terminal y ejecutando:

nvm

Nos aparecerá un texto con las diferentes opciones que permite nvm.

Para instalar la última versión LTS (Long Term Support) de Node.js y Npm ejecutaremos:

nvm install node --lts

Una vez terminado el proceso podemos comprobar la ruta donde se encuentra npm instalado ejecutando:

which npm

En mi caso, este comando devuelve la ruta:

/home/eduardo.rodriguez/.nvm/versions/node/v6.10.3/bin/npm

Y con esto, ya tenemos instalado Node.js y Npm en nuestro entorno.

Saludos

Obtener los valores de la JVM referentes al tamaño de Heap

Ejecutar:

java -XX:+PrintFlagsFinal -version | grep -i HeapSize

El resultado será similar a este:

    uintx ErgoHeapSizeLimit                         = 0                                   {product}
    uintx HeapSizePerGCThread                       = 87241520                            {product}
    uintx InitialHeapSize                          := 125829120                           {product}
    uintx LargePageHeapSizeThreshold                = 134217728                           {product}
    uintx MaxHeapSize                              := 2006974464                          {product}
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

Spring Boot – Cifrar passwords en bbdd

Información obtenida del siguiente enlace.

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);

    @Autowired
    private UserService userService;
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public DaoAuthenticationProvider authProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug("SecurityConfiguration.configure");
        }
        auth.authenticationProvider(authProvider());
    }
}