Selector de Idioma

Puedes cambiar el idioma preferido aquí.

Spring MVC

He trabajado con Spring MVC 3.0 y Spring MVC 4.0 de Java

El sitio actualmente, está construído empleando Spring, empleando algunas funciones básicas.

Configuración de MVC.xml
	
	<mvc:resources location="/img/" mapping="/img/**" />	
	<mvc:resources location="/css/" mapping="/css/**" />
	<mvc:resources location="/js/" mapping="/js/**" />
	
	<bean id="viewResolver" class="org.springframework.web.servlet.view.tiles3.TilesViewResolver"/>
 
    <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
        <property name="definitions">
            <list>
                <value>/WEB-INF/tiles.xml</value>
            </list>
        </property>
    </bean>
	
	<bean id="globalController" class="com.genexis.ccorona.HelloAppEngine" />
	

Para optimizar la carga de la aplicación por el motor de App Engine, no activo el escaneo automático de controllers, sino que registro de forma manual mi único controller, además, especifico qué archivos serán empleados como estáticos e incluyo Tiles como resolvedor de vistas.

Controller Principal
	
	@Controller
	@RequestMapping(value="/")
	public class HelloAppEngine  {
	
	  private static final Logger logger = Logger.getLogger(HelloAppEngine.class.getName());
		
	  @RequestMapping(value="**", method=RequestMethod.GET, produces="text/html")
	  public ModelAndView viewPage(HttpServletRequest req) throws IOException {
		  String request = req.getRequestURI();
		  request = request.length() > 0 ? request.substring(1) : request;
		  logger.info(request);
		  if (request.trim().isEmpty()) {
			  request = "index";
		  } 
		  ModelAndView mav = new ModelAndView(request.replaceAll("\\/", "."));
		  return mav;
	  }
	  	  
	}
	

De ésta forma, con un único control, de un conjunto muy breve de líneas, decido qué vista de las definidas dentro de Tiles se presentará, dependiendo del path de la solicitud. Lo que me permite ampliar rápidamente el sitio.

Tiles

He trabajado con Tiles 2.0 y Tiles 3.0 integrándolo en proyectos con Spring MVC

De hecho, éste sitio está construído empleando tiles, con una configuración que permite la creación de nuevas páginas de forma ágil, donde cada sección emplea un layout con funciones paralax de materialize.

Estructura de Tiles Base
	
	<definition name='base' template='/WEB-INF/templates/pages/layout.jsp' >
   	    <put-attribute name='title' value='Carlos Corona' cascade='true'/>
   	    <put-attribute name='sidemenu' value='/WEB-INF/templates/components/sidemenu.jsp' cascade='true'/>   	    
   	</definition>
   	
Estructura de la pantalla de Index:
	
	<definition name="index" extends="base">
   	    <put-attribute name="banner" value="/WEB-INF/templates/components/banner.jsp" cascade="true" />
   	    <put-list-attribute name="sections" cascade="true" >
   	        <add-attribute value="/WEB-INF/templates/components/index.jsp" />
   	    </put-list-attribute>
   	    <put-list-attribute name="images" cascade="true">
   	        <add-attribute value="ccorona.png" />
   	    </put-list-attribute>
   	</definition>
   	
Definición de la sección de trabajo (ejemplo)
	
	<definition name="work" extends="base">
   	    <put-list-attribute name="sections" cascade="true" >
   	        <add-attribute value="/WEB-INF/templates/components/overcome.jsp" />
   	        <add-attribute value="/WEB-INF/templates/components/orcius.jsp" />
   	        <add-attribute value="/WEB-INF/templates/components/inaoe.jsp" />
   	    </put-list-attribute>
   	    <put-list-attribute name="titles" cascade="true">
   	        <add-attribute value="BIOvercome SAPI de C.V. (Overcome)" />
   	        <add-attribute value="Express Web Consortium (Orcius)" />
   	        <add-attribute value="Instituto Nacional de Astrofísic Óptica y Electrónica (INAOE)" />
   	    </put-list-attribute>
   	</definition>
   	
Layout del sistema

