Metricas-DSL

De wikiPLII
Saltar a: navegación, buscar
Metricas-DSL
Métricas DSL
Miembros Alvaro Galán Piñero
Alejandro Nogueiro Mariscal
Cristina Sánchez García
Curso Académico 2012/2013
Tipo de Contribución DSL
Keywords Eclipse, Xtext, Xtend, Pentaho Data Integrator, Java, Redmine

Contenido

Resumen

Desarrollo de un DSL textual que permita definir métricas relativas a proyectos software y cómo se calculan a partir de la extracción de datos de sistemas externos (como los sistemas de gestión de proyectos, tipo Redmine).

Se implementará un editor “rico” con las características típicas de cualquier lenguaje de programación: autocompletado, coloreado de sintaxis, etc. Este editor permitirá transformar los modelos de entrada en procesos ejecutables directamente por el computador.

Este lenguaje permitirá escribir "fórmulas" cuyo lado izquierdo del "=" sea el nombre de una métrica y en el lado derecho habrá una expresión conteniendo operadores aritméticos, lógicos y de comparación sobre datos de consultas realizados sobre diferentes sistemas de soporte al proceso software.

Finalmente, para cada métrica, se creará un fichero xml en el cual podremos observar el nombre de la misma y el valor resultante.

Motivación

La motivación de este proyecto es poder realizar consultas sobre proyectos, usuarios... que se encuentran trabajando en un sistema de gestión de proyectos para a partir de ahí, facilitar por ejemplo la creación de estadísticas.

Proceso de Desarrollo

Metodología

Para el desarrollo de este proyecto, se ha utilizado el modelo incremental. En cada incremento, se ha ampliado el metamodelo con la parte correspondiente, se ha añadido a la gramática las nuevas reglas correspondientes al metamodelo y se ha introducido la generación de código para obtener el XML desde la entrada. Por último, se han hecho una serie de pruebas en cada etapa para comprobar el correcto funcionamiento de la sintaxis y la generación de código.

Etapas

Se destacan cuatro etapas principales:

  • Etapa 1: En esta etapa se desarrollo el proceso completo para una métrica con la expresión de tipo Number, por ejemplo: Measure metrica1 = 8.00
  • Etapa 2: Aquí se desarrollo el proceso completo para las expresiones infijas, por ejemplo: Measure metrica2 = 8.00 * 2.8
  • Etapa 3: Se desarrolló el proceso para generar la consulta necesaria para las condiciones simples, por ejemplo: Measure metrica3 = query(count, PMS, Ticket, status = 'Open')
  • Etapa 4: Desarrollo de las expresiones compuestas, por ejemplo: Measure metrica4 = query(count, PMS, Ticket, status = 'Open' or status = 'Pending')

Planificación

La planificación seguida ha sido:

Gant-2.PNG Gant-1.PNG

Descripción de las tareas:

Gant-3.PNG

Herramientas utilizadas

  • Eclipse: IDE (Entorno de Desarrollo Integrado) libre y multiplataforma con el que se pueden realizar multitud de proyectos, entre otros, la construcción de DSLs.
  • Ecore: Meta-metamodelo desarrollado por Eclipse y basado en MOF (Meta-Object Facility). Dispone de un editor visual muy similar a UML y un editor con estructura de tipo árbol, muy útil para diseñar nuestro metamodelo.
  • Xtext: Lenguaje para diseñar gramáticas y ampliar aspectos de los DSLs, así hemos podido generar la gramática directamente del metamodelo.
  • Xtend: Lenguaje para generar código Java a partir de una gramática en Xtext.
  • Redmine: Software libre y de código abierto con el que se puede hacer seguimiento de proyectos. En este proyecto en concreto, solo nos ha interesado utilizar el API Rest de Redmine, para obtener la información necesaria para las consultas.
  • Pentaho Data Integrator: Herramienta para integrar diferentes tipos de datos y poder trabajar con ellos.

Documentación Técnica

Análisis

Requisitos funcionales

Cada métrica será de la forma Measure nombre = expresion

