Java: Agnostic Cache with Dynamic Proxies and Reflection

Hace tiempo usé un modulo de Perl muy interesante, Memoize. La idea era muy sencilla, tienes una funcion X, si para una entrada A, hay una salida B constantes y ese cálculo es lento, puede usar un caché.

La idea es esa, calcular una vez, usar muchas; sin tener que tener una implementación con cache de cada implementación original.

Aquí está la implementación en Java usando Reflexión:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

public class ReflectMemoizer implements InvocationHandler {
  private final Object object;
  private final HashMap<Method, ConcurrentHashMap<List<Object>, Object>> caches;

  /**
   * Memoize object
   * @param object source
   * @return proxied object
   */
  public static Object memoize(final Object object) 
        throws InstantiationException, IllegalAccessException {
    final Class<?> clazz = object.getClass();
    final ReflectMemoizer memoizer = new ReflectMemoizer(object);
    return Proxy.newProxyInstance(clazz.getClassLoader(), 
        clazz.getInterfaces(), memoizer);
  }

  private ReflectMemoizer(final Object object) {
    this.object = object;
    this.caches = new HashMap<Method, ConcurrentHashMap<List<Object>, Object>>();
  }

  public Object invoke(final Object proxy, final Method method, 
        final Object[] args) throws Throwable {
    if (method.getReturnType().equals(Void.TYPE)) {
      // Don't cache void methods
      return invoke(method, args);
    } else {
      final Map<List<Object>, Object> cache = getCache(method);
      final List<Object> key = Arrays.asList(args);
      Object value = cache.get(key);
      if ((value == null) && !cache.containsKey(key)) {
        value = invoke(method, args);
        cache.put(key, value);
      }
      return value;
    }
  }

  private synchronized Map<List<Object>, Object> getCache(final Method m) {
    ConcurrentHashMap<List<Object>, Object> cache = caches.get(m);
    if (cache == null) {
      cache = new ConcurrentHashMap<List<Object>, Object>();
      caches.put(m, cache);
    }
    return cache;
  }

  private Object invoke(final Method method, final Object[] args) 
        throws Throwable {
    try {
      return method.invoke(object, args);
    } catch (InvocationTargetException e) {
      throw e.getTargetException();
    }
  }
}

Leer más de esta entrada

Java: ServiceLoader decouple API and Implementation

Un sencillo ejemplo del uso de ServiceLoader, muy útil para para crear aplicaciones extensibles a la vez que separamos las interfaces de sus implementaciones en diferentes empaquetados (jar).

La factoría que usa el ServiceLoader y la interface común (ambos ficheros en storage-api.jar):

package com.acme;

import java.util.ServiceLoader;

public class StorageFactory {
  public static Storage getInstance() {
    for (Storage storage : ServiceLoader.load(Storage.class)) {
      return storage;
    }
    throw new UnsupportedOperationException("No implementation found");
  }
}

La interface que deben implementar todos los servicios:

package com.acme;

public interface Storage {
  void put(String key, String value);

  String get(String key);
}

Un ejemplo de implementación en memoria (ambos ficheros en storage-memory.jar):

package com.acme;

import java.util.concurrent.ConcurrentHashMap;

public class MemoryStorage implements Storage {
  private final ConcurrentHashMap<String, String> map;

  public MemoryStorage() {
    map = new ConcurrentHashMap<String, String>();
  }

  @Override
  public void put(final String key, final String value) {
    map.put(key, value);
  }

  @Override
  public String get(final String key) {
    return map.get(key);
  }
}

El fichero META-INF/services/com.acme.Storage

com.acme.MemoryStorage

Otra implementación estilo /dev/null (ambos ficheros en storage-null.jar):

package com.acme;

public class NullStorage implements Storage {
  @Override
  public void put(final String key, final String value) {
  }

  @Override
  public String get(final String key) {
    return null;
  }
}

El fichero META-INF/services/com.acme.Storage

com.acme.NullStorage

Y para acabar, el código de prueba:

package com.acme;

public class TestStorage {
  public static void main(final String[] args) throws Throwable {
    final Storage storage = StorageFactory.getInstance();
    storage.put("data", "hello world");
    System.out.println(storage.get("data"));
  }
}

Referencias:
java.util.ServiceLoader

Java: Get Current Method Name from a Class

Una cosa que suele mejorar mucho las trazas o el profiling es saber “donde estás”, no solo en que clase, si no en que método. Por desgracia actualmente en Java no existe un simple Thread.currentThread().getCurrentMethod() o similar.

La información se puede sacar del StackTrace (también hay un método oscuro sun.reflect.Reflection.getCallerClass(int), pero en Java 8 ha desaparecido y como dicen los de Oracle: sun.* is private api, is not supported).

Lo malo del StackTrace es que es relativamente pesado, tanto si usas Thread.currentThread().getStackTrace() o new Throwable().getStackTrace()

Se puede acelerar la ejecución usando los metodos internos de Throwable mediante reflexión (no suelo ser muy partidario, pero hablamos de 10 veces más rápido). Para asegurar que si los métodos privados cambian el código siga funcionando, aquí un método mixto:
Leer más de esta entrada

Java: Configuration and Preferences API

Lidiar con la configuración siempre es un tema complejo, históricamente los Properties y los XML siempre han salido al rescate.
La problemática viene cuando empiezan a crecer el número de parámetros, de ficheros de configuración y el número de entornos donde debes desplegar (desarrollo, test, producción), ya sean urls de conexión, configuraciones de bbdd o directorios de almacenamiento. Lo peor es cuando esos parametros van empaquetados dentro del jar o el war, entonces tienes q hacer modificaciones manuales, usar profiles de maven o cosas similares.

Si estas cosas te suenan… Una alternativa es usar el API de Preferences de Java (usando las systemRoot); un ejemplo de uso:

Leer más de esta entrada

Firefox: New look and feel on Firefox 29 (Australis)

Sufriendo los cambios de look-and-feel (otra vez) del nuevo Firefox 29?

Personalmente no entiendo la moda de clonar Chrome, si me gustara Chrome lo usaría, pero no me gusta y por eso uso Firefox.

Si prefieres el look-and-feel funcional y espartano de lineas rectas… eres un un amante de lo clásico, casi los 8 bits, o simplemente quieres colocar las cosas a tu modo para ahorrarte tiempo dejando que tu memoria muscular te lleve a lo que más usas:

Nota de Soporte:
https://support.mozilla.org/en-US/kb/how-to-make-new-firefox-look-like-old-firefox

Dos plug-ins para customizar apariencia y menus:
https://addons.mozilla.org/firefox/addon/classicthemerestorer/
http://menueditor.mozdev.org/

 

 

Java: WallClock versus System.currentTimeMillis

Como de costoso es llamar a System.currentTimeMillis()?

Si tenemos una función dependiente del tiempo y a esa funcion la llamamos, digamos… 500.000.000 veces y tarda 9911ms; hay alguna manera de hacer que sea más rápido? Bueno, si eludimos la llamada al reloj del sistema mediante un pequeño cache puede que ganemos unos segundos…

Esto sería el esqueleto del sistema, un thread que actualiza el reloj a intervalos regulares y una funcion q devuelve el último valor conocido:

public class WallClock implements Runnable {
  private static final int clockLatency = 1;  // WallClock time refresh (millis)
  private static WallClock self = null;       // Singleton
  private volatile long lastTimeMillis = 0L;  // Last time cached from SystemClock
  private volatile Thread clocker = null;     // WallClock Thread

  public static synchronized WallClock getInstance() {
    if (self == null) {
      self = new WallClock();
      self.init();
    }
    return self;
  }

  public long currentTimeMillis() {
    return lastTimeMillis;
  }

  private void init() {
    if (clocker == null) {
      clocker = new Thread(this);
      clocker.setDaemon(true);
      clocker.start();
    }
  }
  public void destroy() {
    clocker.interrupt();
  }

  /**
   * Run, baby, run
   */
  public void run() {
    try {
      while (!Thread.currentThread().isInterrupted()) {
        lastTimeMillis = System.currentTimeMillis();
        Thread.sleep(clockLatency);
      }
    } catch (InterruptedException ie) {
      /* Allow thread to exit */
    } finally {
      clocker = null;
    }
  }
}