El archivo layout.jsp define la estructura general de la plantilla.

	
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="t" uri="http://tiles.apache.org/tags-tiles" %>
<%@ taglib prefix="te" uri="http://tiles.apache.org/tags-tiles-extras" %>
<te:useAttribute id="scripts" name="scripts" classname="java.util.List" ignore="true" />
<te:useAttribute id="styles" name="styles" classname="java.util.List" ignore="true" />
<te:useAttribute id="sections" name="sections" classname="java.util.List" ignore="true" />
<te:useAttribute id="titles" name="titles" classname="java.util.List" ignore="true" />
<te:useAttribute id="subtitles" name="subtitles" classname="java.util.List" ignore="true" />
<te:useAttribute id="images" name="images" classname="java.util.List" ignore="true" />
<!DOCTYPE html>
  <html>
    <head>
      <title><t:getAsString name="title" ignore="true" /></title>
      
      <link href="/css/basic.css" rel="stylesheet" type="text/css"/>
      
      <!-- Estilos para Materialize -->
      <link href='//fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css' />
	  <link href='//fonts.googleapis.com/css?family=Roboto+Condensed' rel='stylesheet' type='text/css' />
	  <link href="//fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css" />
      <link href="//fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.7/css/materialize.min.css">
      <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
      
      <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>      
    </head>
    <body>
    
    <!-- Componente fijo de menú lateral -->
    <t:insertDefinition name="component.sidemenu"/>
  	
  	<!-- Componente fijo de barra superior de navegación -->
  	<t:insertDefinition name="component.navbar" />
  	
  	<!-- Opcionalmente, puedo incluir un 'banner' en cada sección -->
  	<t:insertAttribute name="banner" ignore="true" />
     
    <!-- Incluyo los distintos bloques (secciones) de la página que se muestra, aplicando un separador con efecto Parallax -->  
    <c:forEach items="" var="section" varStatus="i">
      	<div class="parallax-container valign-wrapper">
    		<div class="section no-pad-bot">
				<div class="container">
					<div class="row center">
						<c:if test="false">
							<h3 class="header center white-text text-lighten-2"><c:out value="" /></h3>
						</c:if>
						<c:if test="false">
							<h4 class="header col s12 light"><c:out value="" /></h4>
						</c:if>
						<c:if test="false">
							<img src="/img/" class="circle responsive-img" />
						</c:if>
					</div>
				</div>
      		</div>
      		<div class="parallax">
      			<img src="/img/background_.jpg" />
      		</div>
      	</div>
      	<div class="container">
    		<div class="section">
      			<t:insertAttribute value="" flush="true" ignore="true" />
      		</div>
      	</div>
      </c:forEach>
      
      <!-- Configuración final de Materialize para inicializar la página -->
      <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.7/js/materialize.min.js"></script>
      <script>
      (function($){
    	  $(function(){

    	    $('.button-collapse').sideNav();
    	    $('.parallax').parallax();

    	  }); // end of document ready
    	})(jQuery); // end of jQuery name space
      </script>
    </body>
  </html>
  

De ésta forma, puedo incluir nuevas secciones al sitio únicamente creando nuevas definiciones en tiles, cada una de las cuales puede referenciar o incluir funciones distintas (como el índex), pero manteniendo la misma estructura básica.

AppEngine

Comencé a trabajar con Google App Engine mientras me encontraba laborando en Orcius, donde después de evaluar las características de los distintos servicios de nube de Microsoft, Amazon y Google, me decanté por éste último debido al ecosistema que ofrecía.

Desde entonces me he actualizado en el uso y manejo de los servicios que ofrece, tales como la construcción de máquinas virtuales, integraciones con API's de mapas y geolocalización, endpoints para aplicaciones móviles, etc. Al tiempo que me he destacado en el diseño y construcción de Bases de Datos Orientadas a Objetos.

Para trabajar con Google Cloud Datastore, he creado mi propio framework a partir de Objectify 5.0, de manera que pueda construir y consumir sus servicios de una manera más rápida y efectiva dentro del código.

¿Objectify?

Una librería de Google para trabajar con Bases de Datos No Relacionales, que actualmente se encuentra en su versión 5. Sin embargo, al día de hoy, el trabajo que se debe realizar para integrarla dentro de un proyecto genera grandes cantidades de código 'ruidoso' dentro de la lógica de trabajo. Motivo por el cual he creado mi propio framework

Generalizando Objectify

Primero y antes que nada, Objectify no maneja tipos durante la compilación, por lo que es fácil confundirse sin darse cuenta, motivo por el cual lo he generalizado la clase y he sobre-escrito los métodos más habituales para tenerlos todos a la mano con el tipo de dato correcto, los métodos más importantes de la clase son los más elementales: add, delete, get, list, listByProperty y se encuentran muchas veces sobre-cargados

	
	public class ObjectifyDao<T> {

	protected Class<T> clazz;

	public ObjectifyDao(Class<T> clazz) {
		this.clazz = clazz;
	}

	public Key<T> add(T entity) {
		Result<Key<T>> result = ObjectifyService.ofy().save().entity(entity);
		return result.now();
	}
	
	public Map<Key<T>, T> add(List<T> counts) {
		Result<Map<Key<T>, T>> result = ObjectifyService.ofy().save().entities(counts);  
		return result.now();
	}

	public void delete(T entity) {
		ObjectifyService.ofy().delete().entity(entity);			
	}
	
	public void delete(Collection<Key<T>> entities) {
		ObjectifyService.ofy().delete().entities(entities);
	}

	public void delete(Key<T> entityKey) {
		ObjectifyService.ofy().delete().key(entityKey);		
	}

	public T get(Key<T> key) throws EntityNotFoundException {
		Result<T> result = ObjectifyService.ofy().load().key(key);
		return result.now();
	}
	
	public Map<Key<T>, T> get(Collection<Key<T>> keys) {
		return ObjectifyService.ofy().load().keys(keys);
	}

	public List<T> getAllList(Key<?> parentKey) {
		Query<T> q = ObjectifyService.ofy().load().type(clazz).ancestor(parentKey);
		return q.list();
	}
	
	public List<T> getAllList(String order, int offset, int limit) {
		Query<T> q = ObjectifyService.ofy().load().type(clazz);
		if (order != null && !order.trim().isEmpty()){
			q = q.order(order);
		}
		q = q.offset(offset).limit(limit);
		return q.list();
	}
	
	...
	

Por otro lado, he creado una interface que cualquier manejador de objetos puede implementar para trabajar con entidades del datastore

	
	public interface EntityService<T>{
		public void setObjectifyDao(ObjectifyDao<T> ofyDao);
		public T load(Key<T> key);
		public T load(String key);
		public Map<Key<T>, T> load(Collection<Key<T>> keys);
		public Key<T> save(T entity);
		public Map<Key<T>, T> save(List<T> entity);
		public void delete(Key<T> key);
		public void deleteEntities(List<Keyable<T>> keys);
		public void delete(List<Key<T>> keys);
		public List<T> list(Key<?> parent);
		public List<T> list(Key<?> parent, String order, int offset, int limit);
		public List<T> list(Key<?> parent, int offset, int limit);
		public List<T> list();
		public List<T> list(int offset, int limit);
		public List<T> list(String order, int offset, int limit);
		public boolean isHardRequired();
		public void setHardRequired(boolean hardRequired);	
	}
	

Siendo que muchas entidades deberían implementar ésta interface para procedimientos equivalentes, creé una clase de la que pueden heredar y que realiza muchos de los procesos comunes:

	
	public abstract class EntityServiceImpl<T> implements EntityService<T> {
	
		protected ObjectifyDao<T> dao;
		protected boolean hardRequired = false;
		
		public boolean isHardRequired() { return hardRequired; };
		public void setHardRequired(boolean hardRequired) {
			this.hardRequired = hardRequired;
		}
		
		public void setObjectifyDao(ObjectifyDao<T> objectifyDao) {
			this.dao = objectifyDao;
		}
		
		@Override
		public T load(Key<T> key) {
			try {
				return isHardRequired() ? dao.getHard(key) : dao.get(key);
			} catch (Exception e) {
				return null;
			}
		}
	
		@Override
		public T load(String keyString) {
			if (keyString != null && !keyString.trim().isEmpty()){
				Key<T> key = Key.create(keyString);
				return load(key);
			}
			return null;
		}
		
		@Override
		public Map<Key<T>, T> load(Collection<Key<T>> keys) {
			return dao.get(keys);
		}
	
		@Override
		public Key<T> save(T entity) {
			return isHardRequired() ? dao.addHard(entity) : dao.add(entity);
		}
		
		@Override
		public Map<Key<T>, T> save(List<T> entity) {
			return dao.add(entity);
		}
		
		@Override
		public void delete(Key<T> key) {
			dao.delete(key);
		}
		
		public void deleteEntities(List<Keyable<T>> objects) {
			List<Key<T>> keys = new ArrayList<>(objects.size());
			for (Keyable<T> o : objects) {
				keys.add(o.getKey());
			}
			dao.delete(keys);
		}
		
		public void delete(List<Key<T>> keys) {
			dao.delete(keys);
		}
		
		@Override
		public List<T> list(Key<?> parentKey) {
			if (parentKey != null) {
				return dao.getByParentId(parentKey);
			} else {
				return dao.getAllList();
			}
		}
		
		@Override
		public List<T> list(Key<?> parentKey, int offset, int limit) {
			return list(parentKey, null, offset, limit);
		}
		
		@Override
		public List<T> list(Key<?> parentKey, String order, int offset, int limit) {
			if (parentKey != null) {
				return dao.getList(parentKey, order, offset, limit);
			} else {
				return dao.getAllList(order, offset, limit);
			}
		}
		
		@Override
		public List<T> list() {
			return dao.getAllList();
		}
		
		@Override
		public List<T> list(int offset, int limit) {
			return list("", offset, limit);
		}
		
		@Override
		public List<T> list(String order, int offset, int limit) {
			return dao.getAllList(order, offset, limit);
		}	
	}
	

Éstas clases e interfaces se encuentran definidas dentro de mis propias librerías, por lo que en cualquier proyecto que desee implementarlas, sólo debe crear la clase de entidad y definir los servicios con un mínimo de líneas de código

EntityClass, EntityService y EntityServiceImpl

Partiendo de una clase EntidadEjemplo, se puede construir un proceso muy simple para acceder al Datastore

	
	@Entity
	public class EntidadEjemplo {
		@Id private Long id;
		private String dato;
		@Index private Date fecha;
		
		public Long getId() { return id; }
		public void setId(Long id) { this.id = id; }
		
		public String getDato() { return dato; }
		public void setDato(String dato) { this.dato = dato; }
		
		public Date getFecha() { return fecha; }
		public void setFecha(Date fecha) { this.fecha = fecha; }
	}
	

Creo una interfaz que me pueda permitir personalizar posteriormente los comportamientos, de momento, sólo heredo de EntityService

	
	public interface EntidadEjemploService extends EntityService<EntidadEjemplo> {}
	

Creo una clase que implemente la interfaz que acabo de crear, heredando de EntityServiceImpl

	
	public class EntidadEjemploServiceImpl implements EntidadEjemploService extends EntityServiceImpl<EntidadEjemplo> {}
	

Con ésto, sin más líneas de código, puedo acceder a los métodos básicos de Objectify que he sobrecargado, de manera tipificada en cualquier bloque que requiera

	
	public class FuncionesEjemplo {
		private EntidadEjemploService ees = ServiceFactory.getInstance().getEntidadEjemploService();
		
		public String registrarEjemplo(Date hoy, String dato) {
			EntidadEjemplo ee = ees.getByProperty("fecha", hoy);
			if (ee == null) {
				ee = new EntidadEjemplo();
			}
			ee.setDato(dato);
			Key<EntidadEjemplo> key = ees.save(dato);
			return key.getString();
		}
	}
	
Configuraciones de Spring

Como parte de las configuraciones adicionales que requiere Objectify, es que se inicialice con cada una de las entidades que deseemos leer / escribir, por lo que cree una clase OfyService que mantiene un singleton inicializado desde que se arranca la ejecución de la app con un objeto de Objectify y registro las clases con las que trabajaré

Finalmente, como en el ejemplo, normalmente mantengo singleton ServiceFactory que me permita acceder a los servicios donde quiera que esté en el código del proyecto

	
	...
	<bean id="OfyService" class="com.genexis.dao.OfyService" factory-method="getInstance">
		<constructor-arg name="classes">
			<array value-type="java.lang.Class">
				<value>com.genexis.ccorona.entity.EntidadEjemplo</value>
				...
			</array>
		</constructor-arg>
	</bean>
	...
	
	<bean id="entidadEjemploDao" class="com.genexis.dao.ObjectifyDao">
	    <constructor-arg name="clazz" value="com.genexis.ccorona.entity.EntidadEjemplo" />
	</bean>
	<bean id="entidadEjemploService" class="com.genexis.ccorona.service.impl.EntidadEjemploServiceImpl">
	    <property name="objectifyDao" ref="entidadEjemploDao" />
	</bean>
	
	<bean id="ServiceFactory" class="com.genexis.ServiceFactory" factory-method="getInstance">
		<property name="entidadEjemploService" ref="entidadEjemploService" />
		...
	</bean>
	

Maven

A partir de éste proyecto, estoy comenzando a trabajar con Maven 3.0, creando el proyecto a partir del Archetipe de Google Cloud, aunque planeo en realidad, definir mi propio arquetipo, incluyendo mis librerías y funciones como parte del proyecto, con lo que cada nueva integración requiera únicamente la construcción de los módulos de lógica de negocio.