Donde nombre es el nombre de la métrica y expresion podrá ser de uno de los siguientes tipos:

  • Número: Number de tipo float
  • Operación infija:
    • Operadores aritméticos: +, -, * y /
    • Operadores lógicos: and y or
    • Operadores de comparación: <, <=, >, >=, == y !=
  • Consulta: query(FUNCION_ESTADISTICA, TIPO_SISTEMA, TIPO_EVIDENCIA, PREDICADO)
    • FUNCION_ESTADISTICA: función con la que queramos
      • contar: count
      • máximo: max
      • mínimo: min
      • sumar: sum
      • media aritmética: avg
      • moda: mode
      • mediana: median
      • desviación media: avedev
      • desviación típica: stdev
      • varianza: var
    • TIPO_SISTEMA:
      • Sistema de gestión de proyectos: PMS
      • Sistema de control de versiones: VCS
      • Sistema Wiki: WIKI
      • Repositorio de modelos UML: MODEL
      • Sistema de gestión de aprendizaje: LMS
      • Sistema de gestión de documentos: DMS
    • TIPO_EVIDENCIA: a cada tipo de sistema le corresponde las siguientes evidencias
      • PMS
        • Project
        • Ticket
        • Milestone
        • User
      • VCS
        • Repository
        • Revision
        • File
        • Committer
      • DMS
        • Space
        • Folder
        • Document
      • LMS
        • Course
        • Student
        • Activity
      • WIKI
        • Article
        • Category
        • User
      • MODEL
        • Project
        • Folder
        • Model
        • ModelElement
    • PREDICADO
      • Condición simple: donde se puede comparar cualquier atributo de cada tipo de evidencia con un valor (número, cadena o fecha) o con otro atributo correspondiente, por ejemplo Measure metrica1 = query(count, PMS, Ticket, priority = 5) o Measure metrica1 = query(count, PMS, Ticket, status = priotiry)
      • Condición compuesta: donde se pueden concatenar varios predicados a través de operadores lógicos, por ejemplo Measure metrica3 = query[(count, PMS, Ticket, priority = 5 and status = 'Open']

Requisitos no funcionales

  • Tener instalado Java y Pentaho en el sistema
  • Correcto funcionamiento del DSL
  • Debe ser escalable para incorporar nuevas funcionalidades

Requisitos de interfaz

  • El usuario introducirá correctamente una métrica
  • El sistema generará el XML correspondiente a la métrica introducida

Diseño

Metamodelo

Metamodelo

En la imagen de la derecha se puede ver el metamodelo correspondiente a este proyecto.

Implementación

Xtext

Como se ha comentado anteriormente, con esta herramienta se pueden diseñar gramáticas. Aunque automáticamente se crea una grámatica atendiendo a las características del metamodelo diseñado, ésta ha tenido que ser modificada ampliamente para cumplir los requisitos exigidos para el correcto funcionamiento del sistema pedido.

A continuación, se muestra la gramática resultante final.

// automatically generated by Xtext
grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

import "platform:/resource/gramaticaEMF/model/metamodelo.ecore" 
import "http://www.eclipse.org/emf/2002/Ecore" as ecore

MetricAutomationModel returns MetricAutomationModel:
	{MetricAutomationModel}
		(measures+=ExecutableMeasure ( "," measures+=ExecutableMeasure)*)?;

ExecutableMeasure returns ExecutableMeasure:
	'Measure' namedMeasure=EString '=' expression=MultiExpresion
	;

MultiExpresion returns Expression:
	ArithmeticInfix | ComparisonExpression | LogicInfix 
;

LogicInfix returns Expression:
	LogicExpression ({LogicInfix.expa = current} operator=Logic expb=LogicExpression)*
;

LogicExpression returns Expression:
	'(' ComparisonExpression ')'  
;

ComparisonExpression returns Expression:
	Number {ComparisonInfix.expa = current} operator=Comparison expb=Number
;

Comparison returns Comparison:
	'>'| '<' | '>=' | '<=' | '==' | '!='
;

ComparibleExpression returns Expression:
	Number | '(' ArithmeticInfix ')'
;

Logic returns Logic:
	'&' | '|' | 'and' | 'or' | 'AND' | 'OR'
;

InfixExpression_Impl returns InfixExpression:
	'InfixExpression'
	'{'
		'expa' expa=Expression
		'expb' expb=Expression
	'}';

ComparisonInfix returns ComparisonInfix:
	'ComparisonInfix'
	'{'
		'operator' operator=Comparison
		'expa' expa=Expression
		'expb' expb=Expression
	'}';

ArithmeticInfix returns Expression:
	PriorityArithmeticInfix ({ArithmeticInfix.expa = current} operator=Arithmetic expb=PriorityArithmeticInfix)*;

PriorityArithmeticInfix returns Expression:
	Expression ({ArithmeticInfix.expa = current} operator=ArithmeticPriority expb=Expression)*
;

Expression returns Expression:
	 Number | '(' ArithmeticInfix ')' | Query;

Arithmetic returns Arithmetic:
	'+' | '-'
;


ArithmeticPriority returns Arithmetic:
	'*'|'/'
;

EString returns ecore::EString:
	STRING | ID;
	
EFloat returns ecore::EFloat:
	'-'? INT? ('.' INT (('E'|'e') '-'? INT)?)?;

Number returns Number:
	{Number}
	value=EFloat
	;

Query returns Query:
	'query' '('statistic=StatisticFunction ',' system=SystemType ',' evidence=Evidence ',' predicate = Predicateaux ')';

Predicateaux returns Predicate:
	Predicate | Predicatenull
;

Predicatenull returns Predicate:
	{Predicate}
;


StatisticFunction returns StatisticFunction:
	'count' | 'max' | 'min' | 'sum' | 'avg' | 'mode' | 'median' | 'avedev' | 'stdev' | 'var';

SystemType returns SystemType:
	'PMS' | 'VCS' | 'WIKI' | 'MODEL' | 'LMS' | 'DMS';

Evidence returns Evidence:
	EvidencePMS | EvidenceVCS | EvidenceDMS | EvidenceLMS | EvidenceMODEL | EvidenceWIKI;


EvidencePMS returns EvidencePMS:
	type=PMS;

EvidenceVCS returns EvidenceVCS:
	type=VCS;

EvidenceDMS returns EvidenceDMS:
	type=DMS;

EvidenceLMS returns EvidenceLMS:
	type=LMS;

EvidenceMODEL returns EvidenceMODEL:
	type=MODEL;

EvidenceWIKI returns EvidenceWIKI:
	type=WIKI;

PMS returns PMS:
	'Project' | 'Ticket' | 'Milestone' | 'User';

VCS returns VCS:
	'Repository' | 'Revision' | 'File' | 'Committer';

DMS returns DMS:
	'Space' | 'Folder' | 'Document';

LMS returns LMS:
	'Course' | 'Student' | 'Activity';

MODEL returns MODEL:
	'Project' | 'Folder' | 'Model' | 'ModelElement';

WIKI returns WIKI:
	'Article' | 'Category' | 'User';
	
Predicate returns Predicate:
	SimpleCondition | CompositeCondition;
	
SimpleCondition returns SimpleCondition:
		atribute1=Attribute
		comp=Comparison
		atribute2=Aux
	;

Aux:
	Value | Attribute
;

CompositeCondition returns Predicate:
	'(' Predicate')' ({CompositeCondition.condition1 = current} operator=Logic '(' condition2=Predicate ')')+;

Attribute returns Attribute:
	AttributesProject | AttributesMilestone |AttributesTicket | AttributesUser;

AttributesProject returns AttributesProject:
	{AttributesProject}
	(description='description')?
	(nameProject='name')?
	(homepage='homepage')?
	(creationDate='creationDate')?;

AttributesMilestone returns AttributesMilestone:
	{AttributesMilestone}
	(description='description')?
	(title='title')?
	(creationDate='creationDate')?
	(dueDate='dueDate')?
	(project='project')?;

AttributesTicket returns AttributesTicket:
	{AttributesTicket}
	(description='description')?
	(status='status')?
	(priority='priority')?
	(creationUser='creationUser')?
	(assignedUser='assignedUser')?
	(creationDate='creationDate')?
	(dueDate='dueDate')?
	(doneRatio='doneRatio')?
	(estimatedHours='estimatedHours')?
	(elapsedHours='elapsedHours')?
	(project='project')?;

AttributesUser returns AttributesUser:
	{AttributesUser}
	(email='email')?
	(nameUser='name')?
	(nickname='nickname')?;

Value returns Value:
	Number | Cadena | Fecha;

Cadena returns Cadena:
	value=EString;
	
Fecha returns Date:
	value=DATE
;

terminal DATE returns ecore::EDate : '0'..'9' '0'..'9' '0'..'9' '0'..'9' '-' ('0'..'1') '0'..'9' '-' '0'..'3' '0'..'9' ;

Xtend

Xtend nos facilita la obtención del código Java necesario para satisfacer nuestras necesidades. De una forma mucho más limpia y rápida obtenemos el código Java necesario.

Se añade el código en Xtend que hemos usado.

/*
 * generated by Xtext
 */
package org.xtext.example.mydsl.generator

import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
import metamodelo.*
import metamodelo.Predicate
import metamodelo.Attribute
import org.eclipse.emf.ecore.EClass
import metamodelo.Aux
import metamodelo.Comparison
import java.text.SimpleDateFormat

class MyDslGenerator implements IGenerator {
	
	var String Nombre;
	var int NumeroOr = 1;
	
	override void doGenerate(Resource resource, IFileSystemAccess myHTMLFile) {
		
		for(myMeasure: resource.allContents.toIterable.filter(typeof(ExecutableMeasure))) {
			myHTMLFile.generateFile(myMeasure.getNamedMeasure() + ".kjb", myMeasure.toKJB(resource, myHTMLFile))
		}
	}
	def String toKJB (ExecutableMeasure measure, Resource resource, IFileSystemAccess file)
	 '''
		<?xml version="1.0" encoding="UTF-8"?>
			 <measurement measure="«measure.getNamedMeasure()»"> <value>«calculate(measure, resource, file, measure.getExpression())»</value>
		</measurement>
'''
	def calculate(ExecutableMeasure measure, Resource resource, IFileSystemAccess file,Expression exp) { 
		if (exp.eClass.name.equals ("ArithmeticInfix")) {
			 var ArithmeticInfix expresion
			 expresion = exp as ArithmeticInfix
			 return toKJB(expresion)
		}
		if (exp.eClass.name.equals ("LogicInfix")) {
			 var LogicInfix expresion
			 expresion = exp as LogicInfix
			 return toKJB(expresion)
		}
		if (exp.eClass.name.equals ("ComparisonInfix")) {
			 var ComparisonInfix expresion
			 expresion = exp as ComparisonInfix
			 return toKJB(expresion)
		}
		if (exp.eClass.name.equals ("Number")) {
			 var Number numero
			 numero = exp as Number
			 return numero.value
		}
		if (exp.eClass.name.equals ("Query")) {
			 var Query q
			 q = exp as Query
			 Pentaho(measure,q,resource,file)
			 return ""
		}
	}

	def  Float toKJB(ArithmeticInfix expression){
			var Float aux ;
			var Float expa;
			var Float expb;
			if (expression.getExpa().eClass.name.equals ("ArithmeticInfix")) {
				 var ArithmeticInfix expresiona
				 expresiona = expression.getExpa() as ArithmeticInfix
				 expa = toKJB(expresiona)
			}
			if (expression.getExpb().eClass.name.equals ("ArithmeticInfix")) {
				 var ArithmeticInfix expresiona
				 expresiona = expression.getExpb() as ArithmeticInfix
				 expb = toKJB(expresiona)
			}
			if (expression.getExpa().eClass.name.equals ("Number")) {
				 var Number expresiona
				 expresiona = expression.getExpa() as Number
				 expa = expresiona.value
			}
			
			if (expression.getExpb().eClass.name.equals ("Number")) {
				 var Number expresiona
				 expresiona = expression.getExpb() as Number
				 expb = expresiona.value
			}
			
			switch(expression.operator.value){
				case 0:{
					aux = expa + expb;
				}
				case 1:{
					aux = expa - expb;
				}
				case 2:{
					aux = expa * expb;
				}
				case 3:{
					aux = expa / expb;
				}
				
			}
			return aux;
	}
	
	def  Boolean toKJB(ComparisonInfix expression){
		var Float expa;
		var Float expb;
		
		if (expression.getExpa().eClass.name.equals ("ArithmeticInfix")) {
				 var ArithmeticInfix expresiona
				 expresiona = expression.getExpa() as ArithmeticInfix
				 expa = toKJB(expresiona)
			}
			if (expression.getExpb().eClass.name.equals ("ArithmeticInfix")) {
				 var ArithmeticInfix expresiona
				 expresiona = expression.getExpb() as ArithmeticInfix
				 expb = toKJB(expresiona)
			}
			if (expression.getExpa().eClass.name.equals ("Number")) {
				 var Number expresiona
				 expresiona = expression.getExpa() as Number
				 expa = expresiona.value
			}
			
			if (expression.getExpb().eClass.name.equals ("Number")) {
				 var Number expresiona
				 expresiona = expression.getExpb() as Number
				 expb = expresiona.value
			}
			switch(expression.operator.value){
				case 0:{
					return expa > expb;
				}
				case 1:{
					return expa >= expb;
				}
				case 2:{
					return expa < expb;
				}
				case 3:{
					return expa <= expb;
				}
				case 4:{
					return expa == expb;
				}
				case 5:{
					return expa != expb;
				}
				
			}
	}
	def  Boolean toKJB(LogicInfix expression){	
		var Boolean expa = false;
		var Boolean expb = false;
		if (expression.getExpa().eClass.name.equals ("LogicInfix")) {
				 var LogicInfix expresiona
				 expresiona = expression.getExpa() as LogicInfix
				 expa = toKJB(expresiona)
		}	
		if (expression.getExpb().eClass.name.equals ("LogicInfix")) {
				 var LogicInfix expresiona
				 expresiona = expression.getExpb() as LogicInfix
				 expb = toKJB(expresiona)
		}
		if (expression.getExpa().eClass.name.equals ("ComparisonInfix")) {
				 var ComparisonInfix expresionaa
				 expresionaa = expression.getExpa() as ComparisonInfix
				 expa = toKJB(expresionaa)
		}
			
		if (expression.getExpb().eClass.name.equals ("ComparisonInfix")) {
				 var ComparisonInfix expresionaa
				 expresionaa = expression.getExpb() as ComparisonInfix
				 expb = toKJB(expresionaa)
		}
		
		switch(expression.operator.value){
				case 0:{
					return expa && expb;
				}
				case 1:{
					return expa || expb;
				}
		}
	}
	def String toKTR(Query query)
		'''«query.cabecera()»«query.getEvidencia()»?«		if (query.predicate.eClass.name.equals("CompositeCondition")){
			var CompositeCondition predicate
			predicate = query.predicate as CompositeCondition
			getCompositeCondition(predicate,query)
		}
»«		if (query.predicate.eClass.name.equals("SimpleCondition")){
			var SimpleCondition predicate
			predicate = query.predicate as SimpleCondition
			getSimpleCondition(predicate)
		}
		»«query.pie()»'''
	def getCompositeCondition(CompositeCondition condition, Query query){
		var String resultado = "";
		if (condition.operator.value == 1){
			if (condition.condition1.eClass.name.equals("CompositeCondition")){
				var CompositeCondition predicate
				predicate = condition.condition1 as CompositeCondition
				resultado = resultado + getCompositeCondition(predicate,query)
			}
			if (condition.condition1.eClass.name.equals("SimpleCondition")){
				var SimpleCondition predicate
				predicate = condition.condition1 as SimpleCondition
				resultado= resultado + getSimpleCondition(predicate)
			}
			resultado = resultado + query.pie()
			NumeroOr = NumeroOr + 1
			resultado = resultado + query.cabecera() + query.getEvidencia() + "?"
			if (condition.condition2.eClass.name.equals("CompositeCondition")){
				var CompositeCondition predicate
				predicate = condition.condition2 as CompositeCondition
				resultado = resultado + getCompositeCondition(predicate,query)
			}
			if (condition.condition2.eClass.name.equals("SimpleCondition")){
				var SimpleCondition predicate
				predicate = condition.condition2 as SimpleCondition
				resultado = resultado + getSimpleCondition(predicate)
			}
			
		}
		else
		{
			if (condition.condition1.eClass.name.equals("CompositeCondition")){
				var CompositeCondition predicate
				predicate = condition.condition1 as CompositeCondition
				resultado = resultado + getCompositeCondition(predicate,query)
			}
			if (condition.condition1.eClass.name.equals("SimpleCondition")){
				var SimpleCondition predicate
				predicate = condition.condition1 as SimpleCondition
				resultado = resultado + getSimpleCondition(predicate)
			}
			resultado= resultado + and()
			if (condition.condition2.eClass.name.equals("CompositeCondition")){
				var CompositeCondition predicate
				predicate = condition.condition2 as CompositeCondition
				resultado = resultado + getCompositeCondition(predicate,query)
			}
			if (condition.condition2.eClass.name.equals("SimpleCondition")){
				var SimpleCondition predicate
				predicate = condition.condition2 as SimpleCondition
				resultado = resultado + getSimpleCondition(predicate)
			}
		}
		return resultado
	} 
	def enduri() '''</uri>''' 
	def and() '''&'''
	def cabecera(Query query) '''
  <step>
    <name>GF«NumeroOr»</name>
    <type>RowGenerator</type>
    <description/>
    <distribute>Y</distribute>
    <copies>1</copies>
         <partitioning>
           <method>none</method>
           <schema_name/>
           </partitioning>
    <fields>
    </fields>
    <limit>1</limit>
     <cluster_schema/>
 <remotesteps>   <input>   </input>   <output>   </output> </remotesteps>    <GUI>
      <xloc>31</xloc>
      <yloc>56</yloc>
      <draw>Y</draw>
      </GUI>
    </step>
    
    <step>
    <name>XML«NumeroOr»</name>
    <type>getXMLData</type>
    <description/>
    <distribute>N</distribute>
    <copies>1</copies>
         <partitioning>
           <method>none</method>
           <schema_name/>
           </partitioning>
    <include>N</include>
    <include_field/>
    <rownum>N</rownum>
    <addresultfile>N</addresultfile>
    <namespaceaware>N</namespaceaware>
    <ignorecomments>N</ignorecomments>
    <readurl>N</readurl>
    <validating>N</validating>
    <usetoken>N</usetoken>
    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
    <doNotFailIfNoFile>Y</doNotFailIfNoFile>
    <rownum_field/>
    <encoding>UTF-8</encoding>
    <file>
      <name/>
      <filemask/>
      <exclude_filemask/>
      <file_required>N</file_required>
      <include_subfolders>N</include_subfolders>
    </file>
    <fields>
      <field>
        <name>id</name>
        <xpath>id</xpath>
        <element_type>node</element_type>
        <result_type>valueof</result_type>
        <type>Number</type>
        <format/>
        <currency/>
        <decimal/>
        <group/>
        <length>-1</length>
        <precision>-1</precision>
        <trim_type>none</trim_type>
        <repeat>N</repeat>
      </field>
    </fields>
    <limit>0</limit>
    <loopxpath>«if (query.evidence.eClass.name.equals("EvidencePMS")){
			var EvidencePMS evidencia
				 evidencia = query.evidence as EvidencePMS
				 switch(evidencia.type.value){
				 	case 0:{// Proyecto
				 		 "/projects/project"
				 	} 
				 	case 1:{ // Ticket
				 		 "/issues/issue"
				 	}
				 	case 2:{ // Milestone
				 		 "/versions/version"
				 	}
				 	case 3:{ // User
				 		 "/users/user"
				 	}
				 }
		}»</loopxpath>
    <IsInFields>Y</IsInFields>
    <IsAFile>N</IsAFile>
    <XmlField>result</XmlField>
    <prunePath/>
    <shortFileFieldName/>
    <pathFieldName/>
    <hiddenFieldName/>
    <lastModificationTimeFieldName/>
    <uriNameFieldName/>
    <rootUriNameFieldName/>
    <extensionFieldName/>
    <sizeFieldName/>
     <cluster_schema/>
 <remotesteps>   <input>   </input>   <output>   </output> </remotesteps>    <GUI>
      <xloc>349</xloc>
      <yloc>50</yloc>
      <draw>Y</draw>
      </GUI>
    </step>


  <step>
    <name>HTTP«NumeroOr»</name>
    <type>HTTP</type>
    <description/>
    <distribute>N</distribute>
    <copies>1</copies>
         <partitioning>
           <method>none</method>
           <schema_name/>
           </partitioning>
    <url>'''
    
    
    
    
    
	def pie(Query query) '''</url>
    <urlInField>N</urlInField>
    <urlField>url</urlField>
    <encoding>UTF-8</encoding>
    <httpLogin/>
    <httpPassword/>
    <proxyHost/>
    <proxyPort/>
    <lookup>
    </lookup>
    <result>
      <name>result</name>
      <code/>
      <response_time/>
    </result>
     <cluster_schema/>
 <remotesteps>   <input>   </input>   <output>   </output> </remotesteps>    <GUI>
      <xloc>155</xloc>
      <yloc>52</yloc>
      <draw>Y</draw>
      </GUI>
    </step>

'''

def conexiones (){
	var String resultado = "<order> <hop> <from>FilaNombre</from><to>Juntar</to><enabled>Y</enabled></hop> <hop> <from>Juntar</from><to>Salida</to><enabled>Y</enabled></hop><hop> <from>St</from><to>Juntar</to><enabled>Y</enabled> </hop>"
	var int i = 1
	while (i <= NumeroOr){
		resultado = resultado + "<hop><from>HTTP"+i.toString+"</from><to>XML"+i.toString+"</to><enabled>Y</enabled> </hop>  <hop> <from>XML"+i.toString+"</from><to>St</to><enabled>Y</enabled> </hop>"+"<hop> <from>GF"+i.toString+"</from><to>HTTP"+i.toString+"</to><enabled>Y</enabled> </hop>"
		i=i+1
	}
	resultado = resultado + "</order> <step_error_handling>
  </step_error_handling>
   <slave-step-copy-partition-distribution>
</slave-step-copy-partition-distribution>
   <slave_transformation>N</slave_transformation></transformation>"	
}
def componentesIniciales(Query query)'''<?xml version="1.0" encoding="UTF-8"?>
<transformation>
  <info>
    <name>peticion2</name>
    <description/>
    <extended_description/>
    <trans_version/>
    <trans_type>Normal</trans_type>
    <directory>/</directory>
    <parameters>
    </parameters>
    <log>
<trans-log-table><connection/>
<schema/>
<table/>
<size_limit_lines/>
<interval/>
<timeout_days/>
<field><id>ID_BATCH</id><enabled>Y</enabled><name>ID_BATCH</name></field><field><id>CHANNEL_ID</id><enabled>Y</enabled><name>CHANNEL_ID</name></field><field><id>TRANSNAME</id><enabled>Y</enabled><name>TRANSNAME</name></field><field><id>STATUS</id><enabled>Y</enabled><name>STATUS</name></field><field><id>LINES_READ</id><enabled>Y</enabled><name>LINES_READ</name><subject/></field><field><id>LINES_WRITTEN</id><enabled>Y</enabled><name>LINES_WRITTEN</name><subject/></field><field><id>LINES_UPDATED</id><enabled>Y</enabled><name>LINES_UPDATED</name><subject/></field><field><id>LINES_INPUT</id><enabled>Y</enabled><name>LINES_INPUT</name><subject/></field><field><id>LINES_OUTPUT</id><enabled>Y</enabled><name>LINES_OUTPUT</name><subject/></field><field><id>LINES_REJECTED</id><enabled>Y</enabled><name>LINES_REJECTED</name><subject/></field><field><id>ERRORS</id><enabled>Y</enabled><name>ERRORS</name></field><field><id>STARTDATE</id><enabled>Y</enabled><name>STARTDATE</name></field><field><id>ENDDATE</id><enabled>Y</enabled><name>ENDDATE</name></field><field><id>LOGDATE</id><enabled>Y</enabled><name>LOGDATE</name></field><field><id>DEPDATE</id><enabled>Y</enabled><name>DEPDATE</name></field><field><id>REPLAYDATE</id><enabled>Y</enabled><name>REPLAYDATE</name></field><field><id>LOG_FIELD</id><enabled>Y</enabled><name>LOG_FIELD</name></field></trans-log-table>
<perf-log-table><connection/>
<schema/>
<table/>
<interval/>
<timeout_days/>
<field><id>ID_BATCH</id><enabled>Y</enabled><name>ID_BATCH</name></field><field><id>SEQ_NR</id><enabled>Y</enabled><name>SEQ_NR</name></field><field><id>LOGDATE</id><enabled>Y</enabled><name>LOGDATE</name></field><field><id>TRANSNAME</id><enabled>Y</enabled><name>TRANSNAME</name></field><field><id>STEPNAME</id><enabled>Y</enabled><name>STEPNAME</name></field><field><id>STEP_COPY</id><enabled>Y</enabled><name>STEP_COPY</name></field><field><id>LINES_READ</id><enabled>Y</enabled><name>LINES_READ</name></field><field><id>LINES_WRITTEN</id><enabled>Y</enabled><name>LINES_WRITTEN</name></field><field><id>LINES_UPDATED</id><enabled>Y</enabled><name>LINES_UPDATED</name></field><field><id>LINES_INPUT</id><enabled>Y</enabled><name>LINES_INPUT</name></field><field><id>LINES_OUTPUT</id><enabled>Y</enabled><name>LINES_OUTPUT</name></field><field><id>LINES_REJECTED</id><enabled>Y</enabled><name>LINES_REJECTED</name></field><field><id>ERRORS</id><enabled>Y</enabled><name>ERRORS</name></field><field><id>INPUT_BUFFER_ROWS</id><enabled>Y</enabled><name>INPUT_BUFFER_ROWS</name></field><field><id>OUTPUT_BUFFER_ROWS</id><enabled>Y</enabled><name>OUTPUT_BUFFER_ROWS</name></field></perf-log-table>
<channel-log-table><connection/>
<schema/>
<table/>
<timeout_days/>
<field><id>ID_BATCH</id><enabled>Y</enabled><name>ID_BATCH</name></field><field><id>CHANNEL_ID</id><enabled>Y</enabled><name>CHANNEL_ID</name></field><field><id>LOG_DATE</id><enabled>Y</enabled><name>LOG_DATE</name></field><field><id>LOGGING_OBJECT_TYPE</id><enabled>Y</enabled><name>LOGGING_OBJECT_TYPE</name></field><field><id>OBJECT_NAME</id><enabled>Y</enabled><name>OBJECT_NAME</name></field><field><id>OBJECT_COPY</id><enabled>Y</enabled><name>OBJECT_COPY</name></field><field><id>REPOSITORY_DIRECTORY</id><enabled>Y</enabled><name>REPOSITORY_DIRECTORY</name></field><field><id>FILENAME</id><enabled>Y</enabled><name>FILENAME</name></field><field><id>OBJECT_ID</id><enabled>Y</enabled><name>OBJECT_ID</name></field><field><id>OBJECT_REVISION</id><enabled>Y</enabled><name>OBJECT_REVISION</name></field><field><id>PARENT_CHANNEL_ID</id><enabled>Y</enabled><name>PARENT_CHANNEL_ID</name></field><field><id>ROOT_CHANNEL_ID</id><enabled>Y</enabled><name>ROOT_CHANNEL_ID</name></field></channel-log-table>
<step-log-table><connection/>
<schema/>
<table/>
<timeout_days/>
<field><id>ID_BATCH</id><enabled>Y</enabled><name>ID_BATCH</name></field><field><id>CHANNEL_ID</id><enabled>Y</enabled><name>CHANNEL_ID</name></field><field><id>LOG_DATE</id><enabled>Y</enabled><name>LOG_DATE</name></field><field><id>TRANSNAME</id><enabled>Y</enabled><name>TRANSNAME</name></field><field><id>STEPNAME</id><enabled>Y</enabled><name>STEPNAME</name></field><field><id>STEP_COPY</id><enabled>Y</enabled><name>STEP_COPY</name></field><field><id>LINES_READ</id><enabled>Y</enabled><name>LINES_READ</name></field><field><id>LINES_WRITTEN</id><enabled>Y</enabled><name>LINES_WRITTEN</name></field><field><id>LINES_UPDATED</id><enabled>Y</enabled><name>LINES_UPDATED</name></field><field><id>LINES_INPUT</id><enabled>Y</enabled><name>LINES_INPUT</name></field><field><id>LINES_OUTPUT</id><enabled>Y</enabled><name>LINES_OUTPUT</name></field><field><id>LINES_REJECTED</id><enabled>Y</enabled><name>LINES_REJECTED</name></field><field><id>ERRORS</id><enabled>Y</enabled><name>ERRORS</name></field><field><id>LOG_FIELD</id><enabled>N</enabled><name>LOG_FIELD</name></field></step-log-table>
    </log>
    <maxdate>
      <connection/>
      <table/>
      <field/>
      <offset>0.0</offset>
      <maxdiff>0.0</maxdiff>
    </maxdate>
    <size_rowset>10000</size_rowset>
    <sleep_time_empty>50</sleep_time_empty>
    <sleep_time_full>50</sleep_time_full>
    <unique_connections>N</unique_connections>
    <feedback_shown>Y</feedback_shown>
    <feedback_size>50000</feedback_size>
    <using_thread_priorities>Y</using_thread_priorities>
    <shared_objects_file/>
    <capture_step_performance>N</capture_step_performance>
    <step_performance_capturing_delay>1000</step_performance_capturing_delay>
    <step_performance_capturing_size_limit>100</step_performance_capturing_size_limit>
    <dependencies>
    </dependencies>
    <partitionschemas>
    </partitionschemas>
    <slaveservers>
    </slaveservers>
    <clusterschemas>
    </clusterschemas>
  <created_user>-</created_user>
  <created_date>2013/07/29 18:25:08.100</created_date>
  <modified_user>-</modified_user>
  <modified_date>2013/07/29 18:25:08.100</modified_date>
  </info>
  <notepads>
  </notepads>
  <connection>
    <name>AgileBI</name>
    <server>localhost</server>
    <type>MONETDB</type>
    <access>Native</access>
    <database>pentaho-instaview</database>
    <port>50000</port>
    <username>monetdb</username>
    <password>Encrypted 2be98afc86aa7f2e4cb14a17edb86abd8</password>
    <servername/>
    <data_tablespace/>
    <index_tablespace/>
    <attributes>
      <attribute><code>FORCE_IDENTIFIERS_TO_LOWERCASE</code><attribute>N</attribute></attribute>
      <attribute><code>FORCE_IDENTIFIERS_TO_UPPERCASE</code><attribute>N</attribute></attribute>
      <attribute><code>IS_CLUSTERED</code><attribute>N</attribute></attribute>
      <attribute><code>PORT_NUMBER</code><attribute>50000</attribute></attribute>
      <attribute><code>QUOTE_ALL_FIELDS</code><attribute>N</attribute></attribute>
      <attribute><code>SUPPORTS_BOOLEAN_DATA_TYPE</code><attribute>N</attribute></attribute>
      <attribute><code>USE_POOLING</code><attribute>N</attribute></attribute>
    </attributes>
  </connection>
  

  <step>
    <name>FilaNombre</name>
    <type>RowGenerator</type>
    <description/>
    <distribute>Y</distribute>
    <copies>1</copies>
         <partitioning>
           <method>none</method>
           <schema_name/>
           </partitioning>
    <fields>
      <field>
        <name>nombre</name>
        <type>String</type>
        <format/>
        <currency/>
        <decimal/>
        <group/>
        <nullif>prueba</nullif>
        <length>-1</length>
        <precision>-1</precision>
      </field>
    </fields>
    <limit>1</limit>
     <cluster_schema/>
 <remotesteps>   <input>   </input>   <output>   </output> </remotesteps>    <GUI>
      <xloc>683</xloc>
      <yloc>5</yloc>
      <draw>Y</draw>
      </GUI>
    </step>
    
    <step>
    <name>Juntar</name>
    <type>JoinRows</type>
    <description/>
    <distribute>Y</distribute>
    <copies>1</copies>
         <partitioning>
           <method>none</method>
           <schema_name/>
           </partitioning>
      <directory>%%java.io.tmpdir%%</directory>
      <prefix>out</prefix>
      <cache_size>500</cache_size>
      <main/>
    <compare>
<condition>
 <negated>N</negated>
 <leftvalue/>
 <function>=</function>
 <rightvalue/>
 </condition>
    </compare>
     <cluster_schema/>
 <remotesteps>   <input>   </input>   <output>   </output> </remotesteps>    <GUI>
      <xloc>680</xloc>
      <yloc>91</yloc>
      <draw>Y</draw>
      </GUI>
    </step>
    
    <step>
    <name>St</name>
    <type>UnivariateStats</type>
    <description/>
    <distribute>N</distribute>
    <copies>1</copies>
         <partitioning>
           <method>none</method>
           <schema_name/>
           </partitioning>
       <univariate_stats><source_field_name>id</source_field_name>
<N>«if (query.statistic.literal == "count") "Y" else "N"»</N>
<mean>«if (query.statistic.literal == "avg") "Y" else "N"»</mean>
<stdDev>«if (query.statistic.literal == "stdev") "Y" else "N"»</stdDev>
<min>«if (query.statistic.literal == "min") "Y" else "N"»</min>
<max>«if (query.statistic.literal == "max") "Y" else "N"»</max>
<median>«if (query.statistic.literal == "median") "Y" else "N"»</median>
<percentile>-1.0</percentile>
<interpolate>N</interpolate>
</univariate_stats>
     <cluster_schema/>
 <remotesteps>   <input>   </input>   <output>   </output> </remotesteps>    <GUI>
      <xloc>528</xloc>
      <yloc>84</yloc>
      <draw>Y</draw>
      </GUI>
    </step>
'''
def componentesFinales(Query query)'''
     <step>
    <name>Salida</name>
    <type>XMLOutput</type>
    <description/>
    <distribute>N</distribute>
    <copies>1</copies>
         <partitioning>
           <method>none</method>
           <schema_name/>
           </partitioning>
    <encoding>UTF-8</encoding>
    <name_space/>
    <xml_main_element>Measurement</xml_main_element>
    <xml_repeat_element>Measure</xml_repeat_element>
    <file>
      <name>/home/alejandro/fichero_query</name>
      <extention>xml</extention>
      <servlet_output>N</servlet_output>
      <do_not_open_newfile_init>N</do_not_open_newfile_init>
      <split>Y</split>
      <add_date>N</add_date>
      <add_time>N</add_time>
      <SpecifyFormat>N</SpecifyFormat>
      <omit_null_values>N</omit_null_values>
      <date_time_format/>
      <add_to_result_filenames>N</add_to_result_filenames>
      <zipped>N</zipped>
      <splitevery>0</splitevery>
    </file>
    <fields>
      <field>
        <name>nombre</name>
        <element/>
        <type>String</type>
        <format/>
        <currency/>
        <decimal/>
        <group/>
        <nullif/>
        <length>-1</length>
        <precision>-1</precision>
      </field>
      <field>
        <name>id(«switch (query.statistic.value){
        	case 0:{"N"}
        	case 2:{"min"}
        	case 1: {"max"}
        	case 4:{"mean"}
        	case 8:{"stdDev"}
        	case 6:{"median"} 
        		}»)</name>
        <element>value</element>
        <type>Number</type>
        <format/>
        <currency/>
        <decimal/>
        <group/>
        <nullif/>
        <length>-1</length>
        <precision>-1</precision>
      </field>
    </fields>
     <cluster_schema/>
 <remotesteps>   <input>   </input>   <output>   </output> </remotesteps>    <GUI>
      <xloc>929</xloc>
      <yloc>84</yloc>
      <draw>Y</draw>
      </GUI>
    </step>
	'''
  
	def getEvidencia(Query query){
		if (query.evidence.eClass.name.equals("EvidencePMS")){
			var EvidencePMS evidencia
				 evidencia = query.evidence as EvidencePMS
				 switch(evidencia.type.value){
				 	case 0:{// Proyecto
				 		return "http://www.redmine.org/projects.xml"
				 	} 
				 	case 1:{ // Ticket
				 		return "http://www.redmine.org/issues.xml"
				 	}
				 	case 2:{ // Milestone
				 		return "http://www.redmine.org/projects/redmine/versions.xml"
				 	}
				 	case 3:{ // User
				 		return "http://www.redmine.org/users.xml"
				 	}
				 }
		}
	}
	
	
	def getSimpleCondition(SimpleCondition simple) {
		return getAtribute(simple.atribute1) + getComparison(simple.comp) + getValue(simple.atribute2)
	}
	
	def getAtribute(Attribute at) {
		if(at.eClass.name.equals("AttributesProject")){
			var AttributesProject atribute
			atribute = at as AttributesProject
			if(atribute.description == 'description'){
				return "description"
			}
			if(atribute.nameProject == 'name'){
				return "name"
			}
			if(atribute.homepage == 'homepage'){
				return "homepage"
			}
			if(atribute.creationDate == 'creationDate'){
				return "created_on"
			}
		}
		if(at.eClass.name.equals("AttributesMilestone")){
			var AttributesMilestone atribute
			atribute = at as AttributesMilestone
			if(atribute.description == 'description'){
				return "description"
			}
			if(atribute.title == 'title'){
				return "title"
			}
			if(atribute.creationDate == 'creationDate'){
				return "created_on"
			}
			if(atribute.dueDate == 'dueDate'){
				return "due_date"
			}
			if(atribute.project == 'project'){
				return "project_id"
			}
		}	
		if(at.eClass.name.equals("AttributesTicket")){
			var AttributesTicket atribute
			atribute = at as AttributesTicket
			if(atribute.description == 'description'){
				return "description"
			}
			if(atribute.status == 'status'){
				return "status_id"
			}
			if(atribute.priority == 'priority'){
				return "priority_id"
			}
			if(atribute.creationUser == 'creationUser'){
				return "author_id"
			}
			if(atribute.assignedUser == 'assignedUser'){
				return "assignedUser"
			}
			if(atribute.creationDate == 'creationDate'){
				return "created_on"
			}
			if(atribute.dueDate == 'dueDate'){
				return "due_date"
			}
			if(atribute.doneRatio == 'doneRatio'){
				return "done_ratio"
			}
			if(atribute.estimatedHours == 'estimatedHours'){
				return "estimated_hours"
			}
			if(atribute.elapsedHours == 'elapsedHours'){
				return "elapsedHours"
			}
			if(atribute.project == 'project'){
				return "project_id"
			}
		}
		if(at.eClass.name.equals("AttributesUser")){
			var AttributesUser atribute
			atribute = at as AttributesUser
			if(atribute.email == 'email'){
				return "mail"
			}
			if(atribute.nameUser == 'name'){
				return "name"
			}
			if(atribute.nickname == 'nickname'){
				return "login"
			}
		}
	}
	
	def getComparison(Comparison comparison) {
		return '='
	}
	
	def getValue(Aux aux) {
		var SimpleDateFormat formateador = new SimpleDateFormat("dd-MM-yyyy");
		if(aux.eClass.name.equals("Number")){
			var Number num
			num = aux as Number
			return num.value
		}
		if(aux.eClass.name.equals("Cadena")){
			var Cadena cadena
			cadena = aux as Cadena
			return cadena.value
		}
		if(aux.eClass.name.equals("Date")){
			var Date fecha
			fecha = aux as Date
			return formateador.format(fecha.value)
		}
		if(aux.eClass.name.equals("Attribute")){
			var Attribute at
			at = aux as Attribute
			return getAtribute(at)
		}
	}
	def void Pentaho (ExecutableMeasure myMeasure,Query query, Resource resource, IFileSystemAccess myHTMLFile){
		Nombre = myMeasure.getNamedMeasure()
		myHTMLFile.generateFile(myMeasure.getNamedMeasure() + ".ktr", query.componentesIniciales()+query.toKTR() + query.componentesFinales()+ conexiones())
		NumeroOr = 1
		
	}
}

Pentaho

La herramienta Pentaho nos permite generar informes y estadísticas usando diferentes herramientas. En nuestro caso lo hemos usado para generar el fichero xml que el usuario de la aplicación busca. Definiendo unos sencillos pasos se puede terminar creando una gran estructura para realizar el objetivo requerido.

A continuación, se pasa a describir a grosso modo uno de los ejemplos que hemos necesitado para construir el fichero xml final.

El código que se usa para construir un fichero de Pentaho es xml, aprovechando esta particularidad, desde Eclipse generamos el código xml necesario para satisfacer nuestras necesidades.

Lo primero que definimos será la URL que necesitamos para realizar la petición al servidor, que nos devolverá una respuesta. Con la función "Get data from XML" seleccionamos el campo del fichero xml que nos interesa, usando xPath, para luego generar diferentes estadísticas, tales como la media, desviación típica, máximo valor, mínimo...

Por último, construiremos un nuevo fichero xml final, el cual es el buscado por el usuario, en donde reflejaremos el nombre de la métrica correspondiente y el valor adecuado.

Pentaho Simple

Este fichero Pentaho nos servirá como base para cada consulta, dependiendo de lo que necesitemos deberemos realizar pequeños cambios sobre él. De tal forma, que si la información que nos piden se corresponde con "la media" seleccionaremos en ese caso que queremos como resultado "la media". Del mismo modo, nos puede interesar obtener información general por ejemplo sobre los diferentes tickets o por el contrario información específica como la prioridad de los tickets: alta, baja, media... en este caso también tendremos que cambiar el campo del xml sobre el que trabajamos, indicando que queremos información sobre la prioridad de los tickets o el que corresponda.

Todos estos pequeños cambios son transparentes al usuario, ya que construiremos el fichero xml que lee Pentaho desde Eclipse a partir de la información suministrada en la consulta del fichero .spel.

A continuación se muestra un ejemplo de un archivo para Pentaho generado a partir de una consulta compleja.

Pentaho Complejo

Pruebas

En esta sección pondremos diferentes ejemplos de los ficheros resultantes tras aplicar las consultas en nuestro lenguaje spel.

  • Measure metrica1 = 8.00

Código resultante


<?xml version="1.0" encoding="UTF-8"?>
<Measurement>
 <Measure><name>metrica1</name> <value>8.00</value> </Measure>
</Measurement>


  • Measure metrica2 = 8.00 * 2.8

Código resultante


<?xml version="1.0" encoding="UTF-8"?>
<Measurement>
 <Measure><name>metrica2</name> <value>22.4</value> </Measure>
</Measurement>
  • Measure metrica3 = query(count, PMS, Ticket, status = 'Open').

Con esta consulta, lo que queremos obtener es el número de proyectos cuyos tickets tengan el status de abiertos.

Código resultante


<?xml version="1.0" encoding="UTF-8"?>
<Measurement>
 <Measure><name>metrica3</name> <value>3965.00</value> </Measure>
</Measurement>

Instalación

Requisitos

Para poder ejecutar y probar el DSL:

  • Eclipse (JUNO)
  • Tener instalado EMF en Eclipse
  • Tener instalado xtext en Eclipse

Pasos para la Instalación

Para poder instalar el DSL, primero deberá descargar el siguiente archivo. Posteriormente deberá descomprimir el archivo e iniciar Eclipse. Para instalarlo, deberá irse a la pestaña "Help"->"Install New Software..". Una vez allí, pulsaremos "Add" y dentro de la ventana emergente, "Local". Allí seleccionaremos la carpeta que anteriormente descomprimimos del archivo "metricasDSL.zip". Seleccionaremos todas las "Features" a instalar (metricasDSL) y procederemos a la instalación.

Modo de uso

Para poder usar el plugin una vez instalado seguiremos los siguientes pasos:

  • Crearemos un nuevo proyecto (por ejemplo un proyecto java) o usaremos uno ya creado.
  • Crearemos un archivo con extensión .spel
  • Finalmente, creado el archivo .spel ya podemos proceder a escribir el programa deseado con la notación explicada en la wiki
  • Cada vez que guardamos el fichero, se generará el código, que puede ser .kjb que contendrá el valor de las operaciones si éstas se pueden realizar, o un .ktr a ejecutar en Pentaho para obtener el resultado.

Enlaces externos

Herramientas personales
Espacios de nombres

Variantes
Acciones
Navegación
Herramientas