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());
    }
}

Failed to instantiate Pageable: Specified class is an interface

Al añadir los parámetros de paginación a un controlador de spring. Unos tests que estaba haciendo con MockMvc y Mockito me comenzaron a fallar con el siguiente error:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.domain.Pageable]: Specified class is an interface

La solución consiste en proporcionar una clase que resuelva los argumentos en tiempo de test (setCustomArgumentResolvers). Esta clase solo atiende al parámetro de tipo Pageable.

public class TestUtils {
    public static MockMvcBuilder prepareMockMvcForPageableArguments(Object... controllers) {
        return MockMvcBuilders.standaloneSetup(controllers)
                .setCustomArgumentResolvers(
                        new HandlerMethodArgumentResolver() {
                            @Override
                            public boolean supportsParameter(MethodParameter parameter) {
                                return parameter.getParameterType().equals(Pageable.class);
                            }

                            @Override
                            public Object resolveArgument(MethodParameter parameter,
                                    ModelAndViewContainer container,
                                    NativeWebRequest request,
                                    WebDataBinderFactory binderFactory) throws Exception {
                                return new PageRequest(0, 50);
                            }
                        });

    }
}

Para utilizarlo:

MockMvc mvc = TestUtils.prepareMockMvcForPageableArguments(controller).build();

Fuente:

https://github.com/terasolunaorg/terasoluna-tourreservation/blob/master/terasoluna-tourreservation-web/src/test/java/org/terasoluna/tourreservation/app/searchtour/SearchTourControllerTest.java

Paginación con Spring Data

A modo de recordatorio.

  • El controlador debe recibir un objeto de tipo Pageable
@GetMapping("/myentities")
public Page getMyEntities(final String userUuid, Pageable pageable) {
    return myRepository.findAllByUserUuid(userUuid, pageable);
}
  • Esto significa que a la llamada al controlador se le pueden pasar los parámetros page, limit y sort para controlar la página que queremos recuperar y el orden.
page=número de página a mostrar
limit=número de elementos a mostrar por página
sort=campo por el que se quiere ordenar
  •  Si se quiere personalizar el nombre de estos parámetros, se puede hacer modificando las siguientes propiedades en el fichero application.properties.
spring.data.rest.page-param-name=page
spring.data.rest.limit-param-name=limit
spring.data.rest.sort-param-name=sort
  • El repositorio tiene que extender el interfaz de Spring PagingAndSortingRepository. JpaRepository ya extiende de este.
  • El método findXX del repositorio debe devolver un objeto de tipo Page y recibir un parámetro de tipo Pageable.
@Repository
public interface MyRepository extends JpaRepository&lt;MyEntity, Long&gt; {
    public Page findAllByUserUuid(String userUuid, Pageable pageable);
}

Referencias

https://docs.spring.io/spring-data/rest/docs/1.1.x/reference/html/paging-chapter.html

Saludos

Conectar a un Docker Registry inseguro en Ubuntu con Docker v17.06+

Al actualizar los paquetes de mi instalación de Ubuntu, he visto que se ha actualizado docker.

A partir de ese momento no he podido hacer push/pull al registry que tenemos (está instalado de forma insegura en un servidor de la LAN).

En el access.log del registry veo las siguientes entradas de error 404:

84.88.79.211 - - [05/Jul/2017:07:55:17 +0000] "POST /v2/myimagename/blobs/uploads/ HTTP/1.1" 404 233 "-" "docker/17.06.0-ce go/go1.8.3 git-commit/02c1d87 kernel/4.4.0-83-generic os/linux arch/amd64 UpstreamClient(Docker-Client/17.06.0-ce \(linux\))"

Por defecto Docker, al hacer las peticiones contra el registry, trata de hacerlas contra los servicios “v2” y en caso de no encontrarlos intenta contra “v1”. En mi caso esto último no venía sucediendo.

En las release notes de la versión 16.03 veo lo siguiente:

Note: Docker 17.06 by default disables communication with legacy (v1) registries. If you require interaction with registries that have not yet migrated to the v2 protocol, set the --disable-legacy-registry=false daemon option. Interaction with v1 registries will be removed in Docker 17.12.

La solución pasa por actualizar el docker registry ya que en la versión 17.12 esta funcionalidad directamente no existirá. Mientras tanto, podemos añadir las siguientes líneas al fichero /etc/docker/daemon.json

{
  "disable-legacy-registry": false,
  "insecure-registries" : ["myregistryhost:port"]
}

Saludos

Angular Proxy Config para evitar errores de Cross Domain

En mi entorno de desarrollo, tengo un webservice hecho con Java y que està accesible por el puerto 8888.

El frontend es una aplicación javascript Angular y que está accesible por el puerto 4200.

Para evitar problemas de Cross domain (el browser impide que el frontend ejecute peticiones HTTP contra un host/puerto distinto).

Crear el fichero proxy.conf.json en la raíz del proyecto Angular:

{
    "/oauth": {
        "target": "http://localhost:8888",
        "secure": false
    } 
}

Arrancar el servidor de desarrollo indicando la configuración proxy.

ng serve --proxy-config proxy.conf.json

De esta forma, cuando se haga una petición a http://localhost:4200/oauth, realmente se estará ejecutando contra http://localhost:8888/oauth.

En producción tendremos que configurar NGINX de la misma manera.

Ocultar método soportado por endpoint de terceros. Spring y Swagger.

A veces, queremos ocultar que uno de los métodos soportados por un endpoint que no controlamos no aparezca en Swagger.

No he visto que exista ninguna función para hacer esto.

Este es el Predicate que he implementado.

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.google.common.base.Predicate;

import springfox.documentation.RequestHandler;

public class IsRequestMethodPredicate implements Predicate<RequestHandler> {

	private final String path;
	private final RequestMethod method;

	public IsRequestMethodPredicate(String path, RequestMethod method) {
		this.path = path;
		this.method = method;
	}

	public static IsRequestMethodPredicate isRequestMethod(String path, RequestMethod method) {
		return new IsRequestMethodPredicate(path, method);
	}

	public static Predicate<RequestHandler> isNotRequestMethod(String path, RequestMethod method) {
		return not(isRequestMethod(path, method));
	}

	@Override
	public boolean apply(RequestHandler input) {
		RequestMapping mapping = AnnotationUtils
				.findAnnotation(input.getHandlerMethod().getMethod(), RequestMapping.class);
		if (mapping != null && mapping.path().length > 0) {
			return hasPath(mapping, path) && hasMethod(mapping, method);
		}
		return false;
	}

	private static boolean hasPath(RequestMapping mapping, String path) {
		for (String mappingPath : mapping.path()) {
			if (mappingPath != null && mappingPath.equals(path)) {
				return true;
			}
		}
		return false;
	}

	private static boolean hasMethod(RequestMapping mapping, RequestMethod method) {
		for (RequestMethod mappingMethod : mapping.method()) {
			if (mappingMethod == method) {
				return true;
			}
		}
		return false;
	}
}

Para utilizarlo:

import static IsRequestMethodPredicate.isNotRequestMethod;

@Bean
public Docket api() {
	return new Docket(DocumentationType.SWAGGER_2)
			.select()
			.apis(isNotRequestMethod("/some/third/party/endpoint", RequestMethod.GET))
			.paths(paths())
			.build();
}