Web Services – Interoperabilidad Java (Server) y .Net (Clients)

Por razones de trabajo me he encontrado algunas veces con este escenario, y después de haber tenido un par de problemas no muy graves pero que consumían tiempo se me ocurrió un esquema que hasta el momento me funciona, y es lo que voy a compartir en el post.
Minimalismo, si esa es la respuesta a esto, ya que regresamos a lo mas simple al exponer los web services retornando un string siempre como resultado del método, y así mismo recibir datos primitivos como parámetros, reemplazando a la estructuras de datos complejas, pero no en su totalidad ya que en ese dato primitivo en realidad contiene una estructura compleja serializada en Json y comprimida con un algoritomo Zip estándar y convertido a Hexadecimal.
La solución puede ser aplicada no solo a cliente .Net sino también a clientes Java, pero vamos a enfocar el post en clientes .net.
Para el lado del server Java vamos a necesitar los siguientes componentes adicionales al JDK:

  • Google Gson (Ya he utilizado esta librería en otros posts, recomendada hasta que el JDK no tenga incluya una implementación estándar de manipulación de Json)
  • Apache Commons Codec

Para los clientes de .net van los siguientes componentes adicionales al framework:

Ya con esto listo, creamos un proyecto web en java en su IDE preferido, yo voy a utilizar eclipse.

Para el ejemplo tengo 3 clases; un bean con una propiedad, luego un bean un poco mas complejo y finalmente uno que contiene a todo para mostrar una clase con una estructura no tan simple como prueba de concepto, aquí el código de cada uno:

public class SuperSimpleBean {
	private int number;

	public SuperSimpleBean(int number){
		this.number = number;
	}

	public SuperSimpleBean(){

	}

	public int getNumber() {
		return number;
	}

	public void setNumber(int number) {
		this.number = number;
	}
}
public class SimpleBean{

	private String code;
	private Date datetime;
	private List superSimpleBeans;

	public SimpleBean(){
		superSimpleBeans = new ArrayList();
	}

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public Date getDatetime() {
		return datetime;
	}

	public void setDatetime(Date datetime) {
		this.datetime = datetime;
	}

	public List getSimpleBeans() {
		return superSimpleBeans;
	}

	public void setSimpleBeans(List superSimpleBeans) {
		this.superSimpleBeans = superSimpleBeans;
	}

}
public class ComplexBean {
	private List simpleBeans;
	private String name;

	public ComplexBean(){
		simpleBeans = new ArrayList();
	}

	public List getSimpleBeans() {
		return simpleBeans;
	}

	public void setSimpleBeans(List simpleBeans) {
		this.simpleBeans = simpleBeans;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

El objeto que va a devolver el web service en string va a pasar por los siguientes pasos:

  • Serializacion a Json
  • Compresion del Json
  • Convertir a un String hexadecimal los bytes comprimidos

Las clases que nos van a ayudar con ese cometido son las siguientes:

public class ZipUtil {

	private static String toHexString(byte[] bytes) {
		return Hex.encodeHexString(bytes);
	}

	private static byte[] toByteArray(String hexString) {
		try {
			return Hex.decodeHex(hexString.toCharArray());
		} catch (DecoderException e) {
			e.printStackTrace();
		}
		return null;
	}

	private static byte[] fromGByteToByte(byte[] gbytes) {
		ByteArrayOutputStream baos = null;
		ByteArrayInputStream bais = new ByteArrayInputStream(gbytes);
		try {
			baos = new ByteArrayOutputStream();
			GZIPInputStream gzis = new GZIPInputStream(bais);
			byte[] bytes = new byte[1024];
			int len;
			while ((len = gzis.read(bytes)) > 0) {
				baos.write(bytes, 0, len);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return (baos.toByteArray());
	}

	private static byte[] fromByteToGByte(byte[] bytes) {
		ByteArrayOutputStream baos = null;
		try {
			ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
			baos = new ByteArrayOutputStream();
			GZIPOutputStream gzos = new GZIPOutputStream(baos);
			byte[] buffer = new byte[1024];
			int len;
			while ((len = bais.read(buffer)) >= 0) {
				gzos.write(buffer, 0, len);
			}
			gzos.close();
			baos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return (baos.toByteArray());
	}

	public static String compressAndEncodeText(String text) {
		return toHexString(fromByteToGByte(text.getBytes()));
	}

	public static String decompressAndDecode(String data) {
		data = data.replace("-", "");
		byte[] zippedBytes;
		try {
			zippedBytes = toByteArray(data);
			byte[] decompressed = fromGByteToByte(zippedBytes);
			return new String(decompressed);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";

	}
}
public class ParserUtil {

	public static String parseAndCompress(Object o){
		String json = new Gson().toJson(o);
		return ZipUtil.compressAndEncodeText(json);
	}

}

Y ahora en la clase que se va a exponer como web service creamos objetos dummy con datos y devolvemos el resultado serializado y comprimido:

public class WebService {

	public String getData(){

		ComplexBean cBean = new ComplexBean();
		cBean.setName("A complex bean");

		for(int i=0; i<=1000; i++){
			SimpleBean sBean = new SimpleBean();
			sBean.setCode(String.format("Code %s", i));
			sBean.setDatetime(Calendar.getInstance().getTime());

			for(int j=0; j<=10;j++){
				SuperSimpleBean ssBean = new SuperSimpleBean(j);
				sBean.getSimpleBeans().add(ssBean);
			}

			cBean.getSimpleBeans().add(sBean);
		}

		return ParserUtil.parseAndCompress(cBean);
	}
}

Publicamos el web service, lo probamos con soapUI y el resultado es el siguiente:

soapUI

1f8b0800000000000000dddcbd8aa4d71586d15b693a9ea0cefe39679fce2c83338……… un string hexadecimal.

Si devolviera json en el web service, el tamaño de los datos de respuesta seria de 216kb, mientras que la respuesta comprimida es de 6.34kb, ustedes pueden calcular el porcentaje de compresión.

Del lado del cliente .net simplemente hay que hacer las clases con la misma estructura, algo que aun no he tenido tiempo para revisar es que al momento de deserializar el json las clases de .net en compact framework deben tener declarados los campos, en lugar de tener propiedades automáticas, honestamente no he encontrado en la librería Json for CF algún parámetro para que sea case insensitive, pero para el framework estándar no hay problemas:

public class SuperSimpleClassInfo
    {
        public int Number { get; set; }

        public SuperSimpleClassInfo()
        {
        }

        public SuperSimpleClassInfo(int number)
        {
            this.Number = number;
        }
    }
public class SimpleClassInfo
    {
        public string Code { get; set; }
        public DateTime DateTime { get; set; }
        public List<SuperSimpleClassInfo> SuperSimpleBeans { get; set; }

        public SimpleClassInfo()
        {
            SuperSimpleBeans = new List<SuperSimpleClassInfo>();
        }

    }
public class ComplexClassInfo
    {
        public string Name { get; set; }
        public List<SimpleClassInfo> SimpleBeans { get; set; }

        public ComplexClassInfo()
        {
            SimpleBeans = new List<SimpleClassInfo>();
        }
    }

Necesitamos una clase para convertir el texto hexadecimal a bytes, descomprimir y luego “parsear” el json a nuestra estructura de objetos, para descomprimir el texto creamos una clase ZipUtil similar a la que tenemos del lado de java:

public class ZipUtil
    {
        public static byte[] Decompress(byte[] compressed)
        {
            byte[] buffer = new byte[4096];
            using (MemoryStream ms = new MemoryStream(compressed))
            using (GZipStream gzs = new GZipStream(ms, CompressionMode.Decompress))
            using (MemoryStream uncompressed = new MemoryStream())
            {
                for (int r = -1; r != 0; r = gzs.Read(buffer, 0, buffer.Length))
                    if (r > 0) uncompressed.Write(buffer, 0, r);
                return uncompressed.ToArray();
            }
        }

        public static byte[] Compress(byte[] raw)
        {
            using (MemoryStream memory = new MemoryStream())
            {
                using (GZipStream gzip = new GZipStream(memory, CompressionMode.Compress, true))
                {
                    gzip.Write(raw, 0, raw.Length);
                }
                return memory.ToArray();
            }
        }

        public static string CompressAndEncode(string text)
        {
            byte[] compressed = Compress(System.Text.ASCIIEncoding.UTF8.GetBytes(text));
            string hex = BitConverter.ToString(compressed);
            return hex;
        }

        public static byte[] ToByteArray(string hexString)
        {
            hexString = hexString.Replace("-", "");
            int NumberChars = hexString.Length;
            byte[] bytes = new byte[NumberChars / 2];
            for (int i = 0; i < NumberChars; i += 2)
                bytes[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
            return bytes;
        }

        public static string DecompressAndDecode(string text)
        {
            byte[] textBytes = ToByteArray(text);
            byte[] uncompressed = Decompress(textBytes);
            string result = System.Text.ASCIIEncoding.ASCII
                    .GetString(uncompressed, 0, uncompressed.Length);
            return result;
        }
    }

Listo, con todo ya preparado agregamos la referencia al web service:

addreference

Una vez agregada la referencia es recomendable actualizar en el archivo de configuración el tamaño máximo de datos permitidos en las propiedades: maxBufferSize, maxReceivedMessageSize y maxStringContentLength con el valor 2147483647.

Listo, procedemos a invocar el metodo del web service, a descomprimir y luego con la libreria Json a convertir los datos a un objeto:

JavaWS.WebServiceClient client = new JavaWS.WebServiceClient();
            string data = client.getData();
            string json = ZipUtil.DecompressAndDecode(data);
            ComplexClassInfo complex = JsonConvert.DeserializeObject<ComplexClassInfo>(json);

El resultado es el objeto complex cargado con toda la información  para enviar datos en el mismo formato se aplica el proceso inverso desde .net a java.

Descargar fuentes

Happy Coding!

Saludos,
gish@c

FileUpload con Eclipse, Tomcat y Primefaces

Devolviendo algo de conocimiento a la comunidad, este ejemplo sencillo pero útil para ademas de cargar archivos con el fileupload de primefaces poder leer parámetros adicionales que se envíen en el formulario.

Para poder iniciar se debe configurar JSF en eclipse, deben descargar una implementacion que puede ser Mojarra o MyFaces, una manera de descargar es en el proyecto web Properties->Project Faces -> Java Server Faces -> Download Library (El icono del lado derecho) y ahí escogen la implementacion, y posteriormente agregar el jar de jstl-1.2 a la carpeta WebContent/WEB-INF/lib.

Para usar primefaces basta con agregar el jar a la carpeta WEB-INF/lib , pero para usar el fileUpload si es necesario agregar los jars de commons-fileupload y commons-io los cuales deben revisar en la documentación de primefaces que versión corresponde según la versión de prime y configurar un par de lineas en el web.xml:

<filter>
		<filter-name>PrimeFaces FileUpload Filter</filter-name>
		<filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>PrimeFaces FileUpload Filter</filter-name>
		<servlet-name>Faces Servlet</servlet-name>
	</filter-mapping>

En la vista vamos a tener el siguiente código:

<f:view>
	<h:head>
		<meta http-equiv="Content-Type"
			content="text/html; charset=ISO-8859-1" />
		<title>File Upload Sample</title>
	</h:head>
	<body>
		<h:form id="myform" enctype="multipart/form-data">
			<h:outputStylesheet library="css" name="style.css" />
			<p:growl id="messages" showDetail="true" />
			<p:fileUpload
				fileUploadListener="#{fileuploadcontroller.uploadAttachment}"
				mode="advanced" update="messages" multiple="true"
				sizeLimit="900000000"
				allowTypes="/(\.|\/)(gif|jpe?g|png|txt)$/"
				uploadLabel="Cargar" cancelLabel="Cancelar" label="Buscar archivo"
				required="true" />
			<br />
			<br />
			<strong>Mensaje</strong> <br/><br />
			<p:inputText id="txtField" style="width:250px" maxlength="40">
				<f:validateRequired for="txtField"></f:validateRequired>
			</p:inputText>
		</h:form>
	</body>
</f:view>

En la configuración del fileUpload definimos el modo advanced para que aparezca con ese estilo de menubar, el tamaño máximo del archivo, que permita varios uploads a la vez , los tipos de archivo permitidos y por ultimo el método del controlador para gestionar la carga del archivo; también podemos notar que tenemos un inputText pero que no esta asociado en la propiedad value a ningun bean, esto sucede ya que al form lo hemos definido como enctype=”multipart/form-data” esto causa que lleguen los valores null al controlador, y la manera en que debemos obtener los parámetros es a través del objeto request que tiene el mapa de parámetros del formulario.

FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("form:parametro")

El codigo del controlador:

@ManagedBean(name="fileuploadcontroller")
@RequestScoped
public class FileUploadController {

	public void uploadAttachment(FileUploadEvent event){
		UploadedFile file = event.getFile();
		ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
		String txtField = ec.getRequestParameterMap().get("myform:txtField");
		String filePath = ec.getRealPath(String.format("/resources/img/%s",file.getFileName()));
		try {
			FileOutputStream fos = new FileOutputStream(filePath);
			fos.write(file.getContents());
			fos.flush();
			fos.close();

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		FacesContext context = FacesContext.getCurrentInstance();
		context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO,String.format("Archivo cargado: %s ", file.getFileName()),
				String.format("Mensaje: %s", txtField)));

	}

}

El método uploadAttachment es el que esta asociado en el fileUpload en la propiedad fileUploadListener, dentro de este como vemos podemos obtener el parámetro del inputText a través de :myform:txtField, que corresponden al id del fomulario <form> y el inputText respectivamente, el resultado del ejemplo es cargar el archivo en la carpeta /resources/img y presentar un mensaje con el texto ingresado en el input, que obviamente puede ser utilizado con otros fines acorde al caso que se les presente.

sample

Descargar proyecto eclipse.

Saludos,

@gishac

Java Series – Authentication y Authorization en J2EE

Si hay algo de lo que no se pueden quejar las personas que desarrollan en web con asp.net es sobre lo fácil que es el manejo de seguridad a recursos protegidos (authorization) y lo intuitivo de los tipos de autenticación que ofrece .net (authentication), pero suele ocurrir que por razones laborales/educativas etc dan el salto a otra plataforma de desarrollo y oh! sorpresa… donde esta esa sección de configuración para proteger los accesos??? Y así empieza la odisea … El post de hoy expone justamente el manejo de seguridad en J2EE.
Para el ejemplo vamos a crear un proyecto web (El proyecto está con JSF 2) y vamos a definir directorios donde van a estar las páginas de administración para 2 roles de usuario: Administrador y Super Administrador, controlando el acceso al recurso que le corresponda. La estructura para el ejemplo es la siguiente:

Como se visualiza en el gráfico están claramente definidas las carpetas, en las cuales se supondría que están las páginas a las que sólo se puede tener acceso con ciertos privilegios.
Adicionalmente el proyecto cuenta con la página forbidden.xhtml que para el caso es la página a la que es redirigido el request cuando se trata de acceder a una sección protegida sin tener las credenciales requeridas, y la página index.xhtml que es la página de login.
El estándar casi por omisión para seguridad en J2EE es Spring Security, pero para alguien que viene de otra plataforma le introduce otro millón mas de conceptos (lo cual no es malo, sólo que a veces son tan amplios que nos apartan totalmente el camino de lo que realmente queremos implementar), así que desde la especificación de Servlets 2.3 tenemos un feature que permite interceptar los requests y de esa manera determinar quien está tratando de acceder a los recursos de la aplicación; este feature son los Filters.
Muchos son los casos de uso en los que nos sirve interceptar requests, pero vamos a enfocarnos en autenticacion y autorización.
Para definir un filtro simplemente, creamos una clase que implemente la interfaz javax.servlet.Filter, e implementamos los métodos que nos exige:

public void init(FilterConfig filterConfig);

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain);

public void destroy();

Tenemos definido el filtro y para interceptar los requests tenemos que configurarlo en el web.xml y mapear las rutas en las que va actuar el mismo, para hacerlo colocamos la siguiente configuración:


    <filter>
        <filter-name>AuthAdminFilter</filter-name>
        <filter-class>demo.security.AuthAdminFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>AuthAdminFilter</filter-name>
        <url-pattern>/administration/*</url-pattern>
    </filter-mapping>

El primer elemento filter-name es referencial, lo que si es importante es en filter-class colocar el nombre completo de la clase que está implementando el filtro, luego en el siguiente filter-name se hace referencia a la declaración anterior y colocamos la ruta que queremos que el filtro administre, al colocar * cualquier página que esté dentro de ese contexto será validada.
Para validar en el filtro si el usuario está autenticado y el rol que tiene, en la pantalla de login, una vez validadas las credenciales vamos a almacenar una variable de sesión con un objeto de tipo UserBean, que basicamente tiene el id del usuario y el rol:

        FacesContext context = FacesContext.getCurrentInstance();
        ExternalContext extContext = context.getExternalContext();
        String url = "";
        if(isAdmin(user, password))
        {
            url = extContext.encodeActionURL(
                    context.getApplication().getViewHandler().getActionURL
                    (context, "/administration/adminPanel.jspx"));
            extContext.getSessionMap().put(USER_KEY, new UserBean(user, "admin"));
            extContext.redirect(url);
            return;

        }
        if(isSuperAdmin(user, password))
        {
            url = extContext.encodeActionURL(
                    context.getApplication().getViewHandler().getActionURL
                    (context, "/superadmin/superAdminPanel.jspx"));
            extContext.getSessionMap().put(USER_KEY, new UserBean(user, "superadmin"));
            extContext.redirect(url);
            return;
        }

        }

En el código mostrado se valida si las credenciales son válidas, si es así lo redirige a la sección de administración correspondiente, y finalmente pone en una variable de sesión el objeto de usuario.

Como tenemos 2 secciones que queremos validar los requests vamos a crear 2 filtros, uno para validar si es “admin” y otro para validar si es “super admin”, por lo tanto configuramos otro filtro en el web.xml:


     <filter>
        <filter-name>AuthSuperAdminFilter</filter-name>
        <filter-class>demo.security.AuthSuperAdminFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>AuthSuperAdminFilter</filter-name>
        <url-pattern>/superadmin/*</url-pattern>
    </filter-mapping>

Hasta ahora tenemos el mapeo de las url’s a los filtros que van a validar los request, ahora veamos el desarrollo de las clases que implementan el Filtro de los requests:

Filtro para Sección de Administradores

public class AuthAdminFilter implements Filter {

    private FilterConfig configuration;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.configuration = filterConfig;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (((HttpServletRequest) request).getSession().getAttribute(
                AuthBean.USER_KEY) == null) {
            ((HttpServletResponse) response).sendRedirect("../forbidden.jspx");
        } else {
            chain.doFilter(request, response);
        }

    }

    @Override
    public void destroy() {
        configuration = null;
    }

Filtro para Sección de Super Administradores

public class AuthSuperAdminFilter implements Filter {

    private FilterConfig configuration;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.configuration = filterConfig;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        if (((HttpServletRequest) request).getSession().getAttribute(
                AuthBean.USER_KEY) == null || !((UserBean)((HttpServletRequest) request).getSession().getAttribute(
                AuthBean.USER_KEY)).getRole().equals("superadmin") ) {
            ((HttpServletResponse) response).sendRedirect("../forbidden.jspx");
        } else {
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
        this.configuration = null;
    }

}

Ambas implementaciones son casi idénticas salvo por el caso de que en la de superadmin validamos que además de que exista en sesión la variable que nos indica que está autenticado, el usuario tenga el rol de “superadmin”, en caso que no cumpla la condición se lo redirige a la sección de acceso restringido.
Bueno, eso es todo en el post 😀 para ver el ejemplo pueden descargar el código fuente desde aquí, es un proyecto netbeans 6.9 con glassfish.

Saludos,
gish@c