Al invocar un servicio web (REST) desde una aplicación móvil (ioniccordova/phonegap), me aparecía el siguiente error:

XMLHttpRequest cannot load http://localhost:8080/equinox/catalogos/distritos. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin http://localhost:8100 is therefore not allowed access.

El servidor se está ejecutando mediante mvn jetty:run (localhost:8080) mientras que el cliente está ejecutandose mediante grunt serve (localhost:8100). Siendo dominios diferentes, el navegador bloquea la invocación del servicio por cuestiones de seguridad.

La solución es permitir el acceso CORS. El servicio web esta hecho con Spring, así que para permitir esto necesitamos crear y configurar un filtro.

SimpleCORSFilter.java

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

@Component
public class SimpleCORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {}

    public void destroy() {}

}

y para configurarlo

web.xml

...
<filter>
        <filter-name>SimpleCORSFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>simpleCorsFilter</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>SimpleCORSFilter</filter-name>
        <url-pattern>*</url-pattern>
    </filter-mapping>
...

applicationContext.xml

...
<bean class="mx.gob.tribunalqro.equinox.filters.SimpleCORSFilter" name="simpleCorsFilter"></bean>
...

Nota:
Esta es una implementación muy sencilla y que además agrega el encabezado a todas las peticiones, no sólo a los servicios web. Para ello, necesitamos colocar nuestros servicios en una url como api/v1/resource y mapear el filtro a esas url’s

Fuentes:

Anuncios

Para configurar un servidor de git local podemos seguir la guía oficial.
La guía nos muestra los pasos necesarios para acceder a un repositorio de git mediante ssh, que es la forma más común para acceder (leer y escribir) a un repositorio. Esto debido a la seguridad y eficiencia que ofrece el protocolo ssh. Puedes leer sobre los distintos protocolos (local, http[s], git, ssh) disponibles en git aquí.

Bueno, lo primero que necesitamos es tener un servidor de ssh

sudo apt-get install openssh-server

Luego tenemos que crear al usuario ‘git’. Yo especifiqué el directorio del usuario para que no me creara el directorio /home/git

sudo adduser --home /home/cirovladimir/Apps/git

nos va a pedir una contraseña y algunos datos, podemos dejar vacíos los demás campos.

Luego nos logeamos como el usuario git y creamos el directorio ‘.ssh/’

sudo su git
cd
mkdir .ssh

a continuación vamos a generar las llaves para nuestros usuarios

ssh keygen -t rsa -C 'usuario_1@gmail.com' -f usuario_1_rsa
ssh keygen -t rsa -C 'usuario_2@gmail.com' -f usuario_2_rsa

nos va a generar un par de llaves para cada usuario, una pública (extensión .pub) y una privada

Hay que agregar las llaves públicas al archivo .ssh/authorized_keys

cat usuario_1_rsa.pub >> .ssh/authorized_keys
cat usuario_2_rsa.pub >> .ssh/authorized_keys

Ahora vamos a iniciar un repositorio para nuestro proyecto

mkdir -p repos/proyecto.git
cd repos/proyecto.git
git --bare init

cerramos la sesión del usuario git

exit

si ya tenemos un repositorio local que queremos sincronizar con el servidor que acabamos de configurar, hacemos lo siguiente

cd miproyecto
git remote add origin git@localhost:repos/proyecto.git
git push origin master

en este caso el servidor es local, si lo configuras en otra máquina puedes sustituir localhost por el nombre de la máquina o su ip.

Si al ejecutar el ‘push’ te arroja un error parecido al siguiente

Error: Agent admitted failure to sign

lo puedes resolver con el comando

ssh-add /ruta/usuario1

y te va a pedir el passphrase que utilizaste cuando generaste la llave.

Fuentes:
http://git-scm.com/book/en/Git-on-the-Server-Setting-Up-the-Server
http://git-scm.com/book/ch4-1.html
https://help.github.com/articles/generating-ssh-keys
https://help.github.com/articles/error-agent-admitted-failure-to-sign

Hace poco escribí sobre como buscar/filtrar recursos en el servidor, para ello utilizamos un objeto Criteria -del mismo tipo que el recurso- para crear una consulta JDOQL que nos permitiera obtener los resultados.

