Xtext codea por vos

Buenas. El proyecto final de la carrera de Ing. en  Sistemas que estoy desarrollando es un entorno para enseñar programación orientada a objetos en el que puedo ver los objetos de manera gráfica y darles movimiento y un aspecto.

Es un prototipo todavía, pero con suerte y viento a favor en el mediano plazo va a estar completo,  y quién dice no lo podamos poner a prueba con estudiantes.

Está hecho con Xtext, un framework para desarrollar lenguajes e integrarlos fácilmente a eclipse.

Uno define una gramática, Xtext te genera los elementos para parsear y validar el lenguaje y a partir de ella se puede generar código en el lenguaje que prefiera e incluso construir un intérprete sobre la misma plataforma. HOOPE usa Xtensivamente este framework y otros chiches de la API de eclipse.

Xtext permite generar código java muy fácilmente e integrarlo con las APIs de uso común como JAX-WS o JPA.

En el laburo estoy desarrollando servicios web y tengo que admitir que no siempre es fácil la metodología wsdl-first. Sobre todo porque al que solicita los servicios web le cuesta adaptarse a los wsdl. Pero tampoco es cómodo programar de entrada y anotar con jax-ws. Hay mucho código – andamiaje y puede volverse Vogon Poetry.

Y Xtext andaba en mi cabeza…

Con una gramática muy sencilla:

grammar org.miguelius.ws.Generator with org.eclipse.xtext.xbase.Xbase

generate generator "https://lodemiguel.wordpress.com/ws/WsGenerator"

Service:
	('package' packageName=QualifiedName)?
	('targetNamespace' targetNamespace=STRING)?
	'webservice' name=ValidID '{' operations+=Operation+ '}';

Operation:
	name=ValidID '('
	(params+=FullJvmFormalParameter
	(',' params+=FullJvmFormalParameter)*)?
	')'
	'returns'
	'('
	(returned+=FullJvmFormalParameter
	(',' returned+=FullJvmFormalParameter)*)?
	')';

Como el lenguaje está basado en xbase / jvm, puedo usar un inferrer. El inferrer permite generar código con chequeo de tipos a partir del árbol sintáctico:

   	def dispatch void infer(Service element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
		val requests = new HashMap()
		val responses = new HashMap()
   		// Here you explain how your model is mapped to Java elements, by writing the actual translation code.

   				for (operation : element.operations) {

   						val req = operation.toClass(operation.name + 'Request')
   						acceptor.accept(req).initializeLater[
   							packageName=element.packageName
							for (param : operation.params) {
								members += toField(param.name, param.parameterType)
								val getter = toGetter(param.name, param.parameterType)

								val getterAnnotation=findAnnotation(typeof(javax.xml.bind.annotation.XmlElement),param)
								getterAnnotation.addStringValue("name", param.name)
								getter.annotations+=getterAnnotation

								members += getter
								members += toSetter(param.name, param.parameterType)
							}
   						]
   						requests.put(operation, req)

						req.annotations += findAnnotation(typeof(javax.xml.bind.annotation.XmlRootElement),operation)

   						val resp = operation.toClass(operation.name + 'Response')

						resp.annotations += findAnnotation(typeof(javax.xml.bind.annotation.XmlRootElement),operation)
   						acceptor.accept(resp).initializeLater[
   							packageName=element.packageName

							for (param : operation.returned) {
								members += toField(param.name, param.parameterType)
								val getter = toGetter(param.name, param.parameterType)

								val getterAnnotation=findAnnotation(typeof(javax.xml.bind.annotation.XmlElement),param)
								getterAnnotation.addStringValue("name", param.name)
								getter.annotations+=getterAnnotation

								members += getter
								members += toSetter(param.name, param.parameterType)
							}
   						]
   						responses.put(operation, resp)
				}
   		// An implementation for the initial hello world example could look like this:
   		val wsInterface = element.toInterface(element.name,[])
   		//wsInterface.annotations+=findAnnotation(typeof(javax.jws.WebService),element)
   		acceptor.accept(wsInterface)
   			.initializeLater([
   				packageName=element.packageName

   				val webService = findAnnotation(typeof(javax.jws.WebService),element)
   				webService.addStringValue("name", element.name + "Type")
   				annotations+=webService

   				val soapBinding = findAnnotation(typeof(javax.jws.soap.SOAPBinding),element)
   				soapBinding.addEnumValue("parameterStyle",
   					getTypeForName(javax.jws.soap.SOAPBinding.ParameterStyle, element),
   					'BARE'
   				)
   				annotations+=soapBinding
   				for (operation : element.operations) {

					val responseReference = responses.get(operation).fullName.getTypeForName(element)
					val requestReference = requests.get(operation).fullName.getTypeForName(element)

					val operMethod = operation.toMethod(operation.name, responseReference) [
						val parameter = toParameter(requests.get(operation).fullName.toFirstLower,requestReference)
						val webParam = findAnnotation(typeof(javax.jws.WebParam),operation)
							.addStringValue("name", requests.get(operation).fullName)
							.addStringValue("partName", requests.get(operation).fullName)
						parameter.annotations += webParam
						it.parameters+= parameter
					]
					operMethod.annotations+=findAnnotation(typeof(javax.jws.WebMethod),operation)

   					members += operMethod
   				}
   			])

   	}

Esto está en lenguaje xtend y no soy un especialista (dicho con más que pena que gloria). Admite sendos refactors, por ejemplo mejorar los dispatches del método infer. El pseudcódigo se muy sencillo:

  1. Generar una clase Request y Response por objeto con los parámetros de cada una con sus anotaciones JAXB
  2. Generar una interfaz para el Servicio y anotarla con JAX-WS
  3. Agregar las operaciones y anotarlas con JAX-WS

Esto me genera las clases java y la SEI. Heredo de la interfaz y tengo definido el servicio.

De esta manera, me puedo sentar con el cliente y definir un servicio de una manera comprensible para ambas partes y sin ahondar en detalles.

Supongamos que queremos hacer un servicio web para registrar posts que reciba un título y el contenido y me devuelva el ID del post y la URL.


webservice PostService {

RegistrarPost
(String Titulo, String Cuerpo)
returns
(Integer PostID, String URL)

BuscarPostPorID
(Integer PostID)
returns
(String Titulo, String Cuerpo)

}

Esto construye la interfaz PostService, con los métodos RegistrarPost y BuscarPostPorID. A su vez construye las clases RegistrarPostRequest y RegistrarPostResponse y otro par para BuscarPostPorID. Todo con sus anotaciones y listo para nuestra implementación de JAX-WS lo publique.

Con relativamente poco código tenemos un DSL para generar Web Services en java de manera declarativa y accesible a quién conoce el dominio.

Estoy trabajando en agregar tipos compuestos y listas.

Como sea ya tenemos la primer experiencia con Xtext en protoducción.

¡Hasta la próxima!

Etiquetas: , , , , , ,

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s


A %d blogueros les gusta esto: