Posts Tagged ‘lenguajes’

Xtext codea por vos

noviembre 12, 2013

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!