En este artículo mostraré una forma de utilizar la clase AdvancedCriteria de SmartGwt para realizar una consulta más compleja. Al igual que la vez anterior, vamos a utilizar el framework Apache CXF para nuestros servicios REST, y Jackson para la serialización y deserialización de los objetos en formato JSON. También utilizamos Datanucleus para la persistencia de datos.

Me base en este artículo que explica cómo integrar SmartGwt con ASP.Net.

Lo primero que vamos a necesitar es una clase AdvancedCriteria del lado del servidor


public class AdvancedCriteria {

static ObjectMapper om;

{

om=new ObjectMapper();

om.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);

}

public static class JDOQLData{

public String filter="";

public Map<String,Object> params=new HashMap<String, Object>();

public Class<?> type;

&nbsp;

public JDOQLData(Class<?> type) {

this.type=type;

}

}

&nbsp;

String _constructor;

Integer _startRow;

Integer _endRow;

String fieldName;

String operator;

Object value;

AdvancedCriteria[] criteria;

&nbsp;

public AdvancedCriteria() {

}

&nbsp;

public static AdvancedCriteria fromString(String value){

try {

return om.readValue(value, AdvancedCriteria.class);

} catch (Exception e) {

throw new RuntimeException(e);

}

}

&nbsp;

// Getter's y Setter's ...

&nbsp;

public void toJDOQL(JDOQLData data){

if(this.criteria==null){

if(this.operator.equals(OperatorId.EQUALS.getValue())){

data.filter+=this.fieldName+" == ";

}else if(this.operator.equals(OperatorId.NOT_EQUAL.getValue())){

data.filter+=this.fieldName+" != ";

}else if(this.operator.equals(OperatorId.LESS_THAN.getValue())){

data.filter+=this.fieldName+" < ";

}else if(this.operator.equals(OperatorId.GREATER_THAN.getValue())){

data.filter+=this.fieldName+" > ";

}else if(this.operator.equals(OperatorId.LESS_OR_EQUAL.getValue())){

data.filter+=this.fieldName+" <= ";

}else if(this.operator.equals(OperatorId.GREATER_OR_EQUAL.getValue())){

data.filter+=this.fieldName+" >= ";

}

data.params.put(String.valueOf(data.params.size()+1), fixType(getPropertyType(data.type, this.fieldName), this.value) );

data.filter+=":"+data.params.size();

}else{

data.filter+="(";

for(int index=0;index<this.criteria.length;index++){

this.criteria[index].toJDOQL(data);

if(index+1<this.criteria.length){

if(this.operator.equals(OperatorId.AND.getValue())){

data.filter+=" && ";

}else if(this.operator.equals(OperatorId.OR.getValue())){

data.filter+=" || ";

}else if(this.operator.equals(OperatorId.NOT.getValue())){

data.filter+=" ! ";

}

}

}

data.filter+=")";

}

}

&nbsp;

private Object fixType(Class<?> type, Object value) {

return om.convertValue(value, type);

}

&nbsp;

private Class<?> getPropertyType(Class<?> clazz,String property){

try{

LinkedList<String> properties=new LinkedList<String>();

properties.addAll(Arrays.asList(property.split("\\.")));

Field field = null;

while(!properties.isEmpty()){

field = FieldUtils.getField(clazz,properties.removeFirst(),true);

clazz=field.getType();

}

return field.getType();

}catch(Exception e){

throw new RuntimeException(e);

}

}

}

Lo interesante de esta clase es que tiene un método estático fromString(String) que será utilizado por Apache CXF para crear un objeto de esta clase a partir de una cadena. También tiene un método recursivo toJDOQL(JDOQLData) que nos regresa un filtro y el listado de parámetros en un objeto JDOQLData para poder realizar la consulta en datanucleus. El método fixType y getPropertyType son para utilizar el tipo correcto -de acuerdo a la clase- en nuestro listado de parámetros.

Nuestro servicio REST quedaría de la siguiente forma


@GET

public String retrieve(@QueryParam("")Libro criteria,@QueryParam("")AdvancedCriteria advancedCriteria){

try {

List<Libro> libros = null;

if(advancedCriteria.get_constructor()!=null){

libros = Libro.getByAdvancedCriteria(advancedCriteria);

}else{

libros = Libro.getByCriteria(criteria);

}

Map<String, Object> response = new HashMap<String, Object>();

Map<String, Object> body = new HashMap<String, Object>();

body.put("status", 0);

body.put("data", libros);

response.put("response", body);

return json.writeValueAsString(response);

} catch (Exception e) {

log.error("", e);

}

return "{\"response\":{\"status\":-1, \"data\":\"Hubo un problema al buscar los Libros\"}}";

}

El framework Apache CXF se encarga de crear los objetos criteria(Libro) y advancedCriteria. Para saber cuál fue el que recibimos basta con verificar la propiedad _constructor, si es diferente de null sabemos que hemos recibido un AdvancedCriteria.

Finalmente realizamos la consulta en nuestra clase Libro


public static List<Libro> getByAdvancedCriteria(AdvancedCriteria criteria) {

List<Libro> libros;

PersistenceManager pm = PMF.get().getPersistenceManager();

Transaction tx = pm.currentTransaction();

try{

tx.begin();

JDOQLData data=new JDOQLData(Libro.class);

criteria.toJDOQL(data);

Query query = pm.newQuery(Libro.class,data.filter);

libros=(List<Libro>) query.executeWithMap(data.params);

tx.commit();

}finally{

if(tx.isActive()){

tx.rollback();

}

pm.close();

}

return libros;

}

En el cliente, utilizamos la clase AdvancedCriteria de SmartGwt para realizar la consulta


&nbsp;

AdvancedCriteria criteria=new AdvancedCriteria(OperatorId.OR,

new AdvancedCriteria[]{

new AdvancedCriteria("isbn",OperatorId.EQUALS, txtISBN.getValueAsString()),

new AdvancedCriteria(OperatorId.AND, new AdvancedCriteria[]{

new AdvancedCriteria("autor.nombre", OperatorId.EQUALS, "Gabriel García Márquez"),

new AdvancedCriteria("titulo", OperatorId.EQUALS, "Crónica de una muerte anunciada")

})

});

librosListGrid.fetchData(criteria);

&nbsp;

Finalmente al realizar el ‘fetch’ se realiza una petición a nuestro servicio con los siguientes párametros. Los cuales serán transformados a un objeto de nuestra clase AdvancedCriteria


&nbsp;

__gwt_ObjectId:793

operator:or

criteria:{"__gwt_ObjectId":788,"fieldName":"isbn","operator":"equals","value":"978-1400034956"}

criteria:{"__gwt_ObjectId":791,"operator":"or","criteria":[{"__gwt_ObjectId":789,"fieldName":"autor.nombre","operator":"equals","value":"Gabriel García Márquez"},{"__gwt_ObjectId":790,"fieldName":"titulo","operator":"equals","value":"Crónica de una muerte anunciada"}]}

_constructor:AdvancedCriteria

_operationType:fetch

_startRow:0

_endRow:75

_textMatchStyle:exact

_componentId:isc_ListGrid_0

_dataSource:isc_LibrosModule_3_0

isc_metaDataPrefix:_

isc_dataFormat:json

&nbsp;

Fuentes:
http://wiki.smartclient.com/display/Main/Integrating+with+ASP.Net+MVC
http://wiki.smartclient.com/display/Main/5.+Adding+support+for+AdvancedCriteria
http://www.objectdb.com/database/jdo/manual/chapter7#Query_Parameters
http://www.objectdb.com//database/jdo/manual/chapter7
http://stackoverflow.com/questions/935762/how-to-dynamically-build-jdo-queries-on-multiple-parameters

Cuando inicias algún proyecto, muy probablemente lo hayas creado localmente (en tu computadora). Si después quieres moverlo a un servidor para compartirlo y que los demás puedan contribuir a tu proyecto, necesitas mover tu repositorio local a un servidor compartido.

Para hacer esto, ejecuta el siguiente comando para crear un clon de tu repositorio

git clone –bare /path/to/repo /path/to/bare/repo.git

Luego copia esta carpeta al servidor -mediante usb, ftp, ssh o lo que puedas-.

Puedes comprimirla antes de copiarla con

tar -zcvf /path/to/bare/repo.git repo.tar.gz

y descopmrimirla en el servidor con

tar -zxvf repo.tar.gz

Ahora para poder sincronizar con este nuevo servidor, cambia el origen de tu directorio de trabajo (working tree)

git remote rm origin

git remote add origin ssh://user@dev.foo.com/path/to/bare/repo.git

suponiendo que tienes acceso al servidor mediante ssh y luego envía tus cambios al nuevo servidor

git push

Fuentes:
http://stackoverflow.com/questions/4860166/how-to-synchronize-two-git-repositories
http://kovshenin.com/2011/transfer-your-existing-git-repository-to-github/
http://stackoverflow.com/questions/1402390/git-push-clone-to-new-server/1402783#1402783
http://www.wetware.co.nz/2009/07/pull-a-git-branch-from-remote/ 
http://www.kernel.org/pub/software/scm/git/docs/git-clone.html#_git_urls_a_id_urls_a 

GWT cuenta con varias clases para el manejo y manipulación de datos en formato json.

Supongamos que obtenemos la siguiente respuesta del servidor

{“id”:”766″,”nombre”:”cirovladimir”}

Para obtener los valores, lo podemos hacer de la siguiente manera

JSONValue value=JSONParser.parseStrict(json);
String id=value.isObject().get(“id”).isString().stringValue();
String nombre=value.isObject().get(“nombre”).isString().stringValue();

Pero, ¿qué pasa si uno de los valores es nulos? ¿cómo podemos hacer una validación? Supongamos ahora que el servidor nos regresa la siguiente cadena

{“id”:”766″,”nombre”:null}

value.isObject().get(“nombre”)==null //debería ser true, sin embargo, regresa false
value.isObject().get(“nombre”).isNull()==JSONNull.getInstance() //regresa true, esta es la forma correcta

Uno de mis dolores de cabeza con Eclipse, es la resolucion de las dependencias. En Visual Studio cuando agregamos una dependencia a una libreria o proyecto, sus respectivas dll’s son copiadas al directorio de salida -donde queda el ejecutable de la aplicacion o al directorio bin en el caso de aplicaciones web- al momento de compilar. En Eclipse esto no pasa , en realidad, no se como ni si sea posible. Para resolver las dependencias, Eclipse usa el classpath y no tiene problemas a la hora de ejecutar o depurar la aplicacion desde el IDE. Pero cuando intentas copiar (deploy) la aplicacion a un servidor vienen los problemas ya que no se encuentran los JAR’s dependientes en el direcotrio WEB-INF/lib. Para resolverlo basta con exportar cada uno de los JAR’s de los proyectos dependientes en esa carpeta de forma manual. Este procedimiento se vuelve muy tedioso conforme aumenta el numero de dependencias o actualizas los proyectos dependientes frecuentemente.

Como la mayoria de proyectos que genero son de GWT, he creado un script de Ant que copia el directorio war a una carpeta llamada “deployme” y luego resuelve las dependencias recusrivamente generando y copiando los JAR’s necesarios a la carpeta “deployme/WEB-INF/lib”. Una vez hecho esto, con las dependencias resueltas, podemos copiar la carpeta “deployme” al servidor 😀
Para ejecutar el script necesitas primero instalar ant4eclipse y ant-contrib, asi como instalar el soporte de scripts de javascript.
La seccion con las etiquetas de “jdtClassPathLibrary” son para que ant4eclipse pueda resolver las librerias de usuario, si no tuvieras ninguna, solo necesitas indicarle como resolver la de “com.google.gwt.eclipse.core.GWT_CONTAINER”

<?xml version="1.0" encoding="UTF-8"?>
<project name="gwt-dependency-build" default="build" basedir="." 
	xmlns:a4e="antlib:org.ant4eclipse" 
	xmlns:ac="antlib:net.sf.antcontrib">

	<property name="web-project" value="MI_PROYECTO_GWT" />
	<property name="workspace" value="${basedir}/.." />

	<a4e:jdtClassPathLibrary name="org.eclipse.jdt.USER_LIBRARY/SmartGwt">
		<fileset dir="/home/vladimir/Projects/Librerias/smartgwt-2.2/" includes="*.jar" />
	</a4e:jdtClassPathLibrary>
	<a4e:jdtClassPathLibrary name="com.google.gwt.eclipse.core.GWT_CONTAINER">
		<fileset dir="/opt/eclipse-helios/plugins/com.google.gwt.eclipse.sdkbundle.2.0.4_2.0.4.v201006301309/gwt-2.0.4/" includes="gwt-user.jar, gwt-dev.jar, gwt-servlet.jar" />
	</a4e:jdtClassPathLibrary>
	<a4e:jdtClassPathLibrary name="org.eclipse.jdt.USER_LIBRARY/GwtCKEditor">
	  <fileset dir="/home/vladimir/Projects/Librerias/ckeditor-gwt" includes="gwt-ckeditor.jar"/>
	</a4e:jdtClassPathLibrary >
	<a4e:jdtClassPathLibrary name="org.eclipse.jdt.USER_LIBRARY/Gson">
	  <fileset dir="/home/vladimir/Projects/Librerias/google-gson-1.4" includes="gson-1.4.jar" />
	</a4e:jdtClassPathLibrary >
	
	<ac:var name="dependencyList" value="" />

	<target name="build">
		<list-projects project="${web-project}" />
		<script language="javascript">
		  <![CDATA[
			importPackage(java.lang, java.util);
			
		    var jars = project.getProperty("dependencyList").split(",");
			var tmp=new ArrayList();
			for(i=0;jars[i]!=null;i++){
				if(!tmp.contains(jars[i])){
					tmp.add(jars[i]);
				}
			}
			var dependencies=tmp.toString();
		    project.setProperty("dependencies", dependencies.substring(1,dependencies.length()-1));
		  ]]>
		  </script> 
		<echo>Dependencies found: ${dependencies}</echo>
		<echo>Copying "war" directory to "deployme"</echo>
		<copy todir="deployme">
			<fileset dir="war" />
		</copy>
		<a4e:getJdtOutputPath workspaceDirectory="${workspace}" projectName="${web-project}" property="web-project-classes-dir" />
		<ac:for list="${dependencies}" param="dependency" trim="true">
			<sequential>
				<a4e:getProjectDirectory workspaceDirectory="${workspace}" projectName="@{dependency}" property="dependency-dir" />
				<a4e:getJdtOutputPath workspaceDirectory="${workspace}" projectName="@{dependency}" property="dependency-classes-dir" />
				<ac:if>
					<not>
						<equals arg1="@{dependency}" arg2="${web-project}" />
					</not>
					<ac:then>
						<echo>Compiling ${dependency-classes-dir} into deployme/WEB-INF/lib/@{dependency}.jar</echo>
						<jar destfile="deployme/WEB-INF/lib/@{dependency}.jar" basedir="${dependency-classes-dir}" />
					</ac:then>
				</ac:if>
				
			</sequential>
		</ac:for>
	</target>

	<macrodef name="list-projects">
		<attribute name="project" />
		<attribute name="prefix" default="recurse" />
		<sequential>
			<a4e:getUsedProjects property="@{prefix}.usedProjects" workspaceDirectory="${basedir}/.." projectName="@{project}" separator="," />
			
			<ac:for param="usedProject" list="${@{prefix}.usedProjects}" delimiter=",">
				<sequential>
					<list-projects project="@@{usedProject}" />
				</sequential>
			</ac:for>
			<ac:if>
				<equals arg1="" arg2="${dependencyList}"/>
				<then><ac:var name="dependencyList" value="@{project}" /></then>
				<else><ac:var name="dependencyList" value="${dependencyList},@{project}" /></else>
			</ac:if>
		</sequential>
	</macrodef>
</project>

Hace poco integré el CKEditor en un proyecto. A la hora de enviar el código HTML al servidor tuve algunos problemas con los caracteres especiales.
Y es que hay que asegurarnos de que nuestro JSON sea compatible con su definición; afortunadamente el metodo toString de la clase JSONObject se encarga de “escapar” los caracteres no permitidos en JSON (” \ / \b \f \n \r \t \uXXXX) y nos regresa una cadena válida.

String jsonAcuerdo=new JSONObject(acuerdo).toString();

Finalmente, tenemos que enviar los datos dentro de una variable codificada a HTML de la siguiente forma

RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, URL.encode(jsonUrl));
rb.setHeader("Content-type", "application/x-www-form-urlencoded");
StringBuffer postData = new StringBuffer();
postData.append("acuerdo=").append(URL.encodeComponent(jsonAcuerdo));
rb.sendRequest(postData.toString(),new RequestCallback(){
//TODO on response received
});

aquí es importante utilizar el método “encodeComponent” de la clase URL ya que esté si codifica los caracteres delimitadores de componentes en una URL (; / ? : & = + $ , #) a diferencia del método “encode” que no lo hace. Estos caracteres seguramente estarán presentes en el código HTML que deseemos enviar, por ejemplo en &quot; y ni hablar de los otros. Esta variable será decodificada “automaticamente” cuando la leamos en el servidor mediante el método getParameter de la interfaz ServletRequest