Documente Academic
Documente Profesional
Documente Cultură
2. Que es SAP? 8
2.1. Soluciones y productos tradicionales de SAP . . . . . . . . . . . . . . . . . 8
2.1.1. SAP ERP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.1.2. SAP CRM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.1.3. SAP SRM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.1.4. SAP SCM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.1.5. SAP PLM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.2. Una breve historia de SAP . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2.1. Los primeros anos: 1972-1981 . . . . . . . . . . . . . . . . . . . . . 13
2.2.2. La era de SAP R/2: 1982-1991 . . . . . . . . . . . . . . . . . . . . . 14
2.2.3. La era de SAP R/3: 1992-2001 . . . . . . . . . . . . . . . . . . . . . 16
6. Conclusiones 66
Referencias 68
Apendices 70
6
implementada dentro del mercado, por que funciona bien para nuestros propositos, y
que alternativas deberan considerarse en caso de que, por ejemplo, tuviesemos que dar
soporte a mas empleados o tuviesemos que manejar un volumen de datos mas grande.
Esto sera el quinto captulo.
Por ultimo, escribiremos una breve conclusion detallando como cumple el proyecto
realizado los objetivos aqu descritos, as como algunas posibles crticas que se le podran
hacer desde el punto de vista tecnico, con el objetivo de justificar las decisiones de diseno
realizadas. A esto dedicaremos el sexto y ultimo captulo (sin contar con los apendices,
que contienen el codigo fuente de la aplicacion creada).
7
2. Que es SAP?
Para entender bien el contexto en el que este proyecto ha sido realizado, debemos
entender exactamente que es lo que hace SAP y cuales son las soluciones que ofrece desde
el punto de vista empresarial.
SAP SE (Siglas de Systems, Applications & Products in Data Processing) es actual-
mente la tercera compana de software mas grande del mundo, solo por detras de Oracle
y Microsoft. Sin embargo, al contrario que estas dos, SAP no es apenas conocido fuera
del mundo de la gran empresa. Por que ocurre esto?
La respuesta es que el target de SAP no es el hombre de la calle. SAP es una compana
dedicada a hacer software exclusivamente para otras empresas, nunca para el consumidor
final. Ademas, el perfil tradicional de customer SAP es el de una empresa grande, mercado
en el cual ha cosechado un gran exito. Actualmente el 86 % de las empresas Fortune 500
tienen al menos un sistema SAP en sus premisas [18].
Pero exactamente en que manera puede ayudar un sistema SAP al proceso de negocio
de una gran empresa? Intentaremos responder a esta pregunta mediante un repaso a
las soluciones tradicionales mas importantes de la compana, tras lo cual daremos una
pequena introduccion historica.
SAP ERP
SAP CRM
SAP SRM
SAP SCM
SAP PLM
Estas 5 soluciones no dependen unas de otras, sino que mas bien se complementan
entre s. Una empresa customer de SAP puede disponer de todas, varias, o ninguna de
ellas.
Ademas de estos productos hay varios otros que, sin ser tan universalmente adoptados
por el mercado, merecen tambien mencion, como por ejemplo BW + Business Objects,
HCM, y otros. Pasamos pues a detallarlos:
8
2.1.1. SAP ERP
No es una exageracion decir que no se puede hablar de SAP sin hablar de su ERP. De
una forma o de otra (ya sea llamandose R/1, R/2, R/3, o ERP), el ERP de SAP es el
producto mas antiguo desarrollado por esta, y el nucleo de toda instalacion SAP que se
precie.
Pero, que es un ERP? ERP son las siglas de Enterprise Resource Planning, Planifi-
cacion de Recursos de la Empresa en espanol.
Podemos decir que el ERP provee una vision integrada de los procesos de negocio mas
importantes, a menudo en tiempo real, usando bases de datos comunes mantenidas por
un sistema de gestion. Los sistemas ERP monitorean a los distintos recursos de negocio
(dinero, materiales, capacidad productiva) y al estado de los compromisos de negocio:
ordenes de venta, salarios, etc. Las aplicaciones que componen al sistema comparten datos
entre los distintos departamentos (manufacturacion, compras, ventas, contabilidad, etc.)
que las proveen de datos [21].
Es decir, es esencialmente, una aplicacion sobre una base de datos centralizada, que
contiene toda la informacion de la empresa en materias como recursos humanos, gestion
de materiales, finanzas, contabilidad, y otros ambitos que detallaremos mas adelante.
Esto permite que, por ejemplo, una factura elaborada en el ERP sobre una compra de
algun material en concreto actualice automaticamente los datos contables de la empresa,
su presupuesto, etc. Por poner otro, se puede integrar la estrategia de la compana en
el ERP de tal forma que, desde el nivel ejecutivo, llegue directamente hacia niveles de
gestion de talento en Recursos Humanos, por poner un ejemplo.
9
Como se consigue esto exactamente? Gracias a la estructura modular del ERP [5].
Como se puede observar en la figura 1, el ERP esta compuesto de tres secciones principales,
compuestas a su vez de una serie de modulos:
Seccion de Finanzas: Finanzas, Contabilidad, Gestion de activos Fijos, Sistema de
Proyecto
Seccion de Logstica: Ventas, Materiales, Planificacion de produccion, Control de
Calidad, Gestion de Plantas
Seccion de Recursos Humanos: Recursos Humanos
Todos estos modulos interaccionan entre s, dando lugar a un sistema altamente eficien-
te, que elimina bastante overhead relacionado con la comunicacion entre departamentos
y la gestion en general de los detalles de una empresa. Desafortunadamente esto no viene
sin un coste. Los proyectos de implementacion de SAP en companas Fortune 500 suelen
rondar entre los 100 millones de dolares, suelen tardar varios anos, y las bases de datos
suelen ser de Terabytes. Ademas, el manejo del ERP no es en absoluto algo trivial, siendo
necesaria una formacion de varios anos en lo que se conoce como consultora funcional
para llegar a ser un usuario proficiente en alguno de sus modulos. Tambien cabe destacar
que no son necesarios todos los modulos para que el sistema funcione, un ERP podra
tener solo los modulos FI, CO, y HR, por ejemplo.
Aun as, SAP es hoy en da, y previsiblemente durante un futuro bastante largo, la
compana lder en software empresarial, debido a la robustez de su producto y a sus anos
de experiencia como lder en el mercado. Ademas, como veremos en el captulo siguiente,
se estan dando pasos bastante importantes para resolver algunos de estos problemas.
10
CRM tambien posee una estructura modular, aunque logicamente estos modulos estan
mas centrados en actividades de marketing y ventas. Algunos de los modulos mas impor-
tantes del CRM son:
Ventas
Marketing
Servicios
Analtica
Centro de Interaccion
Canal Web (que esta a cargo de cosas tales como el comercio electronico, marketing
por internet, etc.)
Actualmente SAP es el segundo proveedor de soluciones CRM del mercado, por detras
de SalesForce. [8]
11
finalizados desde el punto de origen hasta el punto de consumicion. Redes interconecta-
das, canales y nodos de negocio estan envueltos en la provision de productos y servicios
requeridos por el consumidor final en una cadena de suministro.
De esta forma, se puede definir el SCM como el diseno, planificacion, ejecucion, control
y monitorizacion de las actividades de la cadena de provisionamiento con el objetivo de
crear un valor neto, construir una infraestructura competitiva, gestionar logstica alrede-
dor del globo, sincronizar oferta y demanda y medir el rendimiento globalmente [1].
Es evidente que el SCM de SAP guarda una gran relacion con los modulos de logstica
del ERP, hasta el punto de que si se tiene uno, normalmente se suele tener el otro,
suponiendo que la empresa sea lo suficientemente grande.
12
2.2. Una breve historia de SAP
A continuacion pasaremos a detallar brevemente la historia de SAP, desde los tiempos
de su fundacion hasta la actualidad. Yendonos a la pagina web corporativa de SAP [10],
vemos que la propia compana divide su historia en 4 etapas, que son:
Presente de la compana
13
En 1981 la base de clientes de SAP se expande a 200 companas, se desarrolla un
modulo de gestion de la produccion y, lo que es mas importante, SAP R/2 pasa a ser
estable, entrando en una nueva era de la compana.
14
debido a un cambio en la legislacion alemana, que requera una supervision mas estricta
de las hojas de balance. El modulo HCM sale al mercado, tras 3 anos de desarrollo.
Al ano siguiente, SAP se expande al Reino Unido, Francia y Espana. A finales de
ano, se tienen mas de 500 empleados y se generan beneficios de 152 millones de marcos.
Ademas, la nueva generacion de servidores IBM hace que el software de SAP sea accesible
a empresas de tamano medio, generando entre 30 y 200 millones de marcos. Es en este
ano cuando se empieza a gestar lo que se convertira en SAP R/3, tras intentar normalizar
los procesos internos de produccion de software.
En 1988, SAP sale en bolsa, aumentando su stock de 5 a 60 millones. La accion sale
en octubre a 750 marcos. Se abren oficinas en Italia, Dinamarca, Suecia y los Estados
Unidos. Se alcanzan 1000 clientes.
Al ano siguiente se introduce una nueva interfaz para SAP R/2, mas amigable y menos
complicada. Tambien se empieza a gestar SAP R/3, con una inversion de 85 millones de
marcos, el 33 % de los beneficios de la compana. El datacenter pasa a tener mas de 1
GB de tamano. Continua la expansion hacia el extranjero, hacia pases como Canada,
Singapur, o Australia.
En 1991, tras la cada del telon de acero, SAP responde con numerosas actividades en
Europa del Este. Se consiguen unos beneficios de 707 millones de marcos y se alcanzan los
2.700 empleados. Durante este ano se empiezan a mostrar al publico algunas pinceladas
de lo que sera SAP R/3, predecesor directo del ERP actual.
15
2.2.3. La era de SAP R/3: 1992-2001
SAP R/3 se caracteriza por introducir la arquitectura cliente-servidor al sistema SAP,
permitiendo que llegue al escritorio. Ademas, soporta bases de datos relacionales, servi-
dores de distintas companas y ofrece una interfaz grafica uniforme.
16
Al ano siguiente, SAP empieza a entrar en el mercado de Stocks de Nueva York
(NYSE). Se consiguen clientes como Daimler-Benz, General Motors, o Deutsche Post
AG. Los beneficios de la compana han crecido en un 62 % hasta alcanzar 6.020 millones
de marcos. SAP cumple 25 anos.
En el 98, Dietmar Kopp y Klaus Tschira, dos de los fundadores, dejan el comite ejecuti-
vo para marcharse hacia el comite supervisor. Esta decision marca un cambio generacional
en la compana. Los directores ejecutivos son ahora Hasso Plattner (otro fundador) y Hen-
ning Kagermann. El 3 de agosto de ese mismo ano, se ven por primera vez las siglas SAP
en el NYSE, el mercado de stocks mas grande del mundo. Se contratan 6.500 emplea-
dos nuevos, trayendo la cifra total a mas de 19.000. Los beneficios en este ejercicio fiscal
superan los 4.300 millones de euros.
En el ano 99, Hasso Plattner anuncia una nueva estrategia que lleva a la compana y a
su flota de productos hacia un nuevo rumbo, se anuncia mySAP.com. Esta reorientacion
combinara soluciones de comercio electronico con las soluciones ERP de SAP mediante
las tecnologas Web.
Tambien cambia la imagen de la compana. En octubre de ese mismo ano, el Bayern
de Munich, MLP, y otros se apuntan a este nuevo servicio. En noviembre y diciembre les
siguen Ford, Hewlett-Packard, y Hoechst Marion Roussel, entre otros.
Los siguientes dos anos ven una expansion de los beneficios de la compana, lo cual es
bastante remarcable teniendo en cuenta el estallido de la burbuja de las punto com y el
17
dano al ecosistema de la economa de internet que provoco. Se generan beneficios de 6.300
billones de euros a finales del ano 2000, y se consiguen clientes como Nestle, que firma el
contrato mas grande de la historia de la compana.
A partir de aqu termina la era de SAP R/3 y comienza la etapa actual de la compana,
con un cambio de nombre de su producto estrella (que pasa a llamarse SAP ERP) y una
apuesta radical por dos nuevas lneas de negocio que se complementan: SAP HANA, y el
Cloud Computing.
Le dedicaremos el siguiente captulo entero a describir el ecosistema actual, y por
que dentro de este nuestro proyecto tiene bastante relevancia y se posiciona positivamente
en la estrategia a futuros de SAP.
18
3. Presente y futuro de SAP. HANA. Cloud Compu-
ting.
En esta seccion pasaremos a detallar la estrategia actual de SAP mediante sus dos
lneas de negocio actuales mas importantes: SAP HANA y las soluciones Cloud.
Estas dos nuevas estrategias, aunque a primera vista pudiesen parecer muy diferentes
(una es una base de datos y la otra es una serie de productos en la nube), se complementan
entre s. Es por eso que no se puede hablar de la una sin la otra.
Dividiremos, por tanto, esta seccion en dos apartados. Uno tratara sobre HANA (as-
pectos estructurales, benchmarks, lenguajes y herramientas asociadas, etc.) y el otro tra-
tara, primeramente de dar una introduccion al Cloud Computing, justificando por que es
tan atractivo para la empresa actual (en especial la PYME), tras lo cual introducira algu-
nos conceptos del Cloud Computing como IaaS, PaaS, o SaaS; y por ultimo hablara sobre
las soluciones Cloud actuales de SAP (centrandonos sobre todo en HCP, ya que es la base
sobre la cual el proyecto tecnico esta fundamentado, pero tambien hablaremos sobre otras
soluciones como SuccessFactors, Ariba, Cloud for Customer, HANA One, etc.).
19
los periodos de inactividad para tomarse un cafe [6]). Esto, a su vez, ha agilizado bastante
la toma de decisiones basada en Big Data a nivel ejecutivo, haciendo que, por ejemplo,
una empresa de inversion sea capaz de decidir en segundos si les conviene o no meterse a
comprar acciones de una empresa, en vez de das (abriendo bastantes posibilidades gracias
a la fusion, ahora posible, de los campos del Big Data y el High Frequency Trading). HANA
es, actualmente, la base de datos mas rapida del mundo con bastante diferencia, llegando
a alcanzar en algunas empresas como Yodobashi una reduccion de tiempo de calculo por
un factor de 125.000 [7].
Esta tecnologa esta poco a poco cambiando las reglas del juego en el campo del
Business Intelligence y en otros aspectos, lo cual le gano a su principal promotor, el doctor
Vishal Sikka, el puesto de Director Ejecutivo de la compana. Este a su vez aprovecho la
marca HANA para promover una renovacion interna bastante importante y un cambio
radical en la forma de trabajar de SAP, un trabajo que su sucesor, Bill McDermott, ha
continuado. Veremos en mas detalle este aspecto del fenomeno HANA en el siguiente
apartado sobre Cloud Computing.
Ahora bien, como se ha conseguido esto? Que es lo que hace que HANA sea una base
de datos tan tremendamente rapida? A continuacion veremos algunos aspectos estructu-
rales que nos ayudaran a comprender por que se pueden conseguir estas velocidades.
HANA tambien puede conectarse a una aplicacion cliente via un modulo opcional
(el motor XS) usando HTTP(S). Esto es util para obtener datos de terceras partes
o datos en varios lugares distintos.
20
Figura 6: Arquitectura de SAP HANA.
3.1.1.5. Motor XS
Las aplicaciones cliente pueden usar HTTP para transmitir datos via el motor opcional
XS, que utiliza SAP ICM como servidor HTTP. Este modulo provee de acceso a los datos
en HANA transformando el modelo de persistencia guardado en la base de datos (SQL)
en un modelo listo para ser consumido por aplicaciones cliente mediante HTTP (OData,
JSON, XML). En esencia, es una integracion de un servidor web en la propia base de
datos.
21
Figura 7: Arquitectura del servidor de indexacion de SAP HANA.
Autenticacion
HANA posee un sistema de privilegios basados en roles y usuarios (aunque los usua-
rios, autorizaciones y roles del ERP no son directamente transferibles a HANA). El
modelo de autenticacion de HANA permite conceder privilegios a roles o a usuarios,
y un privilegio concede a un usuario la capacidad de ejecutar una operacion SQL
sobre un objeto especfico. HANA tambien utiliza una serie de privilegios analticos
22
que representan filtros o limitaciones jerarquicas hacia abajo hacia ciertas queries
analticas, con el objeto de proteger datos clasificados de usuarios no autorizados.
Este modelo fomenta una segregacion de tareas para clientes que tengan requeri-
mientos regulatorios para la seguridad de sus datos.
Procesador SQL
El procesador SQL segmenta las queries de datos y las dirige hacia motores de pro-
cesamiento especializado para aumentar la eficiencia. Tambien provee un corrector
de errores para hacer las queries mas flexibles y eficientes. Algunos de los motores
especializados de procesamiento dentro de esta parte son:
Almacenamientos Relacionales
Para acelerar aun mas el acceso a los datos In-Memory, SAP ha segmentado estos
datos en compartimentos dentro de la memoria. Esto permite un acceso rapido a
los datos mas relevantes. HANA posee cuatro almacenamientos relacionales que
optimizan la eficiencia de las queries:
23
Almacenamiento de Objetos
Es una integracion de la tecnologa Live Cache de SAP en HANA.
Almacenamiento basado en Disco
Es utilizado para datos que no hace falta guardar en RAM. Esta localizado
en un disco duro (como las bases de datos tradicionales) que saca los datos a
RAM cuando hace falta leerlos.
Manager de Transacciones
La base de datos HANA procesa instrucciones SQL como transacciones. El manager
de transacciones controla y coordina sus transacciones y enva los datos relevantes a
los motores de procesamiento apropiados y a la capa de Persistencia. Esta segmen-
tacion simplifica la administracion del sistema y la resolucion de problemas.
Capa de Persistencia
La capa de persistencia prporciona un servicio de recuperacion de datos en caso de
corte de luz u otras emergencias. Los algoritmos y la tecnologa estan basados en
MAX DB y aseguran que la base de datos es restaurada al estado guardado mas
reciente despues de un reinicio, planeado o no. Las copias de seguridad son guardadas
como puntos de recuperacion en los volumenes de datos va un coordinador de
puntos de recuperacion que tpicamente guarda el estado de la base de datos cada
10 minutos. Todos los puntos de cambio que suceden tras un punto de recuperacion
se denominan como transacciones no consignadas y se guardan en los Volumenes
de Logs de transacciones. Tpicamente, estos volumenes son guardados en medios
externos y enviados a otras localizaciones en caso de desastre.
Repositorio
El repositorio gestiona el versionado de objetos de metadatos como pueden ser
los Atributos, las vistas Analticas o los procedimientos almacenados. Se pueden
importar y exportar datos.
24
por los productos de la compana. Esto supone un ejemplo del cambio de la manera de
trabajar de SAP promovido por Vishal Sikka.
INSERT INTO TABLE PERSONAL ( Numero , Nombre , Ciudad ) VALUES (01 , Fulano , Sevilla );
INSERT INTO TABLE PERSONAL ( Numero , Nombre , Ciudad ) VALUES (02 , Mengano , Sevilla );
INSERT INTO TABLE PERSONAL ( Numero , Nombre , Ciudad ) VALUES (03 , Zutano , Sevilla );
En una tabla fila, esto se guarda como: [01, Fulano, Sevilla][02, Mengano, Sevilla][03,
Zutano, Sevilla]. En cambio, en una tabla columna se guarda como [01, 02, 03][Fulano,
Mengano, Zutano][Sevilla(3)]. Como se puede observar, guardandolo como tabla columna
25
conseguimos aprovechar la redundancia en la informacion para reducir el tamano de los
datos guardados. El concepto de tabla columna no existe en SQL nativo. Para conseguir
crear tablas columna se utiliza la instruccion CREATE COLUMN TABLE.
Otro concepto nativo al SQL de HANA es el de las vistas. Hay 3 tipos de vistas:
Analticas, de Atributo, y de Calculo. Por lo general, una vista de Atributo captura un
unico Atributo de una tabla de datos Maestros, de tal forma que ese Atributo pueda
interactuar con todas las otras tablas y vistas del sistema. Una vista Analtica captura
un infocubo (es decir, una serie de atributos con todas las entidades que lo posean). Y
una vista de calculo parte de otras tablas y otras vistas para ejecutar algoritmos que
satisfagan requerimientos de negocio complejos. Para referirse a una vista (que no para
crearla, para eso necesitaremos HANA Studio o SQLScript, que veremos mas adelante)
se utiliza el smbolo ::, que, de nuevo, no esta en la implementacion estandar de SQL.
Y, por ultimo, tambien utilizamos SQL para conceder o quitar privilegios mediante el
comando GRANT, que, de nuevo, no esta en el estandar.
26
que se pueden implementar algoritmos de calculo complejos, y guardarlos en un wrapper
al que podemos llamar mediante SQL. Es, logicamente, mas dificil de manejar que SAP
HANA Studio, pero permite hacer calculos muchsimo mas complejos. Esto es debido,
entre otras cosas, a que mediante SQLScript se pueden crear wrappers de los algoritmos
almacenados en la Librera de Analisis Predictivo, o PAL [13] (de Predictive Analysis
Library). Esta librera posee algoritmos complejos para tareas como la prediccion de series
temporales (ARIMA, Suavizado Exponencial Triple), el clustering (k-means, mapas auto
organizados de Kohonen), la clasificacion y toma de decisiones (C4.5), y demas. Estos
algoritmos combinados con la velocidad de HANA son una herramienta muy poderosa,
pudiendo hacer segmentaciones de mercado, analisis de hashtags corporativos en Twitter,
relaciones de compras entre los productos de una compana, y predicciones de tendencias
en ventas, entre otras muchas cosas, en segundos.
Pero con eso y todo, el numero de algoritmos actualmente disponibles en la PAL
es limitado. Es por eso que disponemos de una herramienta aun mas poderosa si cabe,
conocida como R. R es un lenguaje estadstico de codigo abierto muy poderoso, hasta
el punto de que se ha impuesto a otros como SPSS y demas. HANA no es capaz de
interpretar R nativamente, pero mediante Rserver, puede transmititir los datos necesarios
a un servidor externo que s lo pueda interpretar, y este puede devolver los resultados [14].
Desafortunadamente, dado el volumen de datos con el que se suele trabajar en HANA,
tener que mandarlos hacia un servidor externo y traer los resultados de vuelta puede
suponer un cuello de botella bastante importante, dependiendo del volumen de datos
necesario para hacer el calculo. Pero aun as, para operar con algoritmos matematicos
muy complejos, esta es la manera de hacerlo.
27
Figura 9: Estructura de archivos de un proyecto HANA XS, mostrado en Eclipse.
Figura 10: Captura de pantalla de una aplicacion web desarrollada en HANA XS, usando
SAPUI5.
28
3.1.3. Benchmarks de HANA
Si se tiene que sacar una unica conclusion de los dos subapartados anteriores, proba-
blemente sera esta: HANA es un aparato complejo y laberntico. Sin embargo, a pesar de
la dificultad intrnseca que supone desarrollar sobre esta plataforma, sigue siendo a da
de hoy la tecnologa con mas rapido crecimiento de SAP. Por que ocurre esto?
Como ya hemos adelantado en la introduccion a este apartado, es por la velocidad que
se puede llegar a alcanzar. Fijandonos en los benchmarks de HANA One [12], podemos ver
algunas cifras que nos ayudaran a ver exactamente de que ordenes de magnitud estamos
hablando.
Se empleo para este test una estrucuras de tabla creada durante diez anos de uso por
un modulo SD (Sales & Distribution) de una empresa cuyo nombre no se menciona en
el documento. Esta estructura estaba compuesta por una Fact Table, con 1,2 billones
de lneas, y varias otras tablas mas pequenas. El tamano original de los datos era de 1
PetaByte, o 1.024 TeraBytes. El sistema HANA que se utilizo para procesar esta informa-
cion estaba compuesto por 95 nodos (es decir, 95 aparatos HANA trabajando en paralelo).
Cada uno de estos nodos posea 4 CPUs con 10 nucleos por CPU y 2 Hyper-threads por
nucleo, 1 TB de RAM y 3.3 TB de Disco Duro.
Este volumen de datos se cargo en el sistema HANA usando 9 hilos distintos de sta-
tements SQL, llegando a cargar 53 millones de lneas por minuto. Una vez cargado en el
sistema HANA, este utilizo la tecnica de compresion que hemos detallado anteriormente
para aprovechar la redundancia existente en las columnas de la tabla. De 1 PB, la estruc-
tura fue comprimida a 49.2 TB, un factor de compresion de 20.8. Esto significo que se
pudieron guardar todos los datos en RAM, y lo que es mas, que solo ocuparon la mitad
de esta en cada nodo.
Una vez los datos fueron cargados y comprimidos, se les hicieron una serie de queries
empleadas frecuentemente en Business Intelligence, de Reporting, Drilldown y Analti-
ca. Todas ellas tardaron menos de 3.5 segundos en ejecutarse, menos de un segundo si
ignoramos las queries de analtica.
Como se puede ver, los resultados alcanzados son absolutamente monstruosos, ejecu-
tando queries que podran haber tardado semanas o incluso meses en otras bases de datos
en menos de 5 segundos. Es por esto que SAP esta empleando la marca HANA como un
ariete para intentar hacerse con un puesto de liderazgo en el mercado Cloud, utilizan-
do una estrategia agresiva de compra de soluciones existentes y desarrollo de soluciones
nuevas. Veremos en mas detalle esto en el siguiente apartado.
29
3.2. Cloud Computing
En esta seccion procederemos a hablar del Cloud Computing, introduciendo primero
algunos conceptos basicos, el impacto disruptivo que esta teniendo en el ecosistema em-
presarial de las tecnologas de la informacion, y despues pasaremos a detallar la estrategia
de SAP con respecto a esta nueva manera de ofrecer servicios, tras lo cual habremos
detallado en suficiente detalle el contexto alrededor del cual el proyecto tecnico se ha
desarrollado.
3.2.1.1. onPremise
Un servicio onPremise es un servicio cuya infraestructura fsica esta en manos de la
empresa que lo utiliza. Por ejemplo, un servidor en la empresa que tenga aplicaciones de
correccion de bugs, mantenimiento y versionado de codigo fuente, etc.
30
3.2.1.2. onCloud
Un servicio onCloud es aquel que es proporcionado por una empresa en la nube, y cuyos
servidores no estan en las Premisas de la compana cliente. Hay tres modalidades de
servicios onCloud:
Infrastructure as a Service
Es la forma mas basica de Cloud Computing. Consiste en arrendar al cliente el uso
de una maquina (ya sea fsica o virtual) en la nube. El proveedor se encarga de
mantener la infraestructura y el cliente ha de encargarse de todo lo demas, desde la
instalacion del sistema operativo hasta la gestion y mantenimiento del software que
haya decidido poner. Un ejemplo de IaaS es CloudFlare, que te da un servidor en la
nube mediante el cual puedes crear y exponer diversas aplicaciones web a la nube.
Platform as a Service
En este servicio se ofrece la infrastructura y ademas se ofrecen una serie de herra-
mientas de software, tales como sistemas operativos, compiladores, y demas. Este
servicio elimina la necesidad de contratar a administradores de sistemas, siendo ne-
cesarios unicamente los desarrolladores de aplicaciones web para montar una apli-
cacion sobre la plataforma.
Software as a Service
Esta modalidad de servicio nos ofrece ya directamente una aplicacion software desa-
rrollada por el proveedor en la nube. Ejemplos de este tipo de servicio podran ser
Gmail u Outlook (en contraposicion a aplicaciones como Thunderbird o Maildroid),
Google Docs (en contraposicion a Office), etc.
3.2.1.3. REST
REST son las siglas de Representational State Transfer, Transferencia de Estados de
Representacion en espanol. Es, esencialmente, una manera de interactuar con una base de
datos en remoto mediante un endpoint HTTP, con protocolos como SOAP u OData. Esto
quiere decir, que podemos, por ejemplo, anadir una lnea a una tabla de nuestra Base de
Datos de interes llamando mediante un mensaje POST al servicio OData (por ejemplo)
conectado a esta, con los datos que queremos insertar codificados como parametros en
la URL empleada para la llamada. En otras palabras, es una manera de interactuar en
remoto con una base de datos mediante llamadas a la URL del servicio conectado a ella,
en vez de utilizar SQL.
3.2.1.4. MVC
MVC (siglas de Modelo, Vista, Controlador) es un patron de diseno de aplicaciones web,
que aboga por separar la logica de negocio de la aplicacion (contenida en el controlador),
la interfaz con el back-end y la base de datos (contenida en el modelo), y la interfaz con
31
el usuario de la aplicacion (contenida en la vista). Por motivos estructurales evidentes,
tras pensarlo, el modelo siempre suele residir en el servidor, mientras que la vista suele
residir de lado cliente y el controlador puede estar en ambos, dependiendo del volumen
de informacion que tenga que intercambiar con el modelo o con la vista. En nuestro caso
el controlador reside en el lado del cliente.
32
Ademas de esto, mediante HANA Cloud Platform podemos acceder a servicios como
HCI OData Provisioning, capaz de exponer BAPIs a la nube mediante formato OData, o a
servicios de autentificacion como OAuth o SAML. Es, en esencia, una caja de herramientas
asociada a HANA, mediante la cual se pueden implementar funcionalidades que no se
podran implementar utilizando solo HANA (como veremos mas adelante).
Actualmente HCI todava se esta haciendo un hueco en el mercado, y suele ser emplea-
do para implementar extensiones a SuccessFactors, comunicandose con este directamente
mediante APIs, o utilizando serrvicios como HANA Cloud Integration.
33
cessFactors ha sido una de las mas exitosas en este sentido, lo que le valio a su Director
Ejecutivo una plaza en el consejo ejecutivo de SAP.
3.3. Conclusiones
Como se puede observar mediante la seccion anterior, este cambio tan radical de estra-
tegia esta generando un nuevo mercado en falta de nuevos profesionales con conocimientos
especializados, lo cual abre una serie de posibilidades para las empresas de consultora
dedicadas a SAP. En concreto, estamos empezando a ver cada vez mas proyectos rela-
cionados con la migracion de datos de sistemas onPremise hacia sistemas onCloud, con
HANA, y con otras soluciones como Fiori o Lumira. Para mantenernos al da con las habi-
lidades y conocimientos que requiere el mercado, hemos desarrollado una aplicacion, que,
mediante una interfaz web, es capaz de subir nuestras tablas de datos desde nuestro ERP
hasta nuestra instancia de HCP, y procesarlos. En el siguiente apartado veremos como
hemos desarrollado esta aplicacion, que tecnologas y herramientas han sido necesarias, y
que somos capaces de hacer con ella.
34
4. Proyecto tecnico. Flujo de integracion de datos
entre un ERP y HANA Cloud Platform.
En esta seccion pasaremos a detallar los detalles relacionados con las tecnologas y la
arquitectura necesaria para implementar un flujo de integracion entre un ERP y HANA
Cloud Platform. Podemos dividir el trabajo realizado en tres modulos:
Modulo onPremise: Cloud Connector, ABAP (RFC)
35
Figura 11: Arquitectura de la aplicacion desarrollada.
para, por ejemplo, poder visualizar la estructura organizativa de una empresa desde cual-
quier parte y poder visualizar una ficha resumen de cualquier empleado de la compana,
herramientas que resultaran utiles para, por ejemplo, ejecutivos de una compana grande.
En las siguientes secciones, pasaremos a detallar las tecnologas utilizadas para hacer
posible estas funcionalidades, por que han sido elegidas, y como interactuan entre s.
36
en el ERP para funcionar, siendo simplemente un intermediario (o middleware) entre las
propias capacidades de transmision de datos en remoto del ERP y HCP. Sin embargo,
Cloud Connector no esta pensado, en un principio, para la transmision de datos, sino
mas bien para poder ejecutar codigo en remoto usando una aplicacion web. Veremos mas
adelante como hemos sido capaces de superar esta limitacion.
37
La instalacion de Cloud Connector resulto ser relativamente sencilla, no muy distinta
de la instalacion de cualquier otra aplicacion en Windows. Tras la instalacion, pudimos
acceder a la interfaz de configuracion de Cloud Connector accediendo en el navegador a
la direccion https://localhost:8443.
38
argumento unicamente su nombre. Veremos en el siguiente apartado que tipo de funcion
es necesaria para poder realizar esta tarea.
4.2.2.1. Tablas
La estructura de tablas es la mas universal y utilizada en el ERP, de tal forma que si po-
demos subir nuestras tablas al ERP, podremos subir la mayora de datos que contiene. Las
tablas del ERP pueden ser observadas mediante la transaccion SE11 (Una transaccion es
la ejecucion de un programa. Hay varios tipos de transacciones, para registrar materiales,
contratar a un empleado, etc. Esta en concreto sirve para visualizar tablas).
Por suerte hay una funcion RFC ya includa en SAP capaz de tomar como argumento
el nombre de la tabla y devolver sus datos, conocida como RFC READ TABLE . Esta
funcion, aunque no sea ya mantenida por SAP, es sin embargo capaz de extraer datos de
nuestras tablas y exponerlas al modulo de integracion.
Sin embargo, tenemos varios problemas. Uno de ellos es que el formato en el que
mandamos los datos no se corresponde con ningun estandar, con lo cual nos resulta
39
difcil procesarlos y mandarlos a la base de datos. El segundo, mas acuciante, es que
RFC READ TABLE tiene un lmite de 128B por lnea. Esto, teniendo en cuenta que
muchas tablas en SAP pueden poseer mas de 50 columnas, es una limitacion bastante
importante. El primero de estos problemas sera tratado en el modulo de integracion, pero
el segundo tenemos que tratarlo aqu. Basicamente tenemos dos soluciones:
Eventualmente elegimos la segunda opcion, por ser mas sencilla de programar. En-
contramos un sustituto ideal en la funcion /BODS/RFC READ TABLE2. Sin embargo,
esta funcion resulto no estar por defecto en el ERP. Tuvimos que instalar un modulo de
funcion en el ERP, para lo cual empleamos la transaccion CG3Z para subir los archivos
necesarios al servidor, y despues tuvimos que utilizar el sistema de transporte y correccion
(CTS) para poder acceder a ellos mediante el ERP. Esto aumento nuestro lmite de 128B
a 30KB, mas que suficiente para casi cualquier tabla.
Llamando a esta funcion en remoto y pasandole el nombre de la tabla que queremos
subir, podemos obtener los datos que queremos.
4.2.2.2. Clusters
Un cluster es un tipo especial de tabla, que contiene una gran informacion sobre un
objeto de negocio (como un empleado, un material, etc). Esto hace que, por eficiencia, se
escriban de manera comprimida a la base de datos, lo cual acelera bastante la extraccion
40
de datos mediante una clave primaria y, lo que es quiza mas importante para nuestros
propositos, hace que este tipo de tablas no son visibles directamente mediante SE11, con
lo cual no podemos utilizar /BODS/RFC READ TABLE2. Esto implica que tendremos
que utilizar otro tipo de funciones.
Nos interesan en este caso los clusters salariales, con lo cual, tras investigar, hemos
visto que las funciones PYXX GET RELID FROM PERNR y CU READ RGDIR son
ideales para nuestros propositos, ya que pasando como parametro el numero del em-
pleado del que queremos observar los datos, se nos devuelve una estructura con to-
dos los clusters asociados a su historial salarial. Despues de eso empleamos la funcion
PYXX READ PAYROLL RESULT, para poder acceder a una vista mas detallada de los
periodos salariales del empleado en cuestion.
Para ser mas exactos, mediante la primera funcion obtenemos el ID relacional de la
estructura de clusters en la que se hallan los datos que nos interesan, pasando como
parametro el numero de empleado. Hecho eso, utilizamos el numero de secuencia de la
entrada del historial salarial sobre la que estemos mas interesados como argumento para
pasarle a la tercera funcion.
Sin embargo tuvimos un problema. Estas funciones estan disenadas para extraer datos
y presentarlos a otros procesos del propio ERP. Es decir, no son capaces de comunicarse
mediante RFC. Para solventarlo, tuvimos que crear una funcion Z que llamase a estas
funciones internamente y que s fuese capaz de utilizar RFC.
Que es una funcion Z? Es una funcion disenada internamente por la empresa usuaria
del ERP u otra a la que esta haya contratado, para su uso propio. Obviamente este tipo
de funciones no estan por defecto en el sistema SAP ni son soportadas por la compana,
pero son esenciales en cualquier ERP ya que mediante este tipo de funciones se puede
ajustar el ERP (o parametrizar, como se le conoce a esta practica en el mundillo de la
consultora SAP) a las necesidades especficas de cada cliente. A este tipo de funciones se
le llaman funciones Z debido a que su nombre suele empezar por Z, ya que SAP ya desde
sus inicios se comprometio a no implementar nunca un modulo de funcion que empezase
por esta letra, para evitar conflictos de nombres.
Para crear una funcion Z hemos de movernos a la transaccion SE80, el editor de
objetos, que es la transaccion mas utilizada para cualquier programador ABAP. Mediante
esta transaccion creamos primero un grupo de funciones, al que denominamos Z RFC HR,
en el que depositaramos estas funciones que emplearemos a modo de wrapper, y todas
las demas que nos vayan a hacer falta en un futuro.
Una vez creado este grupo de funciones, empezamos a poblarlo con 2 funciones Z pro-
gramadas por nosotros, Z RFC READ PAYROLL ENTRIES, y Z RFC READ PAYROLL
RESULTS. La primera de estas funciones actua como wrapper RFC para las funcio-
nes PYXX GET RELID FROM PERNR y CU READ RGDIR, mientras que la segunda
actua como wrapper para PYXX READ PAYROLL RESULT. El codigo fuente de estas
funciones esta disponible en el anexo.
41
Utilizando estas dos funciones, somos capaces de extraer los datos salariales de cual-
quier empleado usando solo el numero asociado a el, con lo cual ya somos capaces de
extraer clusters desde nuestro ERP.
Este tipo de estructuras de datos estan tambien guardadas como tablas en SAP. Sin
embargo, estan guardados de una forma que dificulta bastante la reconstruccion de los
arboles organizativos que querremos visualizar mediante el modulo de presentacion. Estos
arboles se pueden visualizar mediante la transaccion PPSM.
Ahora bien, que es un arbol organizativo? Un arbol organizativo muestra por lo
general una estructura jerarquica dentro de la empresa. Puede ser una estructura de
los distintos departamentos, de los centros de compra, de las empresas asociadas a una
corporacion, etc.
Existe una funcion que, tomando como argumento la ID del objeto de negocio que
queremos como nodo raz de este arbol, y el tipo de objeto que es, procesa las tablas
en las que esta guardada la informacion necesaria y las devuelve en un formato mas
apropiado para reconstruir el arbol. Esta funcion se llama RH STRUC GET, y sobre ella
esta fundamentada la transaccion PPSM. De nuevo, contamos con el mismo problema
42
Figura 17: Ejemplo de arbol de estructura organizativa.
que antes, esta funcion es incapaz de transmitir datos mediante RFC. Y, de nuevo, la
solucion pasa por crear otra funcion wrapper que internamente la llame y que s tenga
RFC activado. Hemos llamado a esta funcion Z RFC HR READ ORG STRUCTURE, y
hemos hecho que resida en el mismo grupo de funciones que creamos para el apartado
anterior, Z RFC HR. El codigo fuente de esta funcion estara tambien detallado en el
anexo.
43
Figura 18: Diagrama de flujo de datos del modulo onPremise.
44
acceder, como HANA o el ERP.
4.3.1. ControllerServlet
Ya en la introduccion adelantabamos la primera pregunta que tenemos que hacernos:
que es un Servlet?
Un servlet es una clase Java utilizada para extender las capacidades de un servidor.
Aunque un servlet puede responder a varios tipos de peticiones, son frecuentemente uti-
lizados para extender aplicaciones en servidores web, con lo que se los puede considerar
como applets que corren en servidores, en vez de en los navegadores de internet. Estos
tipos de servlet existen como contrapartida a otras tecnologas de Web dinamicas como
PHP o ASP.NET.
La URL asociada al servlet depende de varios factores. La primera parte de la URL,
el dominio, depende de la direccion IP del servidor sobre el que nuestro servlet resida, o el
nombre que se le haya dado por los servidores DNS. La segunda parte, el path, depende
del programador del servlet. Un servlet generalmente se genera mediante un proyecto de
web dinamico, en Eclipse. Este proyecto consiste en una serie de directorios para guardar
codigo fuente Java, o Javascript, o el archivo war compilado, entre otros.
Entre esta serie de directorios se encuentra uno, al que se conoce como WebContent/WEB-
INF. Este directorio tiene la propiedad especial de que cualquier recurso guardado en el no
es directamente accesible desde el exterior, lo cual lo hace popular para guardar imagenes,
libreras especficas para el proyecto, archivos de base de datos, y cualquier otro documen-
to que no queramos que el usuario pueda descargar directamente. Pero mas alla de eso,
tambien tenemos un archivo generado automaticamente, conocido como web.xml. Este
archivo se encarga de mapear el path de la URL a la clase responsable de manejar las
peticiones que le lleguen a este path. Es decir, mediante este archivo podemos exponer
nuestro codigo al exterior. Ademas de eso podemos definir otra serie de cosas como filtros,
recursos, y demas.
Nuestra clase Servlet se compone de un metodo de inicializacion, en el que iniciali-
zamos los objetos hanaDAO y abapDAO, el contexto inicial, y la fuente de datos que
manipularemos (HANA, en este caso).
Y luego tenemos un metodo al que llamamos (al que tenemos que llamar) doPost. Este
metodo se ejecuta cuando llamamos a la URL definida en WEB-INF mediante el metodo
POST de HTTP. Dado que solo tenemos este metodo definido, esto quiere decir que otros
metodos de llamada, como GET o DELETE, no tendran ningun efecto. En los anexos se
podra encontrar el codigo fuente de WEB-INF.xml y ControllerServlet.java, en el que se
puede uno apoyar para ver los conceptos explicados en mas detalle.
Por que solo empleamos el metodo POST? Debido a la funcion de nuestro modulo.
El metodo POST sirve para pedir que un servidor web acepte los datos que esten en el
cuerpo del mensaje y los guarde [9], que es en esencia la tarea del modulo de integracion.
45
Sin embargo, tenemos que responder a varios tipos de peticiones distintas, en funcion
del tipo de datos que queramos subir. Como hacemos esto? Mediante la inclusion de
parametros en la URL, en la forma application/x-www-form-urlencoded. Recordemos
del apartado anterior que tenamos 3 tipos de datos que necesitabamos subir: tablas,
clusters, y estructuras organizacionales, para lo cual necesitabamos llamar a 4 funciones
distintas.
Para que el servlet sepa a que funcion tiene que llamar en que momento, hacemos que el
servlet capture los parametros mandados a el codificados en la URL, entre los cuales tiene
que estar uno conocido como functionname. Este parametro, como su propio nombre
indica, contiene el nombre de la funcion a la que el servlet tiene que llamar en el ERP.
Mediante este parametro, hacemos que el Servlet llame a abapDAO, que es el que se
encarga de interactuar con el ERP, saque los datos correspondientes, los transforme, y
los traspase a hanaDAO. AbapDAO se encarga de extraer los datos en formato nativo, y
HanaDAO se encarga de transformarlos en statements SQL que la base de datos pueda
entender. Hablaremos de esto en mas detalle en los siguientes apartados.
Una ultima cosa que comentar es la siguiente. El modulo de integracion y el modulo de
presentacion (la aplicacion web) estan en dominios distintos. Esto quiere decir que, debido
a la poltica de mismo origen, una poltica de seguridad implementada por todos los
navegadores modernos, no podemos servir la aplicacion web y los datos necesarios desde
dos dominios distintos, pues el navegador lo bloqueara. Vamos a tener que burlar esta
poltica de alguna forma. Hablaremos en mas detalle de esto cuando tratemos la aplicacion
web, por el momento solo diremos que hay varias formas de conseguir esto. Una de ellas,
y la recomendada por SAP, es la tecnica de proxy inverso, mediante la cual cambiamos
mediante un componente intermedio la URL del destino al que queramos conectarnos, de
tal forma que el navegador vea el mismo dominio en ambas partes de la comunicacion y no
aborte la operacion. Tenemos la ventaja, al utilizar este metodo, de que solo necesitamos
controlar el lado cliente para hacerlo funcionar, pero el inconveniente de que puede ser
complicado ponerse a programarlo. Es por ello que hemos utilizado CORS.
CORS (Cross-Origin Resource Sharing) es un protocolo desarrollado por la W3C me-
diante el cual el navegador es capaz de ignorar la poltica de mismo origen. Este metodo
tiene el inconveniente de que necesitamos controlar ambos puntos de la comunicacion, el
cliente y el servidor, pero tiene la ventaja de que es mucho mas sencillo de implementar.
Unicamente tenemos que anadir un header a las respuesteas de nuestro modulo, que es:
Access-Control-Allow-Origin: *. Tambien tenemos que anadir un parametro en las
llamadas al modulo de integracion desde la aplicacion web, pero eso lo veremos en la
seccion correspondiente.
4.3.2. AbapDAO
Antes de empezar a comentar el funcionamiento de este objeto, tenemos que saber
que es exactamente un Objeto de Acceso a Datos (Data Access Object en ingles). Se
46
define un DAO como un objeto que proporciona de una interfaz abstracta hacia algun
tipo de Base de Datos u otro mecanismo de persistencia de datos. Al mapear las llamadas
a los metodos de este objeto a distintas operaciones en la capa de aplicacion, el DAO
proporciona algunas operaciones de datos especficas sin exponer los detalles de la base
de datos a ControllerServlet. Este aislamiento implica que si necesitasemos cambiar de
base de datos, solo tendramos que modificar el DAO asociado a ella, dejando el resto
intacto. Mediante esta separacion de codigo, se obedece a una de las buenas practicas de
programacion orientada a objetos, el principio de responsabilidad unica, que establece
que cada objeto que compone un programa ha de cumplir una funcion especfica, y todos
los metodos y servicios asociados a el deberan estar relacionados con esa funcion.
AbapDAO, como ya hemos adelantado anteriormente, es una clase a la que se llama
desde ControllerServlet cuando se quieren extraer datos de nuestro ERP. Ahora bien,
ya vimos en el apartado anterior que nuestro ERP solo entiende ABAP, mientras que
nuestro programa esta codificado en Java. Es decir, tenemos que ejecutar codigo ABAP
desde Java. Como conseguimos hacer esto?
Existe una clase Java capaz de ejecutar codigo ABAP en una destinacion remota,
conocida como Java Connector, o JCo.
Para poder llegar al ERP, desde la maquina virtual de Java, recuerdese que en el
apartado anterior mencionamos que mapeamos el end-point HTTP real mediante el que
nos conectamos al ERP por uno virtual que exponemos a la nube, que en este caso hemos
llamado abapserver.hana.cloud:sapgw42. Ahora, tenemos que crear un destino JCo
que coja este mapping virtual y le de un nombre en el espacio nominal de destinos de
HCP, un protocolo de conexion (en este caso RFC) y una serie de parametros distintos
mediante los cuales HCP pueda conectarse al otro endpoint del tunel creado por Cloud
Connector. En este caso le damos el nombre JCoDemoSystem.
Con este destino creado, la variable JCoDemoSystem es inicializada automaticamente
en el espacio Java, con lo cual podemos acceder al destino y ejecutar statements JCo sobre
el, que el ERP entiende como llamadas a funcion mediante RFC. De esa forma, cuando le
llega la orden desde ControllerServlet de ejecutar una funcion ABAP en remoto, utiliza
JCo para importar la lista de parametros que hay que pasarle, transforma variables Java
a parametros JCo, ejecuta la funcion, y devuelve las tablas requeridas en una estructura
a la que hemos llamado AbapData, que consiste de:
getTable
47
Figura 19: Servicio de destinos de HANA Cloud Platform.
getPayrollHistory
getPayrollResult
getOrganizationalStructure
Como se puede observar, cada uno de estos metodos se corresponde con las funciones
en el ERP que describimos en la seccion anterior, de tal forma que ControllerServlet solo
tiene que llamar a estos metodos para recibir los datos, encargandose AbapDAO de los
detalles especficos de la conexion entre ambos.
4.3.3. HanaDAO
HanaDAO es un objeto de acceso a datos como AbapDAO, pero en vez de interactuar
con el ERP, interactua con HANA. Para ello, utiliza Java Database Connectivity, mas
conocido como JDBC.
JDBC es una tecnologa de conexion de datos desarrollada y soportada por Oracle.
Es una API para Java que define como un cliente puede acceder a una base de datos.
Provee metodos para escribir datos en una BBDD. Esta orientada hacia bases de datos
relacionales.
48
En nuestro caso, usamos JDBC para crear y poblar tablas cuya estructura y tipos de
datos son iguales (o, al menos, lo mas parecidos posible) a nuestras tablas y clusters en
el ERP. Para ello, como suceda con JCo, JDBC actua a modo de interfaz entre nuestro
espacio Java y la base de datos, que entiende SQL, como ya vinimos adelantando en el
captulo anterior.
Los metodos que posee HanaDAO (aparte del metodo constructor y otros de baja
relevancia para nuestros propositos) son los siguientes:
checkTable
createTable
existsTable
addRow
typeMapping
Por partes:
createTable se encarga de crear una tabla mediante el comando CREATE TABLE, to-
mando como parametros el nombre de la tabla (suplido por el propio ControllerSer-
vlet), el nombre de las columnas y sus tipos de datos (entregados por AbapDAO,
utilizando la estructura de datos descrita en el apartado anterior). Para transfor-
mar los tipos de datos propios de ABAP a los tipos de datos propios de HANA
empleamos el metodo typeMapping.
addRow se encarga de anadir una fila a nuestras tablas tomando como parametros
el nombre de la tabla, el nombre de las columnas y sus tipos de datos, y la fila que
queramos subir, en formato String[]. Para ello, usa la instruccion SQL INSERT INTO.
Dado que solo sube una fila (como su propio nombre indica) es utilizada muchas
veces dentro de un bucle for, para poder subir varias filas a la vez.
De nuevo, ControllerServlet solo ve los metodos servidos por HanaDAO, con lo que no
necesitamos crear statements SQL desde all. Si cambia la especificacion de HANA, por
tanto, solo tendramos que modificar HanaDAO.
JDBC es una solucion de bajo nivel, comparada con otras como EJB o Hibernate,
que abstraen la necesidad de usar SQL. Sin embargo, es necesario hacerlo con SQL, dado
49
que a diferencia de otras aplicaciones web donde conocemos las estructuras de las tablas
que queremos manipular (como por ejemplo una tabla en la que se guarde el email, la
contrasena y el nombre de usuario de todas las personas que se vayan registrando), aqu,
por la propia naturaleza del proyecto, hemos de operar contando con que es imposible
conocer de antemano las estructuras de todas las tablas que posee el ERP, lo cual hace
imposible trabajar con ORM (Object-Relational Mapping, que transforma objetos Java
en Entidades de Bases de datos directamente).
50
Figura 20: Diagrama de proceso del modulo de integracion.
51
4.4. Modulo de presentacion onCloud: Aplicacion Web
Este modulo es, de todos ellos, el mas complejo, debido a varios motivos.
El primero y mas fundamental es la estructura de paquetes de HANA XS, en el que
reside nuestra aplicacion. Debido a la complejidad de la estructura de permisos y privi-
legios de HANA y a la cantidad de parametros que hay que definir para que el sistema
reconozca los archivos que hay que mandar y como hay que mandarlos, dedicaremos la
primera parte de este apartado a hablar de estos temas.
El segundo es debido a la complejidad de la pagina web desarrollada en s. Tenemos
4 pantallas de presentacion distintas (tablas, historial de nomina, resultados de nomina y
arboles de estructura organizativa) y un shell comun a todas ellas para poder navegar de
una a otra, lo cual hace 5 pares de Vista-Controlador, o 10 archivos de Javascript de lado
cliente. Ademas de eso esta el modelo, en el lado servidor, que se encarga de transformar
los datos de formato SQL a JSON y mandarlos al navegador.
Es decir, tenemos 11 archivos distintos de codigo fuente, sin contar con la parte an-
terior. Aun as, dado que estamos hablando del diseno de una pagina web, algo bastante
mas cercano al da a da que un ERP o un flujo de integracion de datos, la comprension
de lo que hace cada fichero en este apartado no debera ser demasiado complicada.
52
archivo se puede ver en el anexo.
El tercero de ellos es .xsprivileges. En el simplemente declaramos un perfil de privilegios
al que hemos denominado Basic. Sin embargo, si lo dejamos as es un perfil vaco, ya
que no lo hemos asignado a ningun usuario ni le hemos concedido ningun privilegio al
perfil.
Tal y como estan las cosas, si intentasemos escribir a la base de datos fallaramos.
Tenemos que contactar con la seccion de autenticacion de HANA, para lo cual tenemos
que emplear SQL.
El primer paso consiste en concederle privilegios al perfil Basic que tenemos declara-
do. Para ello, tenemos que crear otro archivo de texto, al que llamamos model access.hdbrole.
En este archivo, creamos un rol, al que hemos llamado model access. A este rol le asig-
namos el perfil de privilegios Basic. Y declaramos que el perfil Basic tiene potestad
para leer, actualizar, escribir, y borrar datos en el schema en el que estamos guardando
nuestras tablas, que esta asociado a nuestro modulo de integracion (el nombre del schema
es NEO 2YRCM3I3BM65X2EPAL42IELVB).
Sin embargo no hemos terminado. Hemos creado un perfil de privilegios, le hemos
asociado algunos privilegios y lo hemos asignado a un rol de usuario. Pero todava no
hemos asignado el rol de usuario a ningun usuario.
Para poder hacerlo, tenemos que hablar con el servicio de autenticacion de HANA,
para lo cual sera necesario utilizar SQL. Para asignarlo, nos tenemos que conectar desde
nuestro entorno de desarrollo a nuestra cuenta en HANA, tras lo cual tenemos que abrir
una consola SQL. Y, dentro de esta consola SQL, tenemos que asignar el rol creado a
nuestro usuario en HCP (cuyo nombre es P1940528821) mediante el comando:
CALL "HCP"."HCP_GRANT_ROLE_TO_USER"
(p1940528821trial.myhanaxs.jcodata::model_access,p1940528821)
Una vez hecho esto, ya s tenemos cumplidos nuestros objetivos. HANA XS reconoce
a este paquete como un proyecto Web SAPUI5, se puede acceder a este paquete desde el
exterior, y podemos editar nuestro schema, en el que guardamos nuestras tablas, a traves
de el.
Ahora lo que tenemos que ir haciendo es posicionar nuestro codigo fuente de tal for-
ma que se sirva una pagina web completamente funcional cuando nos conectemos a este
paquete. Empezaremos por el primer archivo que tenemos que darle al navegador, in-
dex.html.
53
el navegador, en los cuales esta toda la informacion necesaria para desplegar la pagina
web con la que el usuario va a interactuar. Apoyandonos en el codigo fuente incluido en
el anexo, vemos que las primeras lneas de la cabecera del documento se dedican a la
codificacion de los caracteres y al modo de compatibilidad de la web, los cuales no son
muy relevantes, y a la inclusion de un archivo CSS, del cual hablaremos mas adelante.
Despues de eso, nos dedicamos a incluir libreras Javascript includas en el propio
paquete, como jsPDF o FileSaver, que nos serviran mas adelante para generar un pdf con
la nomina de los trabajadores.
Por ultimo, inclumos SAPUI5. SAPUI5 es un framework de Javascript desarrollado
por SAP, que proporciona una serie de assets disenados especficamente para crear aplica-
ciones web de negocios. Al estar desarrollado por SAP, ofrece un gran nivel de integracion
con sus soluciones anteriores, lo cual nos facilita bastante la tarea a la hora de programar
esta aplicacion. Ademas de incluir la librera, especificamos un estilo de pagina (bluecrys-
tal), y una serie de libreras internas (como commons, mediante la cual se accede a
los assets de UI5 para equipos de escritorio, o viz, que nos permite mostrar graficas a
partir de nuestros datos).
El siguiente script nos sirve para definir los directorios en los que estan nuestros pares
VC, declara un modelo JSON y lo asocia al nucleo de la aplicacion web para darles a
las variables que hay dentro de el visibilidad global (aunque hemos de destacar que este
modelo no se corresponde con el modelo tpico en el patron de diseno MVC, ya que no
actua de interfaz con ninguna base de datos. Simplemente es un objeto JSON en el que
podemos guardar variables globales, lo cual nos sirve para transferir informacion entre
pares distintos de VC).
Por ultimo cogemos el shell que hemos codificado en mainView y lo ponemos en
content, que es, como se puede ver mas abajo, el cuerpo de nuestro HTML.
Con esto ya hemos descrito la funcionalidad principal de nuestro archivo HTML, con
la excepcion del archivo CSS. CSS son las siglas de Custom StyleSheet, y en este caso lo
estamos utilizando para personalizar algunos assets que SAP nos proporciona en SAPUI5.
Por poner un ejemplo, si queremos poner un fondo blanco sobre una lnea de texto,
SAPUI5 no nos deja hacerlo por defecto. Si queremos anadir margenes entre distintos
elementos en un horizontalLayout, UI5 no nos deja tampoco hacerlo por defecto. Lo que
s nos deja hacer UI5, es inyectar estilos CSS mediante el metodo .addStyleClass().
En el archivo CSS que hemos creado tenemos definidos dos estilos, myWhiteCellStyle y
myTextFieldMargin, cuyos efectos son los que hemos expuesto como ejemplos mas arriba:
dar un background blanco al texto y anadir margenes.
Con esto hemos explicado la funcion de los archivos HTML y CSS de nuestra aplicacion
web. En el siguiente apartado empezaremos a explicar el primero de los pares VC que
hemos anadido en el cuerpo de nuestra aplicacion: mainView.shell.
54
4.4.3. Lado cliente: Pares VC SAPUI5
Antes de meternos a comentar la funcionalidad y el funcionamiento de los pares VC,
queremos comentar un par de cosas.
La primera de ellas es que es altamente recomendable apoyarse en el SDK de SAPUI5
[17] mientras se va leyendo este texto, ya que posee bastante informacion sobre los distintos
controles de SAPUI5 y como funcionan. La segunda es comentar que, cada par VC se
corresponde con un grupo de elementos en pantalla, el shell, o las distintas pestanas y
funcionalidades de nuestra aplicacion. Solo hay un modelo, ya que esta aplicacion solo
interactua con una base de datos (HANA). Hay varias maneras en SAPUI5 de declarar
la vista, se recomienda utilizar XML (ya que, al ser un lenguaje de marcas, no se puede
implementar ninguna clase de logica mediante el, lo cual refuerza la division entre Vista
y Controlador), pero nosotros hemos preferido utiilizar Javascript, ya que as podemos
incluir una pequena parte de la logica de proceso del programa en la vista. Esto, a pesar
de no ser una buena practica, facilita y acorta bastante la programacion del codigo fuente.
Sin mas dilacion, empezamos:
4.4.3.1. Shell
Este es el par VC que hemos incluido en index.html en el cuerpo de la pagina web. Un
shell es un frame de aplicacion, que se muestra siempre en la parte superior de la pantalla,
y que sirve para navegar entre las distintas zonas de la pagina web.
Nuestro shell se compone de un ttulo, en el que aparece el nombre de la aplicacion
web, y 3 botones, que son:
Volcado de tablas
Ficha salarial de empleado
Arbol de estructura organizativa
La vista se encarga de declarar la variable shell, junto al ttulo, y a los 3 botones, aparte
de concretar cosas como el estilo del shell, eliminar elementos que vienen por defecto y
que son innecesarios, y demas. Pero si solo tuviesemos la vista, al pulsar cualquiera de
estos 3 botones no pasara nada. Reiteramos: la vista se encarga de definir que es lo que
sale por pantalla, y nada mas (aunque se relajara un poco esta distincion en los siguientes
apartados). Para poder darle funcionalidad a nuestros botones, necesitamos asignarles una
ID, mediante la cual podamos declarar desde el controlador variables que capturen los
elementos con estas IDs especficas, de tal forma que podamos manipular estos elementos
desde un archivo de codigo fuente distinto.
Una vez hecho esto, armamos en la vista el evento worksetItemSelected para que
le pase el control al controlador (valga la redundancia), el cual se encargara de cambiar
entre pares VC, y establecemos por defecto que aparezca el par VC asociado a la parte
de volcado de tablas.
55
4.4.3.2. Volcado de tablas
La parte de volcado de tablas de nuestra aplicacion es la primera que aparece, por defecto.
Los componentes a la vista que estan por debajo del Shell son: un control de tabla vaco
en cuyo toolbar aparece un textView que nos pide el nombre de una tabla para intentar
mostrarla o subirla, al lado de un textField en el que podemos escribir el nombre de la
tabla y un boton, que esta desactivado si el textField esta vaco. En el cuerpo de la tabla
aparece un texto pidiendo al usuario que introduzca una tabla en el textField.
Cuando el usuario teclea el nombre de una tabla del ERP en el textField y le pulsa
al boton de busqueda, el controlador empieza a ejecutar la siguiente rutina, mediante
llamadas AJAX hacia el modelo de HANA y hacia el Servlet del modulo de integracion.
1. Mandamos una peticion hacia el modelo HANA, en la que indicamos que queremos
que se nos sirva la tabla cuyo nombre hemos introducido en el campo de texto en
formato JSON.
2. Si el modelo encuentra la tabla y nos la sirve, cogemos los datos y los metadatos
y, mediante factory functions, los asignamos al control de tabla vaco que nos
aparece en pantalla, atando los datos a el, de tal forma que nos lo muestra por
pantalla (o nos muestra una alerta indicando que la tabla estaba vaca, en caso de
56
que no tenga datos), terminando aqu la rutina. Si no la encuentra y recibimos una
respuesta de error, pasamos al siguiente apartado.
3. Mandamos una peticion hacia el modulo de integracion, diciendole que queremos
que nos suba la tabla hacia nuestra base de datos en la nube.
4. Si no funciona y recibimos una respuesta de error (por no tener Cloud Connector
encendido y funcionando, por ejemplo), mostramos una alerta diciendo que no hemos
sido capaces de subir la tabla a HANA y terminamos con la rutina. Si funciona,
volvemos al primer paso.
Mediante esta rutina, nos aseguramos de que si tenemos la tabla en nuestra base
de datos, no volvemos a volcarla, si no que simplemente la mostramos por pantalla. Si
no la tenemos, la volcamos primero y luego la mostramos por pantalla. Con esta rutina
por tanto, conseguimos darle a nuestro usuario una interfaz para interactuar con toda
la maquinaria que hemos descrito en los apartados anteriores para poder subir tablas,
sin que este necesite conocer los detalles de la programacion en ABAP o en Java de los
modulos implementados. Esta misma rutina es la que se implementa en todos los otros
controladores de esta aplicacion web para obtener datos, con la unica diferencia de que el
tipo de datos que piden es distinto cada vez. Por tanto, no repetiremos el algoritmo que
siguen los controladores para obtener datos en los siguientes apartados.
57
Figura 23: Aplicacion web: Detalle salarial de un Empleado
58
Una vez entramos en esta vista, podemos observar varios detalles de la nomina del
empleado que hemos decidido observar, como su salario bruto, salario neto, su contribucion
al IRPF, la division de la compana en la que se encuentra, una proyeccion de su salario
en 5 anos (para lo cual utilizamos la librera sap.viz), etc.
Al fondo de la pagina aparecen dos botones. El primero sirve para generar y descargar
la nomina en formato PDF. Dado que la nomina es generada mediante la librera jsPDF,
no nos hace falta descargar ningun dato al generarla, el navegador simplemente lee los
datos de la pestana ya cargada y genera el PDF. El segundo sirve para volver a la pestana
anterior, en caso de que se quiera ver y generar la nomina de otro periodo o de otro
trabajador.
Cabe destacar que la query SQL generada por el controlador en este apartado no es
suficiente para poblar todos los datos de esta pagina, sino que tambien tenemos que pasarle
datos obtenidos en la vista anterior, para lo cual utilizabamos las variables declaradas en
el modelo global declarado en index.html. Para ello, recordemos que habamos asociado
este modelo con el nucleo de la aplicacion. Por tanto, solo tenemos que utilizar los metodos
encadenados sap.ui.getCore().getModel() para acceder a estas variables.
59
Figura 24: Aplicacion web: Arbol Estructura organizativa
Sin embargo, el lector astuto ya habra podido averiguar por que no podemos usar esta
manera de exponer nuestras tablas en nuestra aplicacion.
El problema es el siguiente, .xsodata requiere que se le diga de antemano que tablas
hay que exponer, mediante la modificacion de su codigo fuente. El problema es que esta
aplicacion nos ha de servir para subir y exponer absolutamente todas las tablas que
tenemos en nuestro ERP, con lo cual nos es imposible dar por sabido de antemano que
tablas va a querer subir y exponer el cliente. Ademas, por motivos de seguridad, no
podemos hacer que .xsodata vaya monitorizando y exponiendo un schema entero, sino
que tenemos que especificarle que es lo que queremos exponer a nivel de tabla.
Esto hace completamente imposible trabajar con .xsodata para nuestra aplicacion, con
lo cual tendremos que trabajar de otra manera.
La solucion pasa por utilizar el interprete de XSJS que va incluido en HANA pa-
ra exponer nuestros datos. Como ya comentamos anteriormente, XSJS es derivado de
Scriptmonkey, el interprete Javascript de Firefox. Tanto Scriptmonkey como XSJS son
interpretes ligeros, con funcionalidad muy limitada, pero suficiente para nuestros proposi-
tos.
El modelo programado solo respondera ante el metodo GET, ya que solo queremos
leer datos mediante el, no escribirlos. El modelo recibe el nombre de la tabla con la que
se quiere trabajar, ejecuta (con una excepcion, que veremos mas adelante) un comando
60
SELECT * FROM <Schema>.<Tabla> y devuelve el resultado en formato JSON, que manda hacia
la aplicacion que genero la peticion.
La excepcion se encuentra en las peticiones generadas por la pestana de Historial
Salarial en nuestra aplicacion web. En este caso, dado que nuestro modulo de integracion
sube las tablas y los clusters de datos tal y como vienen en el ERP, y necesitamos los
datos de dos elementos distintos para generar dicha pagina (el cluster en s y los datos de
empleado contenidos en la tabla de RRHH PA0001), no hacemos un simple SELECT, sino
que ademas hacemos un INNER JOIN con PA0001, teniendo como campo de union entre
ambas tablas el numero de empleado.
Como se puede observar, la estructura del codigo de este apartado es bastante compleja
comparada con las anteriores, en parte debido a seguir con bastante fidelidad el patron de
61
diseno MVC. Sin embargo, esta estructura nos da la ventaja de tener un diseno altamente
modular, en el que se pueden detectar y corregir rapidamente los fallos de programacion
que se hayan cometido, y que es altamente escalable. Si tuviesemos que implementar una
nueva pestana, solo tendramos que modificar el Shell y crear un nuievo par VC, en vez
de tener que encontrar el lugar adecuado para declarar nuestras variables en un codigo
fuente de 2000 lneas.
A continuacion escribiremos algunas conclusiones, incluyendo posibles lneas de inves-
tigacion futuras, comparaciones con otros metodos de integracion, etc.
62
5. Comparativa con otras soluciones en el mercado.
Hemos visto, en este proyecto, una manera de integrar datos entre soluciones onCloud
y onPremise. Pero no es, ni mucho menos, la unica. Veremos a continuacion otras maneras
de integrar, y las compararemos con esta, detallando sus pros y sus contras, con la idea de
que el lector pueda hacerse una idea de donde se situa este proyecto dentro del creciente
mercado de integracion de datos en Cloud Computing, y que alternativas existen.
63
en caso de que sea menor, habra que estudiar el coste de instalar los addons necesarios
frente a utilizar otras opciones.
64
5.4. SAP PI
SAP PI es, a diferencia de las soluciones anteriores, un middleware onPremise capaz
de exponer datos a la nube. Dado que PI exista antes de que SAP empezase a potenciar
el Cloud Computing como un nuevo estandar (estableciendo flujos de integracion entre
distintas soluciones onPremise), hoy en da es una de las soluciones mas implementadas
y utilizadas para integrar entre distintas soluciones.
Hoy en da es lo mas utilizado, hasta el punto de que SAP esta regalando licencias de
HCI a quien ya tenga licencias de PI con el objetivo de seguir fomentando la integracion
a la nube. Esto a nos esta explicando la principal ventaja de PI, que es el hecho de
que, al ser una solucion mas madura que las anteriores, la poseen la mayora de los
clientes. Sin embargo, operar con PI es mucho mas complejo que operar con HCI, haciendo
falta administradores de sistemas y programadores Java para mantener los servidores e
implementar flujos de integracion con ellos.
65
6. Conclusiones
Pasaremos a detallar aqu algunas de las conclusiones obtenidas con respecto a los
objetivos establecidos en el primer captulo, y como los hemos cubierto.
Recordemos que una parte de los objetivos contenidos en esta memoria consistan en
introducir el modelo de negocio de SAP a un lego en la materia y explicar el cambio
tan radical de estrategia que haban tenido en estos ultimos anos, debido a la natura-
leza disruptiva del Cloud Computing, sin meternos en tanto detalle como para que esta
parte ocupase la mayor parte de esta memoria. Si bien esto nos ha forzado a dejarnos
algunos detalles interesantes, consideramos que los dos primeros captulos son una buena
introduccion para, al menos, hacerse una idea general de la posicion actual en la que se
encuentra el ecosistema SAP. En ultima instancia, sin embargo, es el lector el que ha de
juzgar si este es, o no, el caso.
Con respecto al proyecto tecnico en s. Podemos observar que hace exactamente lo
que dice que hace, es decir, mueve tablas y estructuras de datos de un ERP a una cuenta
de HCP. No hay elemento subjetivo ah. Una posible crtica que se le podra hacer es en
terminos de escalabilidad, es decir, como reaccionara la aplicacion creada si en vez de
tener que subir tablas de pocos MB tuviesemos que subir tablas de varios GB?
Probablemente tendramos que hacer algunos cambios, sobre todo a la hora de instalar
Cloud Connector en una maquina distinta al ERP, pues si no, tendramos tablas de varios
GB dando saltos por nuestra red local. Tambien podra hacerse la crtica de que JCo
no es, ni mucho menos, un estandar, y que la manera en que se han hecho las cosas es
tan poco ortodoxa que sera difcil encontrar a alguien capaz de mantener y expandir la
funcionalidad de la aplicacion creada desde cero.
Todo esto son crticas validas. Sin embargo, hemos de darnos cuenta de que, en ultima
instancia, un ingeniero tiene que desarrollar, primero acorde con las circunstancias de la
situacion actual, y despues segun lo que podra suceder en un futuro. Hemos tenido que
operar con tres restricciones severas, que son:
La aplicacion realizada no puede depender de ninguna herramienta que sea de pago.
Es decir, el coste de la infraestructura ha de ser cero.
Los cambios que se le tengan que hacer al ERP han de ser lo menos invasivos posible,
pues, en este caso, es una herramienta esencial para muchos otros proyectos actual-
mente en curso. Una parada de varios das para realizar instalaciones complejas
podra ser catastrofica.
Nuestro ERP en concreto no esta del todo actualizado, con lo que muchas de las
opciones mas potentes que tenemos para poder integrar estan restringidas para
nosotros.
A partir de estas tres restricciones, se puede ver el por que de las decisiones de diseno
que hemos tomado. Teniendo en cuenta esto, y el hecho de que estamos desarrollando
66
para una empresa pequena, se puede ver por que hemos instalado, por ejemplo, Cloud
Connector fuera del servidor de nuestro ERP, o por que hemos decidido usar JCo + Cloud
Connector en vez de NetWeaver Gateway + Dell Boomi, por poner un ejemplo. Ninguna
solucion es perfecta, es cuestion de ver cual es la mas adecuada (o, en ciertos casos, cual
es la unica posible de implementar) para cada situacion. Para nuestras circunstancias,
la solucion implementada es perfectamente funcional, pero hemos de insistir en que no
existe una panacea (que es por lo que hemos intentado dar una comparativa de soluciones
distintas en el captulo anterior, y bajo que condiciones su uso sera favorable).
En el creciente mercado de las soluciones de integracion y migracion de datos en
particular, y en el mundo del desarrollo de software en general, hay que tener siempre
muy en cuenta las restricciones con las que hay que operar y los aspectos de desarrollo
que no sean tan crticos. Saber distinguir cuales son las herramientas mas adecuadas para
cada caso es una de las habilidades mas importantes que un ingeniero, especialmente si se
va a dedicar en un futuro a la gestion de proyectos, puede adquirir. Esta es, bajo el humilde
punto de vista del escritor de este texto, quiza la leccion mas importante aprendida a lo
largo de la elaboracion del proyecto aqu descrito.
67
Referencias
[1] APICS. SCM (supply chain management). http://www.apics.org/dictionary/
dictionary-information?ID=4202, 2013. [Online; visitado el 20 de octubre de
2014].
[4] Reuven Cohen. The Art Of Reinvention: SAP Puts Its Weight
Behind A Better, Faster And Stronger Cloud Computing Stra-
tegy. http://www.forbes.com/sites/reuvencohen/2013/05/09/
the-art-of-reinvention-sap-puts-its-weight-behind-a-better-faster-and-stronger-cl
2013. [Online; visitado el 27 de octubre de 2014].
[5] Michael Doane. The New SAP Blue Book. Performance Monitor Press, 2006.
[6] Bob Evans. SAP HANA and the Death of the Lunch Break. http://www.forbes.
com/sites/sap/2011/08/17/sap-hana-and-the-death-of-the-lunch-break/,
2011. [Online; visitado el 22 de octubre de 2014].
68
[12] SAP SE. SAP HANA One Petabyte Performance White Pa-
per. http://www.saphana.com/community/blogs/blog/2012/11/12/
the-sap-hana-one-petabyte-test, 2014. [Online; visitado el 24 de octubre
de 2014].
[13] SAP SE. SAP HANA Predictive Analysis Library (PAL) Reference. http://help.
sap.com/hana/SAP_HANA_Predictive_Analysis_Library_PAL_en.pdf, 2014. [On-
line; visitado el 23 de octubre de 2014].
[15] SAP SE. SAP HANA SQL and System Views Reference. http://help.sap.com/
hana/sap_hana_sql_and_system_views_reference_en.pdf, 2014. [Online; visita-
do el 22 de octubre de 2014].
[19] Robert Shaw. Computer Aided Marketing and Selling. Butterworth Heinemann,
1991.
[22] Verdi Ogewell. Great ERP, worse PLM What SAP PLM needs to sharpen
its competitive edge. http://www.engineering.com/PLMERP/ArticleID/8529/
Great-ERP-worse-PLM-What-SAP-PLM-needs-to-sharpen-its-competitive-edge.
aspx, 2014. [Online; visitado el 20 de octubre de 2014].
[23] Jeffrey Word. SAP HANA Essentials. Epistemy Press LLC, 2013.
69
Apendices
A. Codigo fuente del modulo onPremise: ABAP.
Detallamos aqu el codigo fuente de las funciones Z desarrolladas en el ERP.
70
A.2. Z RFC HR READ PAYROLL RESULTS
1 FUNCTION Z _ R F C _ H R _ R E A D _ P A Y R O L L _ R E S U L T S .
2 *"--------------------------------------------------------------------
3 * "*" Interfase local
4 * " IMPORTING
5 *" VALUE ( INT_RELID ) TYPE RELID
6 *" VALUE ( INT_SEQNR ) TYPE PC261 - SEQNR
7 *" VALUE ( INT_PERNR ) TYPE PC200 - PERNR
8 * " EXPORTING
9 *" VALUE ( ST_RESULT ) TYPE PAYES_RESULT
10 *"--------------------------------------------------------------------
11
12 CALL FUNCTION P Y X X _ R E A D _ P A Y R O L L _ R E S U L T
13 EXPORTING
14 clusterid = int_relid
15 employeenumber = int_pernr
16 sequencenumber = int_seqnr
17 CHANGING
18 payroll_result = st_result
19 EXCEPTIONS
20 I L L E G A L _ I S O C O D E _ O R _ C L U S T E R I D = 1 " Cluster ID Or ISO Code Not in
Table T500L
21 E R R O R _ G E N E R A T I N G _ I M P O R T = 2 " Error When Generating the Import
Routine
22 IMPORT_MISMATCH_ERROR = 3 " Error On Importing : Different
Structures
23 SUBPOOL_DIR_FULL = 4 " More Than 36 Temp . Routines Were
Generated
24 NO_R EAD_A UTHORI TY = 5 " No Reading Authorization for Cluster
25 NO_RECORD_FOUND = 6 " No Payroll Result Found
26 VERSIONS_DO_NOT_MATCH = 7 " Version Indicators Do Not Correspond
27 ERROR_READING_ARCHIVE = 8 " Error When Reading Archives
28 E RR O R_R E A DI N G _ RE L I D = 9. " Error When Reading Cluster ID
29
30 ENDFUNCTION .
71
A.3. Z RFC HR READ ORG STRUCTURE
1 FUNCTION z _ r f c _ h r _ r e a d _ o r g _ s t r u c t u r e .
2 *"----------------------------------------------------------------------
3 * "*" Interfase local
4 * " IMPORTING
5 *" VALUE ( INT_OTYPE ) TYPE OBJEC - OTYPE
6 *" VALUE ( INT_OBJID ) TYPE OBJEKTID
7 *" VALUE ( INT_WEGID ) TYPE GDSTR - WEGID
8 * " EXPORTING
9 *" VALUE ( INT_PLVAR ) TYPE OBJEC - PLVAR
10 * " TABLES
11 *" INT_TAB STRUCTURE SWHACTOR
12 *" INT_OBJEC STRUCTURE OBJEC
13 *" INT_STRUC STRUCTURE STRUC
14 *"----------------------------------------------------------------------
15 CALL FUNCTION RH_STRUC_GET
16 EXPORTING
17 act_otype = int_otype
18 act_objid = int_objid
19 act_wegid = int_wegid
20 IMPORTING
21 act_plvar = int_plvar
22 TABLES
23 result_tab = int_tab
24 result_objec = int_objec
25 result_struc = int_struc
26 EXCEPTIONS
27 no_plvar_found = 1
28 no_entry_found = 2
29 OTHERS = 3.
30 .
31 IF sy - subrc <> 0.
32 WRITE : Error , sy - subrc .
33 * WITH SY - MSGV1 SY - MSGV2 SY - MSGV3 SY - MSGV4 .
34 ENDIF .
35
36
37 ENDFUNCTION .
72
B. Codigo fuente del modulo de integracion: Java.
Pasamos a listar el codigo fuente del modulo de integracion:
B.1. ControllerServlet.java
1 package com . sap . transfer . jco ;
2
3 import java . io . IOException ;
4 import java . io . PrintWriter ;
5 import java . sql . SQLException ;
6 import java . util . Arrays ;
7
8 import javax . naming . InitialContext ;
9 import javax . naming . NamingException ;
10 import javax . servlet . ServletException ;
11 import javax . servlet . http . HttpServlet ;
12 import javax . servlet . http . Htt pS er vl et Re qu es t ;
13 import javax . servlet . http . H tt p S er v l et R e sp o n se ;
14 import javax . sql . DataSource ;
15
16 import com . sap . conn . jco . AbapException ;
17 import com . sap . conn . jco . JCoException ;
18
19 /* *
20 * Application which makes use of / BODS / RFC_READ_TABLE2 to upload a
table whose name is passed by x - www - form - urlencoded
21 * from an SAP ERP back - end to a HANA cloud account , preserving the
structure .
22 *
23 * Note : The JCo APIs are available under < code > com . sap . conn . jco </ code >.
24 */
25 public class Co ntroll erServ let extends HttpServlet
26 {
27 private static final long serialVersionUID = 1 L ;
28 private String destinationName = " JCoDemoSystem " ;
29 // private String functionName = "/ BODS / RFC_READ_TABLE2 ";
30 private boolean tableNotExist ;
31 private TransferView transferView ;
32 private HanaDAO hanaDAO ;
33 private AbapDAO abapDAO ;
34
35 /* * { @inheritDoc } */
36 @Override
37 public void init () throws ServletException {
38 try {
39 InitialContext ctx = new InitialContext () ;
73
40 DataSource ds = ( DataSource ) ctx . lookup ( " java : comp / env / jdbc /
DefaultDB " ) ;
41
42 hanaDAO = new HanaDAO ( ds ) ;
43 abapDAO = new AbapDAO ( destinationName ) ;
44 } catch ( SQLException e ) {
45 throw new ServletException ( e ) ;
46 } catch ( NamingException e ) {
47 throw new ServletException ( e ) ;
48 } catch ( JCoException e ) {
49 throw new ServletException ( e ) ;
50 }
51 }
52
53
54 protected void doPost ( Ht tp Se rv le tR eq ue st request ,
55 H ttp S er v l et R e sp o n s e response ) throws ServletException , IOException
{
56 PrintWriter responseWriter = response . getWriter () ;
57 transferView = new TransferView ( responseWriter ) ;
58 String functionName = request . getParameter ( " functionname " ) ;
59 try {
60 if ( functionName . equals ( " / BODS / RFC_READ_TABLE2 " ) ) {
61 // TABLE UPLOADER CODE
62 AbapData tableData ;
63 String tableName = request . getParameter ( " tablename " ) ;
64
65 response . setStatus ( H t t pS e r vl e t Re s p on s e . SC_CREATED ) ;
66 tableData = abapDAO . getTable ( tableName ) ;
67
68 tableNotExist = hanaDAO . checkTable ( tableName , tableData .
fieldType , tableData . fieldName ) ;
69
70 if ( tableNotExist )
71 for ( int i = 0; i < tableData . tableEntries . length ; i ++)
72 hanaDAO . addRow ( tableName , tableData . tableEntries [ i ] ,
tableData . fieldType , tableData . fieldName ) ;
73
74 } else if ( functionName . equals ( " Z _ R F C _ H R _ R E A D _ P A Y R O L L _ E N T R I E S " ) ) {
75 // PAYROLL HISTORY UPLOADER CODE
76 String employeeNumber = request . getParameter ( " employeenumber " ) ;
77 String tableName = " PH " + employeeNumber ;
78
79 AbapData payrollHistory = abapDAO . getP ayroll Entrie s ( Integer .
parseInt ( employeeNumber ) ) ;
80
81 // fieldName = payrollHistory [0];
82 // fieldType = payrollHistory [1];
74
83 // tableEntries = Arrays . copyOfRange ( payrollHistory , 2 ,
payrollHistory . length ) ;
84
85 // Anadimos PERNR para poder hacer un join
86 payrollHistory . fieldName = Arrays . copyOf ( payrollHistory .
fieldName , payrollHistory . fieldName . length +1) ;
87 payrollHistory . fieldType = Arrays . copyOf ( payrollHistory .
fieldType , payrollHistory . fieldType . length +1) ;
88
89
90 payrollHistory . fieldName [ payrollHistory . fieldName . length - 1] =
" PERNR " ;
91 payrollHistory . fieldType [ payrollHistory . fieldType . length - 1] =
"I";
92 for ( int j = 0; j < payrollHistory . tableEntries . length ; j ++) {
93 payrollHistory . tableEntries [ j ] = Arrays . copyOf ( payrollHistory .
tableEntries [ j ] , payrollHistory . tableEntries [ j ]. length + 1)
;
94 payrollHistory . tableEntries [ j ][ payrollHistory . tableEntries [ j ].
length - 1] = employeeNumber ;
95 }
96
97 tableNotExist = hanaDAO . checkTable ( tableName , payrollHistory .
fieldType , payrollHistory . fieldName ) ;
98
99 if ( tableNotExist )
100 for ( int i = 0; i < payrollHistory . tableEntries . length ; i ++)
101 hanaDAO . addRow ( tableName , payrollHistory . tableEntries [ i ] ,
payrollHistory . fieldType , payrollHistory . fieldName ) ;
102
103 response . setStatus ( H t t pS e r vl e t Re s p on s e . SC_CREATED ) ;
104
105 } else if ( functionName . equals ( " Z _ R F C _ H R _ R E A D _ P A Y R O L L _ R E S U L T S " ) ) {
106 // PAYROLL RESULTS UPLOADER CODE
107 String employeeNumber = request . getParameter ( " employeenumber " ) ;
108 String sequenceNumber = request . getParameter ( " sequencenumber " ) ;
109 String relationId = request . getParameter ( " relationid " ) ;
110
111 String tableName = " PH " + employeeNumber + " _PE " +
sequenceNumber ;
112
113 AbapData payrollResults = abapDAO . getP ayroll Result s (
employeeNumber , sequenceNumber , relationId ) ;
114 // for ( int i = 0; i < payrollResults . length ; i ++)
115 // responseWriter . println (" <p >" + payrollResults [ i ] + " </p >") ;
116
117 tableNotExist = hanaDAO . checkTable ( tableName , payrollResults .
fieldType , payrollResults . fieldName ) ;
118
75
119 if ( tableNotExist )
120 for ( int i = 0; i < payrollResults . tableEntries . length ; i ++)
121 hanaDAO . addRow ( tableName , payrollResults . tableEntries [ i ] ,
payrollResults . fieldType , payrollResults . fieldName ) ;
122
123 response . setStatus ( H t t pS e r vl e t Re s p on s e . SC_CREATED ) ;
124
125 } else if ( functionName . equals ( " Z _ R F C _ H R _ R E A D _ O R G _ S T R U C T U R E " ) ) {
126 // STRUCTURE UPLOADER CODE
127 String objectType = request . getParameter ( " objecttype " ) ;
128 String objectId = request . getParameter ( " objectid " ) ;
129 String evaluationPath = request . getParameter ( " evaluationpath " ) ;
130
131 String tableName = " ST " + objectType + " _ID " + objectId ;
132 AbapData orgTable = abapDAO . g e t O r g a n i z a t i o n a l S t r u c t u r e (
objectType , objectId , evaluationPath ) ;
133
134 tableNotExist = hanaDAO . checkTable ( tableName , orgTable . fieldType
, orgTable . fieldName ) ;
135
136 if ( tableNotExist )
137 for ( int i = 0; i < orgTable . tableEntries . length ; i ++)
138 hanaDAO . addRow ( tableName , orgTable . tableEntries [ i ] , orgTable
. fieldType , orgTable . fieldName ) ;
139
140 response . setStatus ( H t t pS e r vl e t Re s p on s e . SC_CREATED ) ;
141 }
142
143 } catch ( AbapException e ) {
144 transferView . exceptionWriter (e , destinationName ) ;
145 response . setStatus ( H t t pS e r vl e t Re s p on s e . S C _ I N T E R N A L _ S E R V E R _ E R R O R ) ;
146 } catch ( JCoException e ) {
147 transferView . exceptionWriter (e , destinationName ) ;
148 response . setStatus ( H t t pS e r vl e t Re s p on s e . S C _ I N T E R N A L _ S E R V E R _ E R R O R ) ;
149 } catch ( SQLException e ) {
150 transferView . exceptionWriter (e , destinationName ) ;
151 response . setStatus ( H t t pS e r vl e t Re s p on s e . S C _ I N T E R N A L _ S E R V E R _ E R R O R ) ;
152 }
76
B.2. AbapDAO.java
1 package com . sap . transfer . jco ;
2
3 import java . sql . SQLException ;
4 import java . util . ArrayList ;
5 import java . util . Arrays ;
6 import java . util . Collections ;
7 import java . util . List ;
8
9 import com . sap . conn . jco . AbapException ;
10 import com . sap . conn . jco . JCoDestination ;
11 import com . sap . conn . jco . J C o D e s t i n a t i o n M a n a g e r ;
12 import com . sap . conn . jco . JCoException ;
13 import com . sap . conn . jco . JCoFunction ;
14 import com . sap . conn . jco . JCoParameterList ;
15 import com . sap . conn . jco . JCoRepository ;
16 import com . sap . conn . jco . JCoStructure ;
17 import com . sap . conn . jco . JCoTable ;
18
19 /* *
20 * @author Juan Jose Perez
21 *
22 * The following class contains some methods which help abstract away
the intricacies
23 * of the ETL process to our main Servlet
24 *
25 */
26
27 public class AbapDAO {
28
29 String wa ;
30 final String destinationName ;
31 final JCoDestination destination ;
32 final JCoRepository repo ;
33
34 public AbapDAO ( String destName ) throws JCoException {
35 destinationName = destName ;
36
37 // access the RFC Destination " JCoDemoSystem "
38 destination = J C o D e s t i n a t i o n M a n a g e r . getDestination (
destinationName ) ;
39 repo = destination . getRepository () ;
40 }
41
42 /* *
43 * A method which uses / BODS / RFC_READ_TABLE2 and JCo to upload an
ABAP table in native format
44 * @param tableName
77
45 * @return AbapData
46 * @throws AbapException
47 * @throws SQLException
48 * @throws JCoException
49 */
50
51 public AbapData getTable ( String tableName )
52 throws AbapException , SQLException , JCoException {
53
54 AbapData tableData = new AbapData () ;
55 // make an invocation of R F C _ G E T _ T A B L E _ E N T R I E S in the
backend ;
56
57 JCoFunction rfcReadTable = repo . getFunction ( " / BODS /
RFC_READ_TABLE2 " ) ;
58
59 // Execute the local function and retrieve the data
60 JCoParameterList imports = rfcReadTable .
g e t I m p o r t P a r a m e t e r L i s t () ;
61 JCoParameterList exports = rfcReadTable .
g e t E x p o r t P a r a m e t e r L i s t () ;
62 JCoParameterList tables = rfcReadTable . g e t T a b l e P a r a m e t e r L i s t
() ; // Se corresponde con TABLES en la llamada a funcion
ABAP .
63 imports . setValue ( " QUERY_TABLE " , tableName ) ;
64 imports . setValue ( " DELIMITER " ," ; " ) ;
65
66
67 rfcReadTable . execute ( destination ) ;
68 JCoTable fields = tables . getTable ( " FIELDS " ) ;
69 JCoTable data = tables . getTable (( String ) exports . getValue ( "
OUT_TABLE " ) ) ;
70 tableData . fieldType = new String [ fields . getNumRows () ];
71 tableData . fieldName = new String [ fields . getNumRows () ];
72 tableData . tableEntries = new String [ data . getNumRows () ][];
73
74 for ( int i = 0; i < data . getNumRows () ; i ++) {
75 data . setRow ( i ) ;
76 tableData . tableEntries [ i ] = data . getString ( " WA " ) . split ( " ; "
);
77 }
78
79
80
81 for ( int i = 0; i < fields . getNumRows () ; i ++) {
82 fields . setRow ( i ) ;
83 // responseWriter . print (( i == 0 ? "" : ";") + fields .
getString (" FIELDNAME ") ) ;
78
84 tableData . fieldName [ i ] = " \" " + fields . getString ( "
FIELDNAME " ) + " \" " ;
85
86 }
87
88
89 for ( int i = 0; i < fields . getNumRows () ; i ++) {
90 fields . setRow ( i ) ;
91 // responseWriter . print (( i == 0 ? "" : ";") + fields .
getString (" TYPE ") ) ;
92 tableData . fieldType [ i ] = fields . getString ( " TYPE " ) ;
93 }
94
95 return tableData ;
96 }
97
98 /* *
99 * A method which uses Z _ R F C _ H R _ R E A D _ P A Y R O L L _ E N T R I E S and JCo to
upload the Payroll History of an employee
100 * @param employeeNumber
101 * @return AbapData
102 * @throws AbapException
103 * @throws SQLException
104 * @throws JCoException
105 */
106
107
108 public AbapData ge tPayro llEntr ies ( int employeeNumber )
109 throws AbapException , SQLException , JCoException {
110 JCoTable p a y ro l l En t r ie s T ab l e ;
111 AbapData payrollEntries = new AbapData () ;
112 String relationalId ;
113
114 JCoFunction r f c R e a d P a y r o l l E n t r i e s = repo . getFunction ( "
Z_RFC_HR_READ_PAYROLL_ENTRIES ");
115 JCoParameterList imports = r f c R e a d P a y r o l l E n t r i e s .
g e t I m p o r t P a r a m e t e r L i s t () ;
116 JCoParameterList exports = r f c R e a d P a y r o l l E n t r i e s .
g e t E x p o r t P a r a m e t e r L i s t () ;
117 JCoParameterList tables = r f c R e a d P a y r o l l E n t r i e s .
g e t T a b l e P a r a m e t e r L i s t () ;
118 imports . setValue ( " INT_PERNR " , employeeNumber ) ;
119 r f c R e a d P a y r o l l E n t r i e s . execute ( destination ) ;
120 p ay r o ll E n tr i e sT a b le = tables . getTable ( " IT_RGDIR " ) ;
121 relationalId = exports . getString ( " INT_RELID " ) ;
122 // payrollEntries = new String [2 + p ay r o ll E n tr i e sT a b le .
getNumRows () ][ p a y ro l l En t r ie s T ab l e . getNumColumns () + 1];
123 // Arrays . fill ( payrollEntries , "") ;
124
79
125 payrollEntries . fieldType = new String [ p ay r o ll E n tr i e sT a b le .
getNumColumns () + 1];
126 payrollEntries . fieldName = new String [ p ay r o ll E n tr i e sT a b le .
getNumColumns () + 1];
127 payrollEntries . tableEntries = new String [ p ay r o ll E n tr i e sT a b le .
getNumRows () ][ p a y ro l l En t r ie s T ab l e . getNumColumns () + 1];
128
129 for ( int i = 0; i < p a yr o l lE n t ri e s Ta b l e . getNumColumns () ; i ++) {
130 if (! Arrays . asList ( payrollEntries . fieldName ) . contains (
p ay r o ll E n tr i e s Ta b l e . g etReco rdMeta Data () . g etReco rdTyp eName ( i
))) {
131 payrollEntries . fieldName [ i ] = p ay r o ll E n tr i e sT a b le .
getR ecordM etaDa ta () . get Record TypeNa me ( i ) ;
132 payrollEntries . fieldType [ i ] = p ay r o ll E n tr i e sT a b le .
getR ecordM etaDa ta () . getTypeAsString ( i ) ;
133 }
134 }
135
136 payrollEntries . fieldName [ p ay r o ll E n tr i e sT a b le . getNumColumns () ] =
" RELID " ;
137 payrollEntries . fieldType [ p ay r o ll E n tr i e sT a b le . getNumColumns () ] =
" CHAR " ;
138
139 for ( int j = 0; j < p a yr o l lE n t ri e s Ta b l e . getNumRows () ; j ++) {
140 p a yr o l lE n t ri e s Ta b l e . setRow ( j ) ;
141 for ( int i = 0; i < p a yr o l lE n t ri e s Ta b l e . getNumColumns () ; i ++)
{
142 if ( payrollEntries . fieldName [ i ] != null )
143 payrollEntries . tableEntries [ j ][ i ] = p ay r o ll E n tr i e sT a b le .
getString ( i ) ;
144 }
145 payrollEntries . tableEntries [ j ][ p ay r o ll E n tr i e sT a b le .
getNumColumns () ] = relationalId ;
146 }
147
148 List < String > list = new ArrayList < String >( Arrays . asList (
payrollEntries . fieldName ) ) ;
149 list . removeAll ( Collections . singleton ( null ) ) ;
150 payrollEntries . fieldName = list . toArray ( new String [ list . size () ]) ;
151
152 list = new ArrayList < String >( Arrays . asList ( payrollEntries .
fieldType ) ) ;
153 list . removeAll ( Collections . singleton ( null ) ) ;
154 payrollEntries . fieldType = list . toArray ( new String [ list . size () ]) ;
155
156 for ( int j = 0; j < payrollEntries . tableEntries . length ; j ++) {
157 list = new ArrayList < String >( Arrays . asList ( payrollEntries .
tableEntries [ j ]) ) ;
158 list . removeAll ( Collections . singleton ( null ) ) ;
80
159 payrollEntries . tableEntries [ j ] = list . toArray ( new String [ list .
size () ]) ;
160 }
161
162 return payrollEntries ;
163 }
164
165 /* *
166 * A method which uses Z _ R F C _ H R _ R E A D _ P A Y R O L L _ R E S U L T S and JCo to
upload the results of the calculation of the Payroll of an
employee
167 * @param employeeNumber
168 * @param sequenceNumber
169 * @param relationId
170 * @return AbapData
171 * @throws JCoException
172 */
173
174 public AbapData ge tPayro llResu lts ( String employeeNumber , String
sequenceNumber , String relationId ) throws JCoException {
175 JCoStructure p a y r o l l R e s u l t s S t r u c t u r e ;
176 AbapData payrollResults = new AbapData () ;
177
178 JCoFunction r f c R e a d P a y r o l l R e s u l t s = repo . getFunction ( "
Z_RFC_HR_READ_PAYROLL_RESULTS ");
179 JCoParameterList imports = r f c R e a d P a y r o l l R e s u l t s .
g e t I m p o r t P a r a m e t e r L i s t () ;
180 JCoParameterList exports = r f c R e a d P a y r o l l R e s u l t s .
g e t E x p o r t P a r a m e t e r L i s t () ;
181
182 imports . setValue ( " INT_PERNR " , employeeNumber ) ;
183 imports . setValue ( " INT_SEQNR " , sequenceNumber ) ;
184 imports . setValue ( " INT_RELID " , relationId ) ;
185
186 r f c R e a d P a y r o l l R e s u l t s . execute ( destination ) ;
187
188 p a y r o l l R e s u l t s S t r u c t u r e = exports . getStructure ( " ST_RESULT " ) ;
189
190 JCoTable payrollCRT = p a y r o l l R e s u l t s S t r u c t u r e . getStructure ( " INTER " ) .
getTable ( " CRT " ) ;
191 payrollCRT . setRow (0) ;
192 String grossSalary ;
193 String wageType ;
194
195 payrollResults . fieldType = new String []{ " NUM " , " CHAR " };
196 payrollResults . fieldName = new String []{ " BETRG " , " LGART " };
197 payrollResults . tableEntries = new String [ payrollCRT . getNumRows ()
][2];
198
81
199 for ( int i = 0; i < payrollCRT . getNumRows () ; i ++) {
200 payrollCRT . setRow ( i ) ;
201 grossSalary = payrollCRT . getString ( " BETRG " ) ;
202 wageType = payrollCRT . getString ( " LGART " ) ;
203 payrollResults . tableEntries [ i ] = new String []{ grossSalary ,
wageType };
204 }
205
206 return payrollResults ;
207 }
208
209 /* *
210 * A method which uses Z _ R F C _ H R _ R E A D _ O R G _ S T R U C T U R E and JCo to upload
the organizational structure of a node
211 * @param objectType
212 * @param objectId
213 * @param evaluationPath
214 * @return AbapData
215 * @throws JCoException
216 */
217
218 public AbapData g e t O r g a n i z a t i o n a l S t r u c t u r e ( String objectType , String
objectId , String evaluationPath ) throws JCoException {
219 JCoTable orgStructure ;
220 JCoTable orgObjects ;
221
222 AbapData orgTable = new AbapData () ;
223
224 JCoFunction rf c R ea d O rg S t ru c t ur e = repo . getFunction ( "
Z_RFC_HR_READ_ORG_STRUCTURE ");
225
226 JCoParameterList imports = r fc R e ad O r gS t r uc t u re .
g e t I m p o r t P a r a m e t e r L i s t () ;
227 JCoParameterList tables = r fc R e ad O r gS t r uc t u re . g e t T a b l e P a r a m e t e r L i s t
() ;
228
229 imports . setValue ( " INT_OTYPE " , objectType ) ;
230 imports . setValue ( " INT_OBJID " , objectId ) ;
231 imports . setValue ( " INT_WEGID " , evaluationPath ) ;
232
233 r fc R e ad O r gS t r u ct u r e . execute ( destination ) ;
234
235 orgStructure = tables . getTable ( " INT_STRUC " ) ;
236 orgObjects = tables . getTable ( " INT_OBJEC " ) ;
237
238 orgTable . fieldType = new String []{ " C " , " I " , " I " , " C " , " C " , " I " , " C " ,
" I " , " I " , " I " , " I " };
239 orgTable . fieldName = new String []{ " STEXT " , " SEQNR " , " LEVEL " , " OTYPE "
, " OBJID " , " PDOWN " , " DFLAG " , " VCOUNT " , " PNEXT " , " PUP " , " PPREV " };
82
240 orgTable . tableEntries = new String [ orgStructure . getNumRows () ][
orgTable . fieldName . length ];
241
242 for ( int i = 0; i < orgStructure . getNumRows () ; i ++) {
243 orgObjects . setRow ( i ) ;
244 orgStructure . setRow ( i ) ;
245 orgTable . tableEntries [ i ][0] = orgObjects . getString ( orgTable .
fieldName [0]) ;
246 for ( int j = 1; j < orgTable . fieldName . length ; j ++)
247 orgTable . tableEntries [ i ][ j ] = orgStructure . getString ( orgTable .
fieldName [ j ]) ;
248 }
249
250 return orgTable ;
251 }
252 }
83
B.3. HanaDAO.java
1 package com . sap . transfer . jco ;
2
3 import java . sql . Connection ;
4 import java . sql . DatabaseMetaData ;
5 import java . sql . Pr epared Statem ent ;
6 import java . sql . ResultSet ;
7 import java . sql . SQLException ;
8
9 import javax . sql . DataSource ;
10
11 /* *
12 * @author Juan Jose Perez
13 * Data access object encapsulating some necessary JDBC operations for
any possible data row .
14 */
15 public class HanaDAO {
16
17 private enum AbapTypes
18 {
19 B ,S ,L ,C ,N ,P ,D ,T ,F ,X ,g ,y , I
20 }
21
22 private DataSource dataSource ;
23
24 /* *
25 * Create new data access object with data source .
26 */
27 public HanaDAO ( DataSource newDataSource ) throws SQLException {
28 setDataSource ( newDataSource ) ;
29 }
30
31 /* *
32 * Get data source which is used for the database operations .
33 */
34 public DataSource getDataSource () {
35 return dataSource ;
36 }
37
38 /* *
39 * Set data source to be used for the database operations .
40 */
41 public void setDataSource ( DataSource newDataSource ) throws
SQLException {
42 this . dataSource = newDataSource ;
43 }
44
45 /* *
84
46 * Add a row to the table .
47 */
48 public void addRow ( String tableName , String [] entries , String []
dataTypes , String [] dataColumns ) throws SQLException {
49 Connection connection = dataSource . getConnection () ;
50
51 try {
52 String statement ;
53 String typeCheck = " VARCHAR \\(\\ d +\\) | VARBINARY \\(\\ d +\\) | TIME
| DATE " ;
54
55 statement = " INSERT INTO " + tableName + " ( " ;
56
57
58 for ( int i = 0; i < entries . length ; i ++) {
59 if (! entries [ i ]. trim () . isEmpty () ) {
60 statement = statement + dataColumns [ i ] + " , " ;
61 }
62 }
63
64 statement = statement . substring (0 , statement . length () -2) + " )
VALUES ( " ; // Eliminamos la ultima coma y seguimos
escribiendo .
65
66 for ( int i = 0; i < entries . length ; i ++) {
67 if (! entries [ i ]. trim () . isEmpty () ) {
68 // Si es un String , quitamos espacios en blanco al
principio y al final y anadimos comillas . Si no lo es ,
eliminamos todos los caracteres que no sean numericos o
puntos y no anadimos comillas .
69 statement = statement +
70 ( typeMapping ( dataTypes [ i ]) . matches ( typeCheck ) ? " " : " " ) + ( typeMapping
( dataTypes [ i ]) . matches ( typeCheck ) ? entries [ i ]. trim () : entries [ i ].
trim () . replaceAll ( " [^\\ d .] " , " " ) ) +
71 ( typeMapping ( dataTypes [ i ]) . matches ( typeCheck ) ? " " : " " ) + " , " ;
72 }
73 }
74
75 statement = statement . substring (0 , statement . length () -2) + " ) "
;
76
77 Prep aredSt atemen t pstmt = connection
78 . prepareStatement ( statement ) ;
79 pstmt . executeUpdate () ;
80 } finally {
81 if ( connection != null ) {
82 connection . close () ;
83 }
84 }
85
85 }
86
87 /* *
88 * Get all persons from the table .
89 */
90 // public List < Person > selectAllPersons () throws SQLException {
91 // Connection connection = dataSource . getConnection () ;
92 // try {
93 // Pre paredS tateme nt pstmt = connection
94 // . prepareStatement (" SELECT ID , FIRSTNAME , LASTNAME
FROM T_PERSONS ") ;
95 // ResultSet rs = pstmt . executeQuery () ;
96 // ArrayList < Person > list = new ArrayList < Person >() ;
97 // while ( rs . next () ) {
98 // Person p = new Person () ;
99 // p . setId ( rs . getString (1) ) ;
100 // p . setFirstName ( rs . getString (2) ) ;
101 // p . setLastName ( rs . getString (3) ) ;
102 // list . add ( p ) ;
103 // }
104 // return list ;
105 // } finally {
106 // if ( connection != null ) {
107 // connection . close () ;
108 // }
109 // }
110 // }
111
112 /* *
113 * Check if the person table already exists and create it if not .
114 */
115 public boolean checkTable ( String tableName , String [] dataTypes ,
String [] dataColumns ) throws SQLException {
116 Connection connection = null ;
117 boolean result = false ;
118
119 try {
120 connection = dataSource . getConnection () ;
121 if (! existsTable ( connection , tableName ) ) {
122 createTable ( connection , tableName , dataTypes ,
dataColumns ) ;
123 result = true ;
124 }
125 } finally {
126 if ( connection != null ) {
127 connection . close () ;
128 }
129 }
130
86
131 return result ;
132 }
133
134 /* *
135 * Check if the person table already exists .
136 */
137 private boolean existsTable ( Connection conn , String tableName )
throws SQLException {
138 DatabaseMetaData meta = conn . getMetaData () ;
139 ResultSet rs = meta . getTables ( null , null , tableName , null ) ;
140 while ( rs . next () ) {
141 String name = rs . getString ( " TABLE_NAME " ) ;
142 if ( name . equals ( tableName ) ) {
143 return true ;
144 }
145 }
146 return false ;
147 }
148
149 /* *
150 * Create the person table .
151 */
152 private void createTable ( Connection connection , String tableName ,
String [] dataTypes , String [] dataColumns ) throws SQLException {
153 String statement = " CREATE TABLE " + tableName + " ( " ; //
Generamos el Statement SQL en funcion de los datos que
obtenemos .
154
155 for ( int i = 0; i < dataTypes . length ; i ++)
156 {
157 statement = statement + " " + dataColumns [ i ] + " " + typeMapping
( dataTypes [ i ]) + (( i == dataTypes . length - 1) ? " " : " ," ) ;
// TODO : Encontrar la manera de averiguar cuales son los
campos clave y ponerlo
158 }
159
160 statement = statement + " ) " ;
161
162
163 Prep aredSt ateme nt pstmt = connection
164 . prepareStatement ( statement ) ;
165 pstmt . executeUpdate () ;
166 }
167
168 /* *
169 * Map ABAP types to SQL types
170 */
171
172 private String typeMapping ( String type )
87
173 {
174 String result ;
175 type = type . substring (0 , 1) ;
176
177 switch ( AbapTypes . valueOf ( type ) )
178 {
179 case B :
180 result = " VARBINARY (1) " ;
181 break ;
182 case S :
183 result = " VARBINARY (2) " ;
184 break ;
185 case L :
186 result = " VARBINARY (4) " ;
187 break ;
188 case C :
189 result = " VARCHAR (255) " ;
190 break ;
191 case N :
192 result = " VARCHAR (255) " ;
193 break ;
194 case P :
195 result = " DECIMAL " ;
196 break ;
197 case D :
198 result = " DATE " ;
199 break ;
200 case T :
201 result = " TIME " ;
202 break ;
203 case F :
204 result = " DOUBLE PRECISION " ;
205 break ;
206 case X :
207 result = " VARBINARY (255) " ;
208 break ;
209 case I :
210 result = " INTEGER " ;
211 break ;
212 default :
213 result = null ;
214 }
215 return result ;
216 }
217 }
88
B.4. AbapData.java
1 package com . sap . transfer . jco ;
2
3 public class AbapData {
4 String [] fieldType ;
5
6 String [] fieldName ;
7
8 String [][] tableEntries ;
9 }
89
C. Archivos nativos de HANA XS.
A continuacion listamos los archivos necesarios para crear una aplicacion web, expo-
nerla, y hacer que pueda manipular los datos de HANA.
C.1. .xsapp
90
C.2. .xsaccess
1 {
2 " exposed " : true ,
3 " default_file " : " index . html " ,
4 " cors " : [{ " enabled " : true }] ,
5 " a n o n y mo u s _ c o n n e c t i o n " : " p1940528821trial . myhanaxs . jcodata :: AC "
6 }
91
C.3. .xsprivileges
1 { " privileges " :
2 [ { " name " : " Basic " , " description " : " Basic usage privilege " } ]
}
92
C.4. model access.hdbrole
1 role p1940528821trial . myhanaxs . jcodata :: model_access {
2 application privilege : p1940528821trial . myhanaxs . jcodata :: Basic ;
3 catalog schema " N E O _ 2 Y R C M 3 I 3 B M 6 5 X 2 E P A L 4 2 I E L V B " : SELECT , UPDATE , INSERT ,
DELETE ;
4 package p1940528821trial . myhanaxs . jcodata : REPO .
ACTIVATE_IMPORTED_OBJECTS ;
5 }
93
D. Codigo fuente del modulo de presentacion: Javas-
cript, CSS3, HTML5.
A continuacion pasamos a listar el codigo fuente de la aplicacion web:
D.1. index.html
1 <! DOCTYPE html >
2 < html >
3 < head >
4 < meta http - equiv = X - UA - Compatible content = IE = edge , chrome =1 >
5 < meta http - equiv = Content - Type content = text / html ; charset = UTF -8 / >
6 < link href = " customStyles . css " type = " text / css " rel = " stylesheet " / >
7
8 < script id = " jspdf - bootstrap " type = " text / javascript " src = " jspdf . js " > <
/ script >
9 < script id = " jspdf - standard - fonts - metrics - bootstrap " type = " text /
javascript "
10 src = " jspdf . plugin . s t a n d a r d _ f o n t s _ m e t r i c s . js " > </ script >
11 < script id = " filesaver - min - bootstrap " type = " text / javascript " src = "
FileSaver . min . js " > </ script >
12
13 < script id = sap - ui - bootstrap " type = " text / javascript
14 src = https :// sapui5 . hana . ondemand . com / resources / sap - ui - core . js
15 data - sap - ui - theme = " sap_bluecrystal "
16 data - sap - ui - libs = sap . ui . commons , sap . ui . ux3 , sap . viz , sap . ui .
table , sap . ui . core
17 data - sap - ui - xx - bindingSyntax = " complex " >
18 </ script >
19
20 < script >
21 sap . ui . localResources ( " mainView " ) ;
22 sap . ui . localResources ( " dataTableView " ) ;
23 sap . ui . localResources ( " dataPayrollView " ) ;
24 sap . ui . localResources ( " orgStructureView " ) ;
25
26 var transferModel = new sap . ui . model . json . JSONModel () ;
27 transferModel . setData ({
28 FPBEG : [] ,
29 FPEND : [] ,
30 RELID : [] ,
31 BUKRS : [] ,
32 WERKS : [] ,
33 ENAME : []
34 }) ;
35
94
36 sap . ui . getCore () . setModel ( transferModel ) ;
37
38 var view = sap . ui . view ({ viewName : " mainView . shell " ,
39 type : sap . ui . core . mvc . ViewType . JS }) ;
40 view . placeAt ( " content " ) ;
41 </ script >
42 </ head >
43
44 < body class = " sapUiBody " role = " application " >
45 < div id = " content " > </ div >
46 </ body >
47 </ html >
95
D.2. customStyles.css
1 @CHARSET " ISO -8859 -1 " ;
2
3
4 /* Custom Styles */
5
6 . myWhiteCellStyle {
7 background - color : # FFFFFF ;
8 }
9
10 . myTex tField Margin {
11 margin - left : 6 px ;
12 margin - right : 6 px ;
13 }
96
D.3. shell.controller.js
1 sap . ui . controller ( " mainView . shell " , {
2 w or k set I t em s C o nt r o l : function ( oEvent , key ) {
3 oShell = this . getView () . byId ( " shell " ) ;
4 if ( key == " table_button " ) {
5 var viewTable = sap . ui . view ({ viewName : " dataTableView . table " ,
6 type : sap . ui . core . mvc . ViewType . JS }) ;
7 oShell . setContent ( viewTable , true ) ;
8 } else if ( key == " cluster_button " ) {
9 var viewCluster = sap . ui . view ({ viewName : " dataPayrollView .
cluster " ,
10 type : sap . ui . core . mvc . ViewType . JS }) ;
11 oShell . setContent ( viewCluster , true ) ;
12 } else if ( key == " structure_button " ) {
13 var viewStructure = sap . ui . view ({ viewName : " orgStructureView . tree " ,
14 type : sap . ui . core . mvc . ViewType . JS }) ;
15 oShell . setContent ( viewStructure , true ) ;
16 }
17
18 }
19 }) ;
97
D.4. shell.view.js
1 sap . ui . jsview ( " mainView . shell " , {
2 get Contro llerNa me : function () {
3 return " mainView . shell " ;
4 },
5
6 createContent : function ( oController ) {
7
8 // Shell
9 var oShell = new sap . ui . ux3 . Shell ( this . createId ( " shell " ) , {
10 showPane : false ,
11 showSearchTool : false ,
12 showFeederTool : false ,
13 showLogoutButton : false ,
14 designType : sap . ui . ux3 . ShellDesignType . Crystal ,
15
16 worksetItems : [
17 new sap . ui . ux3 . NavigationItem ({ key : " table_button " ,
text : " Volcado de tablas " }) ,
18 new sap . ui . ux3 . NavigationItem ({ key : " cluster_button " ,
text : " Ficha Salarial Empleado " }) ,
19 new sap . ui . ux3 . NavigationItem ({ key : " structure_button " ,
text : " Arbol estructura organizativa " })
20 ],
21
22 w ork s et I t em S e le c t e d : function ( oEvent ) {
23 var key = oEvent . getParameter ( " item " ) . getKey () ;
24 oController . wo r k se t I te m s Co n t ro l ( oEvent , key ) ;
25 }
26 }) ;
27
28 var viewTable = sap . ui . view ({ viewName : " dataTableView . table " ,
29 type : sap . ui . core . mvc . ViewType . JS }) ;
30 oShell . setContent ( viewTable , true ) ;
31
32 return oShell ;
33 }
34
35 }) ;
98
D.5. table.controller.js
1 sap . ui . controller ( " dataTableView . table " , {
2
3 setTableModel : function ( tableName , layout , table ) {
4 layout . setBusy ( true ) ;
5 this . tableModel = new sap . ui . model . json . JSONModel () ;
6 table . setModel ( this . tableModel ) ;
7
8 jQuery . ajax ({
9 url : " / p1940528821trial / myhanaxs / jcodata / json . model . xsjs / " ,
10 type : GET ,
11 contentType : application / json ,
12 dataType : json ,
13 data : {
14 tablename : tableName
15 },
16 success : jQuery . proxy ( function ( result ) {
17 // Asociamos el modelo a la tabla
18 this . tableModel . setData ( result ) ;
19 var rowcount = this . tableModel . oData . data . length ;
20 this . showData ( rowcount , table ) ;
21 } , this ) ,
22 error : jQuery . proxy ( function () {
23 this . uploadTable ( tableName ) ;
24 } , this ) ,
25 complete : jQuery . proxy ( function () {
26 layout . setBusy ( false ) ;
27 } , this )
28 }) ;
29 },
30
31
32 showData : function ( rowcount , table ) {
33 if ( rowcount === 0) {
34 sap . ui . commons . MessageBox . alert ( " Table was empty . " ) ;
35 rowcount = 2;
36 table . setNoData ( new sap . ui . commons . TextView ({
37 text : " "
38 }) ) ;
39 }
40
41 // Y atamos las filas y columnas al modelo
42 table . bindColumns ( " / metadata " , function ( sID , oContext ) {
43 var sProperty = oContext . getObject () ;
44 return new sap . ui . table . Column ({
45 width : 150 px ,
46 wrapping : true ,
47 id : sProperty ,
99
48 label : sProperty ,
49 template : sProperty ,
50 sortProperty : sProperty ,
51 filterProperty : sProperty
52 }) ;
53 }) ;
54
55 table . bindRows ( " / data " ) ;
56
57 table . bindProperty ( " visibleRowCount " , " / rowcount " , function () {
58 return Math . min (15 , rowcount ) ;
59 }) ;
60 },
61
62
63 uploadTable : function ( tableName ) {
64 jQuery . ajax ({
65 url : " https :// j c o t f p 1 9 4 0 5 2 8 8 2 1 t r i a l . hanatrial . ondemand . com /
jcotf / Contro llerSe rvlet " ,
66 type : POST ,
67 crossDomain : true ,
68 data : {
69 functionname : " / BODS / RFC_READ_TABLE2 " ,
70 tablename : tableName } ,
71 success : jQuery . proxy ( function ( result ) {
72 sap . ui . commons . MessageBox . alert ( " Tabla subida con exito !
");
73 layout = this . getView () . byId ( " layout " ) ;
74 table = this . getView () . byId ( " table " ) ;
75 this . setTableModel ( tableName , layout , table ) ;
76 } , this ) ,
77 error : function () { sap . ui . commons . MessageBox . alert ( " Error
subiendo tabla . " ) ; }
78 }) ;
79 }
80 }) ;
100
D.6. table.view.js
1 sap . ui . jsview ( " dataTableView . table " , {
2 get Contro llerNa me : function () {
3 return " dataTableView . table " ;
4 },
5
6 createContent : function ( oController ) {
7
8 // Layout
9 var oLayout = new sap . ui . commons . layout . MatrixLayout ( this . createId ( "
layout " ) , {
10 widths : 150 px ,
11 allowWrapping : true
12 }) ;
13
14 // Toolbar Controls
15 var oTableNameLabel = new sap . ui . commons . Label ({
16 text : " Introduzca el nombre de la tabla que desee ver ( e . g .:
PA0001 ) : "
17 }) ;
18
19 var oTableNameValue = new sap . ui . commons . TextField ( this . createId ( "
oTableNameValue " ) , {
20 value : ,
21 width : 20 em ,
22 press1 : [ oController . onLiveChange , oController ] ,
23 liveChange : function ( oEvent ) {
24 var searchBtn = oController . getView () . byId ( " oSearchButton " ) ;
25 if ( this . getLiveValue () === " " ) {
26 searchBtn . setEnabled ( false ) ;
27 } else {
28 searchBtn . setEnabled ( true ) ;
29 }
30 }
31 }) ;
32
33 var oSearchButton = new sap . ui . commons . Button ( this . createId ( "
oSearchButton " ) , {
34 text : " Search " ,
35 press1 : [ oController . onSearch , oController ] ,
36 enabled : false ,
37 press : function ( event ) {
38 var tableName = oController . getView () . byId ( " oTableNameValue " ) ;
39 var layout = oController . getView () . byId ( " layout " ) ;
40 var table = oController . getView () . byId ( " table " ) ;
41
42 oController . setTableModel ( tableName . getDomRef () . value , layout ,
table ) ;
101
43 }
44 }) ;
45
46
47
48 // Stock Values Table
49 var oTable = new sap . ui . table . Table ( this . createId ( " table " ) , {
50 visibleRowCount : 2 ,
51 toolbar : new sap . ui . commons . Toolbar ({
52 items : [ oTableNameLabel , oTableNameValue , oSearchButton ]
53 }) ,
54 noData : new sap . ui . commons . TextView ({
55 text : " Introduzca el nombre de la tabla que quiera ver o subir .
"
56 }) ,
57 }) ;
58
59
60
61 oLayout . createRow ( new sap . ui . commons . layout . MatrixLayoutCell () .
addContent ( oTable ) ) ;
62
63 // Add chart here
64
65 return oLayout ;
66 }
67
68 }) ;
102
D.7. cluster.controller.js
1 sap . ui . controller ( " dataPayrollView . cluster " , {
2
3 setHistoryModel : function ( employeeNumber , layout , table ) {
4 layout . setBusy ( true ) ;
5 this . tableModel = new sap . ui . model . json . JSONModel () ;
6 table . setModel ( this . tableModel ) ;
7
8 jQuery . ajax ({
9 url : " / p1940528821trial / myhanaxs / jcodata / json . model . xsjs / " ,
10 type : GET ,
11 contentType : application / json ,
12 dataType : json ,
13 data : {
14 tablename : " PH " + employeeNumber
15 },
16 success : jQuery . proxy ( function ( result ) {
17 // Asociamos el modelo a la tabla
18 this . tableModel . setData ( result ) ;
19 var rowcount = this . tableModel . oData . data . length ;
20 this . showData ( rowcount , table ) ;
21 } , this ) ,
22 error : jQuery . proxy ( function () {
23 this . uploadHistory ( employeeNumber ) ;
24 } , this ) ,
25 complete : jQuery . proxy ( function () {
26 layout . setBusy ( false ) ;
27 } , this )
28 }) ;
29 },
30
31
32 showData : function ( rowcount , table ) {
33 if ( rowcount === 0) {
34 sap . ui . commons . MessageBox . alert ( " Payroll History was empty . "
);
35 rowcount = 2;
36 table . setNoData ( new sap . ui . commons . TextView ({
37 text : " "
38 }) ) ;
39 }
40
41 // Y atamos las filas y columnas al modelo
42 table . bindColumns ( " / metadata " , function ( sID , oContext ) {
43 var sProperty = oContext . getObject () ;
44 return new sap . ui . table . Column ({
45 width : 150 px ,
46 wrapping : true ,
103
47 id : sProperty ,
48 label : sProperty ,
49 template : sProperty ,
50 sortProperty : sProperty ,
51 filterProperty : sProperty
52 }) ;
53 }) ;
54
55 table . bindRows ( " / data " ) ;
56
57 table . bindProperty ( " visibleRowCount " , " / rowcount " , function () {
58 return Math . min (15 , rowcount ) ;
59 }) ;
60 },
61
62
63 uploadHistory : function ( employeeNumber ) {
64 jQuery . ajax ({
65 url : " https :// j c o t f p 1 9 4 0 5 2 8 8 2 1 t r i a l . hanatrial . ondemand . com /
jcotf / Contro llerSe rvlet " ,
66 type : POST ,
67 crossDomain : true ,
68 data : {
69 functionname : " Z _ R F C _ H R _ R E A D _ P A Y R O L L _ E N T R I E S " ,
70 employeenumber : employeeNumber } ,
71 success : jQuery . proxy ( function ( result ) {
72 sap . ui . commons . MessageBox . alert ( " Historial Salarial
subido con exito ! " ) ;
73 layout = this . getView () . byId ( " layout " ) ;
74 table = this . getView () . byId ( " table " ) ;
75 this . setHistoryModel ( employeeNumber , layout , table ) ;
76 } , this ) ,
77 error : function () { sap . ui . commons . MessageBox . alert ( " Error
subiendo el Historial Salarial . " ) ; }
78 }) ;
79 },
80
81 passVariables : function ( oEvent ) {
82 var payrollModel = sap . ui . getCore () . getModel () ;
83 var indexSelectedRow = oEvent . getParameters () . rowIndex ;
84 payrollModel . setData ({
85 FPBEG : this . getView () . byId ( " table " ) . getModel () . oData . data [
indexSelectedRow ]. FPBEG ,
86 FPEND : this . getView () . byId ( " table " ) . getModel () . oData . data [
indexSelectedRow ]. FPEND ,
87 RELID : this . getView () . byId ( " table " ) . getModel () . oData . data [
indexSelectedRow ]. RELID ,
88 CDSEQ : this . getView () . byId ( " table " ) . getModel () . oData . data [
indexSelectedRow ]. CDSEQ ,
104
89 BUKRS : this . getView () . byId ( " table " ) . getModel () . oData . data [
indexSelectedRow ]. BUKRS ,
90 WERKS : this . getView () . byId ( " table " ) . getModel () . oData . data [
indexSelectedRow ]. WERKS ,
91 ENAME : this . getView () . byId ( " table " ) . getModel () . oData . data [
indexSelectedRow ]. ENAME ,
92 PERNR : this . getView () . byId ( " o E m p l o y e e N u m b e r V a l u e " ) . getDomRef () .
value
93 }) ;
94 }
95 }) ;
105
D.8. cluster.view.js
1 sap . ui . jsview ( " dataPayrollView . cluster " , {
2 get Contro llerNa me : function () {
3 return " dataPayrollView . cluster " ;
4 },
5
6 createContent : function ( oController ) {
7
8 // Layout
9 var oLayout = new sap . ui . commons . layout . MatrixLayout ( this . createId ( "
layout " ) , {
10 widths : 150 px ,
11 allowWrapping : true
12 }) ;
13
14 // Toolbar Controls
15 var oTableNameLabel = new sap . ui . commons . Label ({
16 text : " Introduzca el numero de un empleado para ver su historial
salarial : "
17 }) ;
18
19 var o E m p l o y e e N u m b e r V a l u e = new sap . ui . commons . TextField ( this .
createId ( " o E m p l o y e e N u m b e r V a l u e " ) , {
20 value : ,
21 width : 20 em ,
22 press1 : [ oController . onLiveChange , oController ] ,
23 liveChange : function ( oEvent ) {
24 var searchBtn = oController . getView () . byId ( " oSearchButton " ) ;
25 if ( this . getLiveValue () === " " ) {
26 searchBtn . setEnabled ( false ) ;
27 } else {
28 searchBtn . setEnabled ( true ) ;
29 }
30 }
31 }) ;
32
33 var oSearchButton = new sap . ui . commons . Button ( this . createId ( "
oSearchButton " ) , {
34 text : " Search " ,
35 press1 : [ oController . onSearch , oController ] ,
36 enabled : false ,
37 press : function ( event ) {
38 var tableName = oController . getView () . byId ( " o E m p l o y e e N u m b e r V a l u e
");
39 var layout = oController . getView () . byId ( " layout " ) ;
40 var table = oController . getView () . byId ( " table " ) ;
41
106
42 oController . setHistoryModel ( tableName . getDomRef () . value , layout ,
table ) ;
43 }
44 }) ;
45
46
47 var oTable = new sap . ui . table . Table ( this . createId ( " table " ) , {
48 visibleRowCount : 2 ,
49 toolbar : new sap . ui . commons . Toolbar ({
50 items : [ oTableNameLabel , oEmployeeNumberValue , oSearchButton ]
51 }) ,
52 noData : new sap . ui . commons . TextView ({
53 text : " Introduzca el numero de un empleado . "
54 }) ,
55 ro wS el ec ti on Ch an ge : function ( oEvent ) {
56 oController . passVariables ( oEvent ) ;
57 var viewResult = sap . ui . view ({ viewName : " dataPayrollView . result " ,
58 type : sap . ui . core . mvc . ViewType . JS }) ;
59 oShell . setContent ( viewResult , true ) ;
60 }
61 }) ;
62
63
64
65 oLayout . createRow ( new sap . ui . commons . layout . MatrixLayoutCell () .
addContent ( oTable ) ) ;
66
67 // Add chart here
68
69 return oLayout ;
70 }
71
72 }) ;
107
D.9. result.controller.js
1 sap . ui . controller ( " dataPayrollView . result " , {
2
3 getPayrollData : function () {
4
5 transferModel = sap . ui . getCore () . getModel () ;
6 var sequenceNumber = transferModel . oData . CDSEQ ;
7 var employeeNumber = transferModel . oData . PERNR ;
8 var payrollData ;
9
10 // Eliminamos los ceros a la izquierda del n de secuencia
11 sequenceNumber = sequenceNumber . replace (/^[0]+/ g , " " ) ;
12
13 jQuery . ajax ({
14 // Necesitamos que sea sincrono , si no payrollData estaria
indefinido
15 async : false ,
16 url : " / p1940528821trial / myhanaxs / jcodata / json . model . xsjs / " ,
17 type : GET ,
18 contentType : application / json ,
19 dataType : json ,
20 data : {
21 tablename : " PH " + employeeNumber + " _PE " +
sequenceNumber
22 },
23 success : jQuery . proxy ( function ( result ) {
24 payrollData = result ;
25 } , this ) ,
26
27 error : jQuery . proxy ( function () {
28 this . upl oa dP ay ro ll En tr y ( employeeNumber , sequenceNumber ,
relationId ) ;
29 // else
30 // sap . ui . commons . MessageBox . alert (" Esta funcion depende
de la tabla PA0001 para funcionar . " +
31 // " Si despues de subirla el error persiste , contacte
con el desarrollador de la aplicacion .")
32 } , this )
33 }) ;
34
35 return payrollData ;
36 },
37
38
39 up lo ad Pa yr ol lE nt ry : function ( employeeNumber , sequenceNumber ,
relationId ) {
40 jQuery . ajax ({
41 async : false ,
108
42 url : " https :// j c o t f p 1 9 4 0 5 2 8 8 2 1 t r i a l . hanatrial . ondemand . com /
jcotf / Contro llerSe rvlet " ,
43 type : POST ,
44 crossDomain : true ,
45 data : {
46 functionname : " Z _ R F C _ H R _ R E A D _ P A Y R O L L _ R E S U L T S " ,
47 employeenumber : employeeNumber ,
48 sequencenumber : sequenceNumber ,
49 relationid : relationId } ,
50 success : jQuery . proxy ( function ( result ) {
51 sap . ui . commons . MessageBox . alert ( " Entrada salarial subida
con exito ! " ) ;
52 this . getPayrollEntry () ;
53 } , this ) ,
54 error : function () { sap . ui . commons . MessageBox . alert ( " Error
subiendo entrada salarial . " ) ; }
55 }) ;
56
57 jQuery . ajax ({
58 async : false ,
59 url : " https :// j c o t f p 1 9 4 0 5 2 8 8 2 1 t r i a l . hanatrial . ondemand . com /
jcotf / Contro llerSe rvlet " ,
60 type : POST ,
61 crossDomain : true ,
62 data : {
63 functionname : " / BODS / RFC_READ_TABLE2 " ,
64 tablename : " PA0001 "
65 },
66 success : jQuery . proxy ( function ( result ) {
67 sap . ui . commons . MessageBox . alert ( " PA0001 subida con exito
!");
68 this . getPayrollEntry () ;
69 } , this ) ,
70 error : function () { sap . ui . commons . MessageBox . alert ( " Error
subiendo PA0001 . " ) ; }
71 }) ;
72 },
73
74 getChartModel : function ( year , grossPay ) {
75 var chartModel = new sap . ui . model . json . JSONModel ({
76 predictedSalary : [
77 { year_M : year , grossPay_M : grossPay }
78 ]
79 }) ;
80
81 for ( var i = 0; i < 4; i ++) {
82 dataObj = { year_M : ++ year , grossPay_M : ( grossPay *= 1.02) .
toFixed (2) }
83 chartModel . oData . predictedSalary . push ( dataObj ) ;
109
84 }
85
86 return chartModel ;
87 },
88
89 pdfPayroll : function ( organization , division , employeeName ,
payrollBeginning , payrollEnd ,
90 grossPay , taxableBase , baseOfContribution , baseOfContributionPPC
, IRPF , netPay ) {
91
92 var pdf = new jsPDF ( l , cm , a5 ) ;
93 pdf . setLineWidth (0.05) ;
94 pdf . setFontSize (10) ;
95
96 // 1.2 cm de margen horizontal entre rectangulos y borde de
pagina
97 // 0.2 cm de margen entre borde izquierdo o derecho de
rectangulo y linea
98 // 0.5 cm de margen vertical entre lineas de texto
99
100 // Rectangulo superior izquierdo : Datos de la Empresa
101 pdf . rect (1.2 , 0.5 , 9 , 3) ;
102 pdf . text (1.4 , 1 , " Empresa : " + organization ) ;
103 pdf . text (1.4 , 1.5 , " Division : " + division ) ;
104 pdf . text (1.4 , 2 , " Direccion de la empresa : Por subir " ) ;
105 pdf . text (1.4 , 2.5 , " CIF de la empresa : Por subir " ) ;
106 pdf . text (1.4 , 3 , " Codigo postal de la empresa : Por subir " ) ;
107
108
109 // Rectangulo superior derecho : Datos del empleado
110 pdf . rect (10.8 , 0.5 , 9 , 3) ;
111 pdf . text (11 , 1 , " Empleado : " + employeeName ) ;
112 pdf . text (11 , 1.5 , " Antiguedad : Por subir " ) ;
113 pdf . text (11 , 2 , " DNI del empleado : Por subir " ) ;
114 pdf . text (11 , 2.5 , " Numero Seguridad Social : Por subir " ) ;
115 pdf . text (11 , 3 , " Periodo de Nomina : " + payrollBeginning + " /
" + payrollEnd ) ;
116
117 // Tabla Central : Conceptos de nomina
118 // Header izquierdo
119 pdf . rect (1.2 , 4 , 15 , 1) ;
120 pdf . text (1.4 , 4.5 , " Concepto " ) ;
121 // Header derecho
122 pdf . rect (16.2 , 4 , 3.6 , 1) ;
123 pdf . text (16.4 , 4.5 , " Cantidad " ) ;
124 // Conceptos
125 pdf . rect (1.2 , 5 , 15 , 3.5) ;
126 pdf . text (1.4 , 5.5 , " Salario Bruto " ) ;
127 pdf . text (1.4 , 6 , " Base Imponible " ) ;
110
128 pdf . text (1.4 , 6.5 , " Base de Cotizacion " ) ;
129 pdf . text (1.4 , 7 , " Base de Cotizacion por contingencias
profesionales " ) ;
130 pdf . text (1.4 , 7.5 , " IRPF " ) ;
131 pdf . text (1.4 , 8 , " Salario Neto " ) ;
132 // Cantidades
133 pdf . rect (16.2 , 5 , 3.6 , 3.5) ;
134 pdf . text (16.4 , 5.5 , grossPay ) ;
135 pdf . text (16.4 , 6 , taxableBase ) ;
136 pdf . text (16.4 , 6.5 , ba se Of Co nt rib ut io n ) ;
137 pdf . text (16.4 , 7 , b a s e O f C o n t r i b u t i o n P P C ) ;
138 pdf . text (16.4 , 7.5 , " -" + IRPF ) ;
139 pdf . text (16.4 , 8 , netPay ) ;
140
141 // Rubrica
142 pdf . text (1.4 , 9.5 , " Sevilla , a " + this . spanishFullDate () ) ;
143
144 pdf . output ( save , nomina . pdf ) ;
145 },
146
147 spanishFullDate : function () {
148 today = new Date () ;
149
150 // Mes para la rubrica
151 switch ( today . getMonth () + 1) {
152 case 1:
153 var month = " enero " ;
154 break ;
155 case 2:
156 var month = " febrero " ;
157 break ;
158 case 3:
159 var month = " marzo " ;
160 break ;
161 case 4:
162 var month = " abril " ;
163 break ;
164 case 5:
165 var month = " mayo " ;
166 break ;
167 case 6:
168 var month = " junio " ;
169 break ;
170 case 7:
171 var month = " julio " ;
172 break ;
173 case 8:
174 var month = " agosto " ;
175 break ;
111
176 case 9:
177 var month = " septiembre " ;
178 break ;
179 case 10:
180 var month = " octubre " ;
181 break ;
182 case 11:
183 var month = " noviembre " ;
184 break ;
185 case 12:
186 var month = " diciembre " ;
187 }
188
189 return today . getDate () + " de " + month + " de " + today . getFullYear
() ;
190 }
191 }) ;
112
D.10. result.view.js
1 sap . ui . jsview ( " dataPayrollView . result " , {
2 get Contro llerNa me : function () {
3 return " dataPayrollView . result " ;
4 },
5
6 createContent : function ( oController ) {
7
8 transferModel = sap . ui . getCore () . getModel () ;
9
10
11 var employeeName = transferModel . oData . ENAME ;
12 var organization = transferModel . oData . BUKRS ;
13 var division = transferModel . oData . WERKS ;
14 var payrollBeginning = transferModel . oData . FPBEG ;
15 var payrollEnd = transferModel . oData . FPEND ;
16 var relationId = transferModel . oData . RELID ;
17
18 // Layout
19 var oLayout = new sap . ui . commons . layout . MatrixLayout ( this . createId ( "
layout " ) , {
20 widths : 150 px ,
21 width : " 100 % " ,
22 allowWrapping : true
23 }) . addStyleClass ( " myWhiteCellStyle " ) ;
24
25 var payrollData = oController . getPayrollData () ;
26
27 // Datos salariales del empleado
28
29 oLayout . createRow ( " Ficha de Empleado : " ) ;
30 oLayout . createRow ( " Nombre : " + employeeName ) ;
31 oLayout . createRow ( " Organizacion : " + organization + " Division :
" + division ) ;
32 oLayout . createRow ( " Periodo de nomina : " + payrollBeginning + " - " +
payrollEnd ) ;
33
34 for ( var i = 0; i < payrollData . data . length ; i ++) {
35 switch ( payrollData . data [ i ]. LGART ) {
36 case " /101 " :
37 var printWageType = true ;
38 var prefix = " Salario Bruto : " ;
39 var grossPay = parseFloat ( payrollData . data [ i ]. BETRG ) ;
40 break ;
41 case " /106 " :
42 var printWageType = true ;
43 var prefix = " Base Imponible : " ;
44 var taxableBase = parseFloat ( payrollData . data [ i ]. BETRG ) ;
113
45 break ;
46 case " /342 " :
47 var printWageType = true ;
48 var prefix = " Base de Cotizacion : " ;
49 var ba se Of Co nt rib ut io n = parseFloat ( payrollData . data [ i ]. BETRG ) ;
50 break ;
51 case " /343 " :
52 var printWageType = true ;
53 var prefix = " Base de Cotizacion por contingencias profesionales
: ";
54 var b a s e O f C o n t r i b u t i o n P P C = parseFloat ( payrollData . data [ i ]. BETRG
);
55 break ;
56 case " /398 " :
57 var printWageType = false ;
58 var contribution = parseFloat ( payrollData . data [ i ]. BETRG ) ;
59 break ;
60 case " /401 " :
61 var printWageType = true ;
62 var prefix = " IRPF : " ;
63 var IRPF = parseFloat ( payrollData . data [ i ]. BETRG ) ;
64 break ;
65 default :
66 var printWageType = false ;
67 }
68
69 if ( printWageType )
70 oLayout . createRow ( prefix + payrollData . data [ i ]. BETRG ) ;
71 };
72
73 var netPay = ( grossPay - contribution - IRPF ) . toFixed (2) ;
74
75 oLayout . createRow ( " Salario neto : " + netPay . toString () ) ;
76
77 // Prediccion salarial a 5 anios
78
79 chartModel = oController . getChartModel ( parseInt ( payrollEnd . substring
(0 , 4) ) , grossPay ) ;
80
81 var chartDataset = new sap . viz . ui5 . data . FlattenedDataset ({
82 dimensions : [
83 {
84 axis : 1 , // must be one for the x - axis , 2 for y - axis
85 name : Anio ,
86 value : " { year_M } "
87 }
88 ],
89
90 measures : [
114
91 // measure 1
92 {
93 name : Salario Bruto , // name is used as label in
the Legend
94 value : { grossPay_M } // value defines the binding
for the displayed value
95 },
96 ],
97
98 data : {
99 path : " / predictedSalary "
100 }
101
102 }) ;
103
104 var oBarChart = new sap . viz . ui5 . Bar ({
105 width : " 50 % " ,
106 height : " 400 px " ,
107 plotArea : {
108 // colorPalette : d3 . scale . category20 () . range ()
109 },
110 title : {
111 visible : true ,
112 text : Prediccion de Salario Bruto a 5 anios .
113 },
114 dataset : chartDataset
115 }) ;
116
117 oBarChart . setModel ( chartModel ) ;
118
119 oLayout . createRow ( oBarChart ) ;
120
121 // Botones para generar nomina y volver
122
123 var oButton1 = new sap . ui . commons . Button ( this . createId ( " button1 " ) , {
124 text : " Generar nomina " ,
125 press : function () {
126 oController . pdfPayroll ( organization , division , employeeName ,
payrollBeginning , payrollEnd ,
127 grossPay , taxableBase , baseOfContribution ,
baseOfContributionPPC , IRPF , netPay ) ;
128 }
129 }) . addStyleClass ( " my TextFi eldMar gin " ) ;
130
131 var oButton2 = new sap . ui . commons . Button ( this . createId ( " button2 " ) , {
132 text : " Volver " ,
133 press : function () {
134 var viewCluster = sap . ui . view ({ viewName : " dataPayrollView . cluster
",
115
135 type : sap . ui . core . mvc . ViewType . JS }) ;
136 oShell . setContent ( viewCluster , true ) ;
137 }
138 }) ;
139
140 oLayout . createRow (
141 new sap . ui . commons . layout . MatrixLayoutCell ({
142 content : [ oButton1 , oButton2 ]
143 })
144 );
145
146 return oLayout ;
147 }
148 }) ;
116
D.11. tree.controller.js
1 sap . ui . controller ( " orgStructureView . tree " , {
2 createTree : function ( oTree , sIdValue , oLayout ) {
3 oLayout . setBusy ( true ) ;
4 this . org Struc tureMo del = new sap . ui . model . json . JSONModel () ;
5
6 jQuery . ajax ({
7 url : " / p1940528821trial / myhanaxs / jcodata / json . model . xsjs / " ,
8 type : GET ,
9 contentType : application / json ,
10 dataType : json ,
11 data : {
12 tablename : " STO_ID " + sIdValue
13 },
14 success : jQuery . proxy ( function ( oModel ) {
15 // Asociamos el modelo a la tabla
16 this . drawTree ( oTree , oModel ) ;
17 } , this ) ,
18 error : jQuery . proxy ( function () {
19 this . uploadTree ( oTree , sIdValue , oLayout ) ;
20 } , this ) ,
21 complete : jQuery . proxy ( function () {
22 oLayout . setBusy ( false ) ;
23 } , this )
24 }) ;
25 },
26
27 drawTree : function ( oTree , oModel ) {
28 var addNode ;
29 nodeList = [];
30 for ( var i = 0; i < oModel . data . length ; i ++) {
31 addNode = true ;
32
33 // We check for repeated nodes . If a node is already in the tree ,
we don t add it .
34 for ( var j = 0; j < nodeList . length ; j ++)
35 if ( parseInt ( oModel . data [ i ]. SEQNR ) === nodeList [ j ])
36 addNode = false ;
37
38 if ( addNode ) {
39 nodeList [ nodeList . length ] = parseInt ( oModel . data [ i ]. SEQNR ) ;
40
41 switch ( oModel . data [ i ]. OTYPE ) {
42 case " O " :
43 var prefix = " Es superior directo de : " ;
44 break ;
45 case " S " :
46 var prefix = " Comprende : " ;
117
47 break ;
48 case " P " :
49 var prefix = " Titular : " ;
50 break ;
51 default :
52 var prefix = " " ;
53 }
54
55
56 var oNode = new sap . ui . commons . TreeNode ( this . createId ( " Node_ " +
oModel . data [ i ]. SEQNR ) , {
57 text : ( oModel . data [ i ]. LEVEL !== " 1 " ? prefix : " " ) + oModel .
data [ i ]. STEXT ,
58 expanded : false
59 }) ;
60
61 if ( oModel . data [ i ]. LEVEL !== " 1 " ) {
62 var oParent = this . getView () . byId ( " Node_ " + oModel . data [ i ]. PUP
);
63 oParent . addNode ( oNode ) ;
64 }
65
66 else
67 oTree . addNode ( oNode ) ;
68 }
69
70 }
71
72 },
73
74 uploadTree : function ( oTree , sIdValue , oLayout ) {
75 jQuery . ajax ({
76 url : " https :// j c o t f p 1 9 4 0 5 2 8 8 2 1 t r i a l . hanatrial . ondemand . com /
jcotf / Contro llerSe rvlet " ,
77 type : POST ,
78 crossDomain : true ,
79 data : {
80 functionname : " Z _ R F C _ H R _ R E A D _ O R G _ S T R U C T U R E " ,
81 objecttype : " O " ,
82 objectid : sIdValue ,
83 evaluationpath : "O -S - P "
84 },
85 success : jQuery . proxy ( function ( result ) {
86 sap . ui . commons . MessageBox . alert ( " Tabla subida con exito !
");
87 this . createTree ( oTree , sIdValue , oLayout ) ;
88 } , this ) ,
89 error : function () { sap . ui . commons . MessageBox . alert ( " Error
subiendo tabla . " ) ; }
118
90 }) ;
91 }
92 }) ;
119
D.12. tree.view.js
1 sap . ui . jsview ( " orgStructureView . tree " , {
2 get Contro llerNa me : function () {
3 return " orgStructureView . tree " ;
4 },
5
6 createContent : function ( oController ) {
7 var oLayout = new sap . ui . commons . layout . MatrixLayout ( this . createId ( "
layout " ) , {
8 layoutFixed : false ,
9 columns : 2 ,
10 width : " 100 % "
11 }) ;
12
13 var oIdLabel = new sap . ui . commons . Label ({
14 text : " Introduzca la ID del nodo raiz que desee ver ( tipo O ) : "
15 }) ;
16
17 var oIdValue = new sap . ui . commons . TextField ( this . createId ( " textfield
" ) ,{
18 value : ,
19 width : 20 em ,
20
21 press1 : [ oController . onLiveChange , oController ] ,
22 liveChange : function ( oEvent ) {
23 var searchBtn = oController . getView () . byId ( " searchbutton " ) ;
24 if ( this . getLiveValue () === " " ) {
25 searchBtn . setEnabled ( false ) ;
26 } else {
27 searchBtn . setEnabled ( true ) ;
28 }
29 }
30 }) ;
31
32 var oIdButton = new sap . ui . commons . Button ( this . createId ( "
searchbutton " ) ,{
33 text : " Search " ,
34 enabled : false ,
35 press : function ( event ) {
36 var oIdValue = oController . getView () . byId ( " textfield " ) ;
37 var sIdValue = oIdValue . getDomRef () . value ;
38 var oTree = oController . getView () . byId ( " tree " ) ;
39 var oLayout = oController . getView () . byId ( " layout " ) ;
40
41 oController . createTree ( oTree , sIdValue , oLayout ) ;
42 }
43 }) ;
44
120
45 oRow = new sap . ui . commons . layout . MatrixLayoutRow ({
46 cells : [
47 new sap . ui . commons . layout . MatrixLayoutCell ({
48 // backgroundDesign : sap . ui . commons . layout .
BackgroundDesign . Plain ,
49 content : new sap . ui . commons . layout . HorizontalLayout ( "
treeViewHorizontalLayout ", {
50 content : [ oIdLabel , oIdValue . addStyleClass ( "
myT extFie ldMarg in " ) , oIdButton ]
51 })
52 }) . addStyleClass ( " myWhiteCellStyle " ) ,
53 new sap . ui . commons . layout . MatrixLayoutCell ({
54 // backgroundDesign : sap . ui . commons . layout .
BackgroundDesign . Plain
55 }) . addStyleClass ( " myWhiteCellStyle " ) ]
56 }) ;
57
58 oLayout . addRow ( oRow ) ;
59
60 var oTree = new sap . ui . commons . Tree ( this . createId ( " tree " ) ) ;
61
62 oLayout . createRow ( new sap . ui . commons . layout . MatrixLayoutCell ({
63 content : oTree ,
64 colSpan : 2
65 }) ) ;
66
67 return oLayout ;
68 }
69
70 }) ;
121
D.13. model.json.xsjs
1 // **** Example for basic REQUEST RESPONSE handling
2 var paramName ; var paramValue ; var headerName ; var headerValue ; var
contentType ;
3
4 // Implementation of GET call
5 function handleGet () {
6 paramName = $ . request . parameters [0]. name ;
7 paramValue = $ . request . parameters [0]. value ;
8
9 if ( paramName === tablename ) {
10 var data = [];
11 var metadata = [];
12 var i ;
13 var result ;
14 var stmt ;
15 if ( paramValue . match (/\ bPH \ d +\ b / g ) !== null ) {
16 stmt = " SELECT FPBEG , FPEND , BUKRS , WERKS , ENAME , RELID , CDSEQ FROM \"
N E O _ 2 Y R C M 3 I 3 B M 6 5 X 2 E P A L 4 2 I E L V B \".\" " + paramValue + " \" " ;
17 stmt = stmt + " INNER JOIN \" N E O _ 2 Y R C M 3 I 3 B M 6 5 X 2 E P A L 4 2 I E L V B \".\"
PA0001 \" " ;
18 stmt = stmt + " ON \" " + paramValue + " \". PERNR = \" PA0001 \". PERNR
";
19 } else {
20 stmt = " SELECT * FROM \" N E O _ 2 Y R C M 3 I 3 B M 6 5 X 2 E P A L 4 2 I E L V B \".\" " +
paramValue + " \" " ;
21 }
22 var conn = $ . db . getConnection () ;
23 var pstmt = conn . prepareStatement ( stmt ) ;
24 var rs = pstmt . executeQuery () ;
25 var rsmd = pstmt . getMetaData () ;
26 var numColumns = rsmd . getColumnCount () ;
27 // Save metadata
28
29 var row ;
30 for ( i = 1; i <= numColumns ; i ++) {
31 metadata . push ( rsmd . getColumnLabel ( i ) ) ;
32 }
33 // Save data
34
35 while ( rs . next () ) {
36 row = {};
37 for ( i = 1; i <= numColumns ; i ++) {
38 row [ metadata [i -1]] = rs . getString ( i ) ;
39 }
40 data . push ( row ) ;
41 }
42 $ . response . contentType = " application / json " ;
122
43 // $ . response . status = $ . net . http . CREATED ;
44 rs . close () ;
45 pstmt . close () ;
46 conn . close () ;
47 result = {
48 " metadata " : metadata ,
49 " data " : data
50 };
51 return result ;
52 }
53 return { " myResult " : " Wrong parameters " };
54 }
55 // Check Content type headers and parameters
56 function validateInput () {
57 var i ; var j ;
58 // Check content - type is application / json
59 contentType = $ . request . contentType ;
60 if ( contentType === null || contentType . startsWith ( " application / json "
) === false ) {
61 $ . response . status = $ . net . http . I N T E R N A L _ S E R V E R _ E R R O R ;
62 $ . response . setBody ( " Wrong content type request use application / json "
);
63 return false ;
64 }
65 // Extract parameters and process them
66 for ( i = 0; i < $ . request . parameters . length ; ++ i ) {
67 paramName = $ . request . parameters [ i ]. name ;
68 paramValue = $ . request . parameters [ i ]. value ;
69 if ( paramName !== tablename ) {
70 return false ;
71 }
72 }
73 // Extract headers and process them
74 for ( j = 0; j < $ . request . headers . length ; ++ j ) {
75 headerName = $ . request . headers [ j ]. name ;
76 headerValue = $ . request . headers [ j ]. value ;
77 // Add logic
78 }
79 return true ;
80 }
81 // Request process
82 function processRequest () {
83 if ( validateInput () ) {
84 try {
85 switch ( $ . request . method ) {
86 // Handle your POST calls here
87 case $ . net . http . GET :
88 $ . response . setBody ( JSON . stringify ( handleGet () ) ) ;
89 break ;
123
90 // Handle your other methods : PUT , DELETE
91 default :
92 $ . response . status = $ . net . http . ME TH OD _N OT _A LL OW ED ;
93 $ . response . setBody ( " Wrong request method " ) ;
94 break ;
95 }
96 $ . response . contentType = " application / json " ;
97 } catch ( e ) {
98 $ . response . status = $ . net . http . I N T E R N A L _ S E R V E R _ E R R O R ;
99 $ . response . setBody ( " Failed to execute action : " + e . toString () ) ;
100 }
101 }
102 }
103 // Call request processing
104 processRequest () ;
124