Y cuanto tiempo ganamos con esta aproximación?

  public static void main(final String[] args) throws Throwable {
    final int TOTAL = 500000000;
    long ax = 0, ts = 0;
    //
    ts = System.currentTimeMillis();
    for (int i = 0; i < TOTAL; i++) {
      ax += (System.currentTimeMillis() & 0xF);
    }
    System.out.println("SystemClock: " + (System.currentTimeMillis() - ts) + "ms");
    //
    final WallClock wc = WallClock.getInstance();
    ts = System.currentTimeMillis();
    for (int i = 0; i < TOTAL; i++) {
      ax += (wc.currentTimeMillis() & 0xF);
    }
    System.out.println("WallClock: " + (System.currentTimeMillis() - ts) + "ms");
    if ((ax > 0) || (ts > 0)) // Dummy (always true)
      wc.destroy();
  }
SystemClock: 9911ms
WallClock..: 1820ms

El resultado es un aumento de la velocidad x5, el precio q se paga es tener un thread en background y algo de perdida de precision en los valores del reloj, pero es lo que tienen los caches.

Source code: WallClock.java

Java: The Thread Unsafe SimpleDateFormat

A muchos nos ha pasado que, quizá por la tímida nota del JavaDoc a pie de pagina del SimpleDateFormat (Date formats are not synchronized), hemos visto a la clase morder el polvo, aqui el ejemplo:

Leer más de esta entrada

MySQL: Small Guide for Simple Partitioning

Una mini guia de particionamiento sencillo en MySQL 5.5 basado en fecha:

Nota(1): Hay que incluir en la PK el campo que quieras usar en el particionado, así te ahorraras el error:

ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function
CREATE TABLE IF NOT EXISTS T1 (
  ID INT NOT NULL AUTO_INCREMENT,
  PART DATE NOT NULL,
  NUM INT NOT NULL,
  PRIMARY KEY (ID,PART))
ENGINE = InnoDB 
  PARTITION BY RANGE COLUMNS(PART) ( 
    PARTITION p20140417 VALUES LESS THAN ('2014-04-18'),  
    PARTITION p20140418 VALUES LESS THAN ('2014-04-19'),  
    PARTITION p20140419 VALUES LESS THAN ('2014-04-20')
);

Nota(2): Ojo con las columnas DATE/DATETIME, se almacena la fecha tal cual (a diferencia de un TIMESTAMP que se almacena en formato UTC).

Si la tabla ya existe sin particionamiento y quiere activarse:

ALTER TABLE T1 
  PARTITION BY RANGE COLUMNS(PART) ( 
    PARTITION p20140417 VALUES LESS THAN ('2014-04-18'),  
    PARTITION p20140418 VALUES LESS THAN ('2014-04-19'),  
    PARTITION p20140419 VALUES LESS THAN ('2014-04-20')
);

Agregar nuevas particiones a un particionamiento existente:

ALTER TABLE T1 
  ADD PARTITION (
    PARTITION p20140420 VALUES LESS THAN ('2014-04-21'),
    PARTITION p20140421 VALUES LESS THAN ('2014-04-22')
);

Para hacer el DROP de particiones y los datos contenidos en ellas:

ALTER TABLE T1 
  DROP PARTITION p20140417, p20140418;

Para quitar el particionamiento (sin perder datos):

ALTER TABLE T1 REMOVE PARTITIONING;

Para ver los particionamientos que hay definidos:

SELECT TABLE_SCHEMA,TABLE_NAME,PARTITION_NAME,
       PARTITION_METHOD,PARTITION_DESCRIPTION,TABLE_ROWS 
  FROM INFORMATION_SCHEMA.PARTITIONS
 WHERE PARTITION_NAME IS NOT NULL;

O un resumen de uso:

SELECT TABLE_SCHEMA,TABLE_NAME,PARTITION_NAME,TABLE_ROWS 
  FROM INFORMATION_SCHEMA.PARTITIONS 
 WHERE PARTITION_NAME IS NOT NULL;
+--------------+------------+----------------+------------+
| TABLE_SCHEMA | TABLE_NAME | PARTITION_NAME | TABLE_ROWS |
+--------------+------------+----------------+------------+
| S1           | T1         | p20140417      |          3 |
| S1           | T1         | p20140418      |          3 |
| S1           | T1         | p20140419      |          2 |
+--------------+------------+----------------+------------+
3 rows in set (0.01 sec)

Para poder sacar los planes de acceso del particionamiento:

EXPLAIN PARTITIONS 
 SELECT * FROM T1
  WHERE ID=1 
    AND PART = UTC_DATE(); 
+----+-------------+-------+------------+-------+---------------+---------+---------+-------------+------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref         | rows | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------------+------+-------+
|  1 | SIMPLE      | T1    | p20140417  | const | PRIMARY       | PRIMARY | 11      | const,const |    1 |       |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------------+------+-------+
1 row in set (0.00 sec)
EXPLAIN PARTITIONS 
 SELECT * FROM T1
  WHERE ID=1;
+----+-------------+-------+-------------------------------+------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | partitions                    | type | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+-------+-------------------------------+------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | T1    | p20140417,p20140418,p20140419 | ref  | PRIMARY       | PRIMARY | 8       | const |    2 |       |
+----+-------------+-------+-------------------------------+------+---------------+---------+---------+-------+------+-------+
1 row in set (0.00 sec)

Y la select de prueba que usa la columna de particionamiento (PART):

-- asumiento que hoy es 2014-04-17 y tenemos 365 particionamientos (1 por dia)
SELECT * FROM T1
 WHERE ID=3 
   AND PART BETWEEN UTC_DATE()-1 AND UTC_DATE()+1;
-- en este caso la busqueda estaria entre: p20140416, p20140417 y p20140418

Nota(3): El numero maximo de particiones por tabla es de 1024.

Referencias:
Restrictions and Limitations on Partitioning
Partitioning Types
RANGE Partitioning
RANGE COLUMNS partitioning
ALTER TABLE
ALTER TABLE Partition Operations
Data Type Storage Requirements
The DATE, DATETIME, and TIMESTAMP Types
Date and Time Functions

Java: HttpURLConnection and ignored setRequestProperty headers

Si usas HttpURLConnection es posible que haya ciertas cabeceras que no puedas usar mediante setRequestProperty (esto no siempre fué así, fue un cambio q añadieron en Java 1.6.0_22, sin aviso ni documentación oficial que yo haya encontrado), quizá por una cuestion de seguridad en Applets/Navegadores Java?.

Aquí está el origen del problema:

sun.net.www.protocol.http.HttpURLConnection
/*
 * Restrict setting of request headers through the public api
 * consistent with JavaScript XMLHttpRequest2 with a few
 * exceptions. Disallowed headers are silently ignored for
 * backwards compatibility reasons rather than throwing a
 * SecurityException. For example, some applets set the
 * Host header since old JREs did not implement HTTP 1.1.
 * Additionally, any header starting with Sec- is
 * disallowed.
 *
 * The following headers are allowed for historical reasons:
 *
 * Accept-Charset, Accept-Encoding, Cookie, Cookie2, Date,
 * Referer, TE, User-Agent, headers beginning with Proxy-.
 *
 * The following headers are allowed in a limited form:
 *
 * Connection: close
 *
 * See http://www.w3.org/TR/XMLHttpRequest2.
 */
private static final boolean allowRestrictedHeaders;
private static final String[] restrictedHeaders = {
    /* Restricted by XMLHttpRequest2 */
    //"Accept-Charset",
    //"Accept-Encoding",
    "Access-Control-Request-Headers",
    "Access-Control-Request-Method",
    "Connection", /* close is allowed */
    "Content-Length",
    //"Cookie",
    //"Cookie2",
    "Content-Transfer-Encoding",
    //"Date",
    //"Expect",
    "Host",
    "Keep-Alive",
    "Origin",
    // "Referer",
    // "TE",
    "Trailer",
    "Transfer-Encoding",
    "Upgrade",
    //"User-Agent",
    "Via"
};
// ... cut ...

static {
  // ... cut ...
  allowRestrictedHeaders = ((Boolean)java.security.AccessController.doPrivileged(
    new sun.security.action.GetBooleanAction(
      "sun.net.http.allowRestrictedHeaders"))).booleanValue();
  // ... cut ...
}

Así, por ejemplo, si quieres hacer una peticion HTTP PUT/POST con el body comprimido usando un HttpURLConnection + GZIPOutputStream con su correspondiente setRequestProperty(“Content-Transfer-Encoding”, “gzip”); cuando llegue al servidor no será capaz de entender los datos.

El workarround, definir la System Property:

-Dsun.net.http.allowRestrictedHeaders=true

Habría sido buena idea añadir un comentario en el metodo “setRequestProperty”, verdad?

Referencias:
Java BUG#6996110: Won’t Fix
Source of HttpURLConnection

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

%d personas les gusta esto: