Buscar

Limpiar filtro
Pregunta
Yone Moreno · 25 nov, 2022

Desde un Entorno de INTEGRACION conectar a otro de PREPRODUCCION para acceder al Servidor de Recursos que Valida Token JWT

Buenos días, Hemos estado indagando de qué manera activar un Servidor para Generar Tokens y un Servidor de Recursos asociado para Validar el Token. Este paso, actualmente lo hemos averiguado con el inestimable apoyo de @Alberto Fuentes de Intersystems. A continuación la necesidad es la siguiente: Para centralizar en 1 único Entorno ( PREPRODUCCION ) y en 1 único Namespace el Servidor de Recursos ( por ejemplo en un NAMESPACE dedicado llamado AUTHSERVER ) ; necesitaríamos de alguna manera "llamar", "invocar", "comunicar" desde los otros entornos ( por ejemplo INTEGRACION ) con el Servidor de Recursos de PREPRODUCCION con el fin de Validar el Token. ¿Por favor, ustedes nos podrían orientar, guiar, pautar, instruir al respecto? 📍¿Qué vía existe para "llamar", "invocar", "comunicar" desde los otros entornos ( por ejemplo INTEGRACION ) con el Servidor de Recursos de PREPRODUCCION con el fin de Validar el Token? En concreto, disponemos en Integracion de un Servicio REST llamado "Servicios.REST.Radiologia.CConcertadosv01r00", y dentro un método denominado "ResServer". Nuestras pesquisas nos han llevado a profundizar en el siguiente código: ClassMethod ResServer(accessToken As %String(MAXLEN="")) As %Status { $$$LOGINFO("Entra en método ResServer") // This is a dummy resource server which just gets the access token from the request // and uses the introspection endpoint to ensure that the access token is valid. // Normally the response would not be security related, but would contain some interesting // data based on the request parameters. // retrieve access token from HTTP request ;set accessToken = ##class(%SYS.OAuth2.AccessToken).GetAccessTokenFromRequest(.status) /* if $$$ISERR(status) { set %response.Status = ..#HTTP401UNAUTHORIZED write "[Error] GetAccessTokenFromRequest: "_$system.Status.GetErrorText(status),! quit $$$OK } */ $$$LOGALERT("Antes de set isJWTValid = ##class(%SYS.OAuth2.Validation).ValidateJWT('resserver',accessToken,'','',.jwtPayload ,.securityParameters,.sc)") // validate token ;set isJWTValid = ##class(%SYS.OAuth2.Validation).ValidateJWT("resserver",accessToken,"","",.jwtPayload ,.securityParameters,.sc) set isJWTValid = ##class(%SYS.OAuth2.Validation).ValidateJWT("https://[Servidor de PRE]:57773/resserver/",accessToken,"","",.jwtPayload ,.securityParameters,.sc) $$$LOGALERT("Despues de isJWTValid ... apuntando a https://[Servidor de PRE]:57773/resserver/") $$$LOGINFO("Antes de if (('isJWTValid) || ($$$ISERR(sc))) {") $$$LOGINFO("Imprimir ('isJWTValid): "_('isJWTValid)) $$$LOGINFO("Imprimir ($$$ISERR(sc): "_($$$ISERR(sc))) $$$LOGINFO("Imprimir $system.Status.GetErrorText(sc): "_$system.Status.GetErrorText(sc)) if (('isJWTValid) || ($$$ISERR(sc))) { /* set %response.Status = ..#HTTP401UNAUTHORIZED write "Error Getting Access Token="_$system.Status.GetErrorText(sc),! */ quit '$$$OK } $$$LOGINFO("Antes de set sc = ##class(%SYS.OAuth2.AccessToken).GetIntrospection('resserver', accessToken, .jsonObject)") // introspection set sc = ##class(%SYS.OAuth2.AccessToken).GetIntrospection("resserver", accessToken, .jsonObject) if $$$ISERR(sc) { /* set %response.Status = ..#HTTP401UNAUTHORIZED write "Introspection Error="_..EscapeHTML($system.Status.GetErrorText(sc)),! */ quit '$$$OK } $$$LOGINFO("Antes de write 'OAuth 2.0 access token used to authorize resource server (RFC 6749)<br>'") /* write "OAuth 2.0 access token used to authorize resource server (RFC 6749)<br>" write "Access token validated using introspection endpoint (RFC 7662)<br>" write " scope='"_jsonObject.scope_"'<br>" write " user='"_jsonObject.username_"'",! */ $$$LOGINFO("Antes del final quit $$$OK") quit $$$OK } Seguidamente nuestras indagaciones nos llevan a: Ésta línea si llama desde PREPRODUCCION al Servidor de Recursos de PREPRODUCCION y Valida el Token de manera correcta: set isJWTValid = ##class(%SYS.OAuth2.Validation).ValidateJWT("resserver",accessToken,"","",.jwtPayload ,.securityParameters,.sc) Sin embargo si nosotros tratamos invocar desde INTEGRACION al Servidor de Recursos de PREPRODUCCION calificando el primer parámetro, el "client" con la Dirección completa del Servidor de Recursos de PRE ; da Excepción: set isJWTValid = ##class(%SYS.OAuth2.Validation).ValidateJWT("https://[DNS de PRE]:57773/resserver/",accessToken,"","",.jwtPayload ,.securityParameters,.sc) En particular la Excepción que observamos en los LOGS del Servicio REST es: ERROR #5002: Error de cache: <UNDEFINED>zValidateJWT+22^%SYS.OAuth2.Validation.1 *client Lo cual parece que se debe al hecho de que en la función interna de la clase: "%SYS.OAuth2.Validation" titulada: "ValidateJWT" ; en la llamada "Set client=##class(OAuth2.Client).Open(applicationName,.sc)" Se genera una Excepción basada en el hecho de que le cuesta abrir el Servidor de Recursos; en la clase: OAuth2.Client en el método Open en la línea Set client=##class(OAuth2.Client).%OpenId(applicationName,,.sc) ¿Por favor, ustedes nos podrían orientar, guiar, pautar, instruir al respecto? 📍 La cuestión es ¿Qué vía existe para "llamar", "invocar", "comunicar" desde los otros entornos ( por ejemplo INTEGRACION ) con el Servidor de Recursos de PREPRODUCCION con el fin de Validar el Token? Muchísimas gracias de antemano a ustedes por su tiempo, apoyo y auxilio. Un saludo Adicionalmente hemos leído con atención: https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GOAUTH_CERTS https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GOAUTH_background https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GOAUTH_certs#GOAUTH_certs_client https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GOAUTH_certs#GOAUTH_certs_resource https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=GOAUTH_jwt_headers https://community.intersystems.com/post/making-jwtoauth20 Sin embargo, sí seguimos con la misma cuestión: ¿Qué vía existe para "llamar", "invocar", "comunicar" desde los otros entornos ( por ejemplo INTEGRACION ) con el Servidor de Recursos de PREPRODUCCION con el fin de Validar el Token? Es decir la pregunta de otra forma sería: ¿Qué mecanismo existe para desde un Entorno A (Integración) se comunique con un Entorno B (Preproducción) con la misión de Validar el Token desde el Entorno A empleando el Servidor de Recursos centralizados disponible en el Entorno B? Muchísimas gracias por su atención, y gracias por sus respuestas. Un saludo Hola Yone, Entiendo que quieres montar lo siguiente: * Cliente externo (e.g. Postman) que solicite token a servidor de autorización. * Servidor de autorización en PREPRODUCCIÓN. * Servicio REST en INTEGRACIÓN que actúe como servidor de recursos, que reciba y valide el token. En principio estás en un escenario como el de *Client Credentials* del [workshop-iris-oauth2](https://github.com/intersystems-ib/workshop-iris-oauth2). En resumen, tendrías que configurar lo siguiente: * Definir servidor autorización OAuth en PREPRODUCCIÓN. * Registrar cliente en servidor de autorización en PREPRODUCCIÓN: tendrás ClientID y ClientSecret. Lo necesitarás para solicitar token. * Definir servidor de recursos en INTEGRACIÓN como se hacía en este [paso](https://github.com/intersystems-ib/workshop-iris-oauth2#a3-resource-server). * Cuando defines el servidor de recursos, tienes que indicar cuál es el endpoint del servidor de autorización. * Tienes que asignarle un nombre, e.g. "resserver" * En el [código](https://github.com/intersystems-ib/workshop-iris-oauth2/blob/4c0143af52606686d105b81c29fa35649e4fcc63/oauth-resource-server/src/res/Server.cls#L33) del servidor de recursos, en INTEGRACIÓN, en el método `ValidateJWT ` sólo necesitas referirte al nombre que le hayas puesto al servidor de recursos (e.g. "resserver"). * El método `ValidateJWT` ya se encarga de utilizar tu definición de servidor de recursos "resserver", donde tenías el endpoint del servidor de autorización, y validar el token que le pases. Hola Alberto, Gracias por la respuesta. Lo estudiamos internamente, probamos concienzudamente y te respondemos en cuanto nos sea posible. Muchas gracias de antemano. Un saludo.
Artículo
Ricardo Paiva · 4 sep, 2019

Contenedores, ¿qué es un contenedor?

¡Hola a tod@s! Con el lanzamiento de la plataforma de datos InterSystems IRIS, podemos ofrecer nuestro producto incluso en un contenedor Docker. Pero... ¿qué es un contenedor? La principal definición de un contenedor es que se trata de un entorno protegido para un proceso. Los contenedores son paquetes definidos por un software, que tienen algunas similitudes con las máquinas virtuales (MV), como por ejemplo, que pueden ejecutarse. Los contenedores proporcionan aislamiento sin la necesidad de emular completamente al sistema operativo (SO). Por lo tanto, los contenedores son mucho más ligeros que una MV. En esencia, los contenedores son una respuesta a la cuestión de cómo trasladar de forma fiable una aplicación desde un sistema/entorno a otro y garantizar que funcione. Cuando todas las dependencias de la aplicación se encierran dentro de un contenedor y crean un espacio para aislar un proceso, hay más posibilidades de garantizar que la solución de la aplicación se ejecutará al moverse entre las plataformas. El sistema operativo nos permite ejecutar procesos. Estos procesos comparten espacios de direcciones, namespaces, grupos de control (cgroups)... y, en general, tienen acceso a todo el entorno del sistema operativo, a su programación y también los administran. Todo esto es positivo. Sin embargo, ¿qué pasaría si quisiéramos aislar un proceso en particular o un número de procesos para ejecutar una tarea, operación o servicio específicos? Los contenedores nos brindan la capacidad de aislar un proceso. Por lo tanto, podríamos definir un contenedor como el entorno protegido para un proceso. Entonces ¿qué es un entorno protegido? Es un grado de aislamiento en el cual un contenedor guarda sus procesos. Esta característica se implementa mediante la función central de Linux llamada namespaces (https://en.wikipedia.org/wiki/Linux_namespaces), la cual también permite que otras partes importantes del sistema se aíslen, como la interfaz de red, los puntos de montaje, comunicaciones entre procesos (IPC) y tiempo compartido universal (uts). El contenedor o entorno protegido también puede dirigirse o controlarse mediante otra función central llamada grupos de control o cgroups (https://en.wikipedia.org/wiki/Cgroups). Las reglas que damos a los contenedores sirven para garantizar que el contenedor sea un buen vecino cuando comparta recursos con otros contenedores y con el servidor. Para entender cuál es la diferencia entre un contenedor y una MV, podríamos utilizar la analogía de que una MV es como una casa, mientras que el contenedor es como un piso. Las máquinas virtuales son autónomas e independientes como una casa. Todas las casas tienen su propia infraestructura: tuberías, calefacción, electricidad, etc. Además, una casa tiene requerimientos mínimos (al menos 1 dormitorio, 1 techo, etc.) En cambio, los contenedores se construyeron para aprovechar una infraestructura compartida y por ello podemos compararlos con un piso. El complejo de pisos comparte las tuberías, la calefacción, el sistema eléctrico, la entrada principal, los ascensores, etc. De la misma forma, los contenedores aprovechan los recursos disponibles del servidor mediante el kernel de Linux. También, hay que considerar que los pisos son de diferentes tamaños y formas. Debido a que los contenedores no tienen un sistema operativo completo, sino únicamente las características mínimas para acoplarse a Linux, como algunos ejecutables en /bin, algunos archivos de configuración y definición en /etc y algunos otros archivos, estos pueden ser de tamaño pequeño, lo cual los hace más rápidos al momento de moverlos o ejecutarlos en segundo plano. Esto se traduce en mayor velocidad a partir del momento en que se construyen, desde el canal de suministro de la fábrica de software hasta la ejecución final en producción. Por cierto, los contenedores se ajustan perfectamente al contexto de la arquitectura de microservicios CI/CD, pero esa es otra historia. Los procesos en el contenedor están estrechamente relacionados con el ciclo de vida del contenedor. Cuando inicializo un contenedor, normalmente quiero que todos los servicios de mi aplicación se ejecuten y funcionen correctamente (por ejemplo, piense en el puerto 80 para el contenedor de un servidor web y en los puertos 57772 y 1972 para un contenedor de InterSystems IRIS). Cuando detengo el contenedor, también se detienen todos los procesos. Lo que describí en esta publicación es la noción fundamental del tiempo de ejecución de un contenedor, es decir, un entorno protegido que aísla sus procesos del servidor y de otros contenedores. Hay otra parte para entender mejor el trabajo que realizan los contenedores, el cual hace referencia a sus imágenes. Pero esto lo veremos en una siguiente publicación.
Artículo
Fabiano Sanches · 18 feb, 2020

Panel de Control "IRIS History Monitor"

¡Hola a todos! Quiero compartir un proyecto personal que comenzó con una simple solicitud en el trabajo: ¿Es posible saber cuántas licencias de Caché estamos usando? Leyendo otros artículos en la Comunidad, encontré este excelente artículo de David Loveluck: APM - Utilizando "Caché History Monitor"https://community.intersystems.com/post/apm-using-cach%C3%A9-history-monitor Siguiendo el artículo de David, empezé a usar Caché History Monitor para mostrar toda esa información. Al considerar la pregunta: ¿Qué tecnología genial debo usar? Mi decisión fue CSP, simple y potente, para que mi cliente se diera cuenta de que Caché es mucho más que MUMPS/Terminal. Después de crear las páginas para mostrar el historial de Licencias, Crecimiento de la base de datos y Sesiones CSP, he decidido crear un nuevo diseño para las páginas del Panel de Control y de los Procesos del Sistema. Todo funciona muy bien con mi instancia de Caché. Sin embargo, ¿qué pasa con IRIS? Siguiendo el artículo de Evgeny Shvarov: Utilizando Docker con tu repositorio de desarrollo de InterSystems IRIShttps://community.intersystems.com/post/using-docker-your-intersystems-iris-development-repository Yo lo "dockerizé" y subí los códigos a GitHub, así que ahora todo el mundo puede intentarlo con unos pocos pasos. Cómo ejecutarlo Para comenzar a codificar con este repositorio, sigue estos pasos: 1. Clona el repositorio en cualquier directorio local:$ git clone https://github.com/diashenrique/iris-history-monitor.git 2. Abre el terminal en este directorio y ejecútalo:$ docker-compose build 3. Ejecuta el contenedor IRIS con tu proyecto:$ docker-compose up -d Cómo probarlo Abre el navegador y ve a: Ejemplo: http://localhost:52773/csp/irismonitor/dashboard.csp El usuario _SYSTEM puede ejecutar el Panel de Control y las otras funciones. Panel de Control del Sistema El Panel de Control del Sistema muestra los siguientes elementos: Licencias (Licensing) Hora del sistema (System Time) Errores de la aplicación (Application Errors) Procesos de Cache (Cache Processes) Sesiones de CSP (CSP Sessions) Bloqueos de tablas (Lock Table) Espacio del Diario (Journal Space) Estado del Diario (Journal Status) Servidor de aplicaciones ECP (ECP AppServer) Servidor de datos ECP (ECP DataServer) Write Daemon Eficiendia de Cache (Cache Efficiency) Alertas graves (Serious Alerts) Los widgets de gráficos de líneas trazan un punto cada 5 segundos. Menu del Sistema Procesos del Sistema Filtros de Procesos Utiliza diferentes filtros para obtener los resultados que necesites. También podrás utilizar hacer Ordenaciones Múltiples (Multiple Sorts), presionando Shift + click en el encabezado de la columna. ¡E incluso exportar la cuadrícula de datos a Excel! History Monitor El "History Monitor" para Sesiones CSP y Licencias muestran la información entre tres secciones: Cada 5 minutos Diariamente A cada hora El Crecimiento de la Base de Datos (Database Growth) solo muestra información diaria. Las páginas de historial comparten las siguientes características: Selector de Rango de Fechas (Date Range Picker) El valor predeterminado es "Últimos 7 Días" (Last 7 Days). Gráficos / Tablas de Datos En la parte superior derecha de cada pantalla, hay dos botones: Gráfico / Tabla de Datos (Chart / Data Tables) La tabla de datos muestra la información con la que se crea el gráfico, y también se puede descargar en formato Excel. Excel muestra el mismo formato, contenido y grupo definidos en el CSP. Zoom Todos los gráficos tienen la opción "Zoom" para visualizar la información con más detalle. Promedio y Máximo Para las sesiones "por hora" o "por día", los gráficos muestran los valores Promedio y Máximo. AVG (Promedio) MAX (Máximo) ¡Espero que os resulte útil!
Artículo
Jose-Tomas Salvador · 20 abr, 2021

Conexión con JDBC desde Python a la base de datos de IRIS - una nota rápida

Palabras clave: Python, JDBC, SQL, IRIS, Jupyter Notebook, Pandas, Numpy y aprendizaje automático  *Hoy me he encontrado con este artículo de Zphong Li, que publicó en Enero de 2020 pero que creo que es muy interesante y aún útil a día de hoy. Así que... para los que estéis haciendo vuestros primeros pinitos en Machine Learning con InterSystems IRIS, Python y Jupyter... aquí lo tenéis!!* ## 1. Objetivo Esta es una nota sencilla de 5 minutos, donde os muestro cómo invocar el controlador JDBC de IRIS con la ayuda de Python 3, por ejemplo desde un Jupyter Notebook, para leer y escribir datos en una instancia de la base de datos de IRIS vía SQL. El año pasado Zhong Li publicó una nota breve sobre como [Enlazar Python con una base de datos Caché](https://community.intersystems.com/post/deep-learning-demo-kit-python3-binding-healthshare-part-i) (sección 4.7). Ahora podría ser el momento de recapitular algunas opciones y discusiones sobre el uso de Python para acceder a una base de datos de IRIS, para leer sus datos en un dataframe de Pandas y una matriz de NumPy para realizar un análisis básico, y después escribir algunos datos pre-procesados o normalizados de nuevo en IRIS y que esté listo para canalizaciones (*pipelines*) adicionales de ML/DL. Inmediatamente se me ocurren varias **opciones**: 1.    **ODBC**: ¿Cómo hacerlo con PyODBC para Python 3 y SQL nativo? 2.    **JDBC**: ¿Cómo hacerlo con JayDeBeApi para Python 3 y SQL nativo? 3.    **Spark**:  ¿Cómo hacerlo con PySpark y SQL? 4.    **API nativa de Python para IRIS**: ¿Qué hay más allá del anterior Python Binding para Caché? 5.   ** ¿IPtyhon Magic SQL %%sql**? ¿Podría [](https://github.com/catherinedevlin/ipython-sql) funcionar con IRIS?  ¿Hay alguna otra opción que se me haya escapado? También estoy interesado en probarla.  ## 2. Alcance  ¿Comenzamos con un enfoque JDBC común? En la siguiente nota breve recapitularemos ODBC, Spark y la API nativa de Python.  ### En el alcance: Los siguientes componentes comunes se abordan en esta demostración rápida: Anaconda Jupyter Notebook  Python 3 JayDeBeApi JPyPe Pandas NumPy Una instancia de IRIS 2019.x ### Fuera del alcance: En esta nota rápida NO se abordarán los siguientes aspectos - son importantes y pueden tratarse por separado con soluciones, implementaciones y servicios específicos:  Seguridad de extremo a extremo. Rendimiento no funcional, etc. Solución de problemas y soporte. Licencias.  ## 3. Demostración ### 3.1 Ejecución de una instancia de IRIS: Simplemente ejecuté un contenedor IRIS 2019.4 como servidor "remoto" de la base de datos. Puedes utilizar cualquier instancia de IRIS a la que tengas acceso autorizado. zhongli@UKM5530ZHONGLI MINGW64 /c/Program Files/Docker Toolbox$ docker psCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                PORTS                                              NAMESd86be69a03ab        quickml-demo        "/iris-main"        3 days ago          Up 3 days (healthy)   0.0.0.0:9091->51773/tcp, 0.0.0.0:9092->52773/tcp   quickml ### 3.2 Anaconda y Jupyter Notebook: Reutilizaremos el mismo enfoque de configuración descrito [aquí](https://community.intersystems.com/post/deep-learning-demo-kit-python3-binding-healthshare-part-i) para Anaconda (sección 4.1) y [aquí](https://community.intersystems.com/post/run-deep-learning-demo-python3-binding-healthshare-part-ii) para Jupyter Notebook (sección 4) en un ordenador portátil. Python 3.x se instala junto con este paso. ### 3.3 Instalar JayDeBeApi y JPyPe: Inicié mi JupyterNotebook, y luego simplemente ejecuté lo siguiente en sus celdas para configurar un puente desde Python-a-JDBC/Java:     !conda install --yes -c conda-forge jaydebeapi JayDeBeApi utiliza JPype 0.7 en el momento de escribir este artículo (enero del 2020), pero no funciona debido a un error conocido, por lo que tuve que utilizar la 0.6.3 !conda install --yes -c conda-forge JPype1=0.6.3 --force-reinstall ### 3.4 Conectar a una base de datos de IRIS por medio de JDBC  Hay una [documentación oficial de JDBC en IRIS](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_jdbc) aquí.  Para las ejecuciones de Python SQL sobre JDBC, utilicé los siguientes códigos como ejemplo. Se conecta a una tabla de datos llamada "`DataMining.IrisDataset`" dentro del namespace "USER" de esta instancia de IRIS.  ### 1. Establezca las variables de entorno, si es necesario #importa os #os.environ['JAVA_HOME']='C:\Progra~1\Java\jdk1.8.0_241' #os.environ['CLASSPATH'] = 'C:\interSystems\IRIS20194\dev\java\lib\JDK18\intersystems-jdbc-3.0.0.jar' #os.environ['HADOOP_HOME']='C:\hadoop\bin'   #winutil binary must be in Hadoop's Home ### 2. Obtiene la conexión JDBC y el cursor import JayDeBeApi url = "jdbc:IRIS://192.168.99.101:9091/USER" driver = 'com.intersystems.jdbc.IRISDriver' user = "SUPERUSER" password = "SYS" #libx = "C:/InterSystems/IRIS20194/dev/java/lib/JDK18" jarfile = "C:/InterSystems/IRIS20194/dev/java/lib/JDK18/intersystems-jdbc-3.0.0.jar" conn = jaydebeapi.connect(driver, url, [user, password], jarfile) curs = conn.cursor() ### 3. Especifica la fuente de la tabla de datos dataTable = "DataMining.IrisDataset" ### 4. Obtiene el resultado y visualiza curs.execute("select TOP 20 * from %s" % dataTable) result = curs.fetchall() print("Total records: " + str(len(result))) for i in range(len(result)):     print(result[i]) ### 5. Cerrar y limpiar - Los mantendre abiertos para los próximos accesos. #curs.close() #conn.close()   Total records: 150 (1, 1.4, 0.2, 5.1, 3.5, 'Iris-setosa') (2, 1.4, 0.2, 4.9, 3.0, 'Iris-setosa') (3, 1.3, 0.2, 4.7, 3.2, 'Iris-setosa') ... ... (49, 1.5, 0.2, 5.3, 3.7, 'Iris-setosa') (50, 1.4, 0.2, 5.0, 3.3, 'Iris-setosa') (51, 4.7, 1.4, 7.0, 3.2, 'Iris-versicolor') ... ... (145, 5.7, 2.5, 6.7, 3.3, 'Iris-virginica') ... ... (148, 5.2, 2.0, 6.5, 3.0, 'Iris-virginica') (149, 5.4, 2.3, 6.2, 3.4, 'Iris-virginica') (150, 5.1, 1.8, 5.9, 3.0, 'Iris-virginica')   Ahora hemos verificado que Python en JDBC estaba funcionando. Lo siguiente es solo un poco de análisis de datos de rutina y preprocesamiento para los canales habituales de ML que deberíamos mencionar una y otra vez para demostraciones y comparaciones posteriores, por lo que se adjunta para mayor comodidad.  ### 3.5 Convertir los resultados de SQL al DataFrame de Pandas y después a las matrices de NumPy Instala los paquetes Pandas y NumPy a través de Conda si aún no están instalados, como se explicó en el punto 3.3. A continuación, ejecute lo siguiente como un ejemplo: ### Transforma los resultados de SQL "sqlData" en un dataframe de Pandas "df", y después en una matriz de NumPy "arrayN" para otras canalizaciones (*pipelines*) de ML import pandas as pd sqlData = "SELECT * from DataMining.IrisDataset" df= pd.io.sql.read_sql(sqlData, conn) df = df.drop('ID', 1) df = df[['SepalLength', 'SepalWidth', 'PetalLength', 'PetalWidth', 'Species']] # Establece las etiquetas en 0, 1, 2, para la matriz NumPy df.replace('Iris-setosa', 0, inplace=True) df.replace('Iris-versicolor', 1, inplace=True) df.replace('Iris-virginica', 2, inplace=True) # Convierte el dataframe en una matriz Numpy arrayN = df.to_numpy() ### 6. Cierra y limpia - ¿si la conexión ya no es necesaria? #curs.close() #conn.close() Echemos un vistazo rutinario a los datos actuales: df.head(5) df.describe() Ahora tenemos un DataFrame, y una matriz NumPy normalizada de una tabla de datos fuente a nuestra disposición.   Ciertamente, ¿podemos probar varios análisis de rutina con los que comenzaría un experto en ML, como se indica a continuación, en Python para reemplazar a [R, como en el enlace que se encuentra aquí](http://www.lac.inpe.br/~rafael.santos/Docs/CAP394/WholeStory-Iris.html)? La fuente de datos se cita aquí ### 3.6 Dividir los datos y volver a escribirlos en la base de datos de IRIS por medio de SQL: Ciertamente, podemos dividir los datos en un conjunto de Entrenamiento y otro de Validación o Prueba, como es habitual, y después escribirlos de nuevo en tablas temporales de la base de datos, para algunas emocionantes funciones ML de IRIS: import numpy as np from matplotlib import pyplot from sklearn.model_selection import train_test_split # mantiene, por ejemplo, el 20% = 30 filas como datos de prueba, y el 80% = 120 filas para entrenamiento X = arrayN[:,0:4] y = arrayN[:,4] X_train, X_validation, Y_train, Y_validation = train_test_split(X, y, test_size=0.20, random_state=1, shuffle=True) # Hace que el 80% de las filas, escogidas aleatoriamente, estén en el conjunto Entrenamiento labels1 = np.reshape(Y_train,(120,1)) train = np.concatenate([X_train, labels1],axis=-1) # Hace que el 20% de las filas, escogidas eleatoriamente, estén en el conjunto Prueba, lTest1 = np.reshape(Y_validation,(30,1)) test = np.concatenate([X_validation, lTest1],axis=-1) # Escribe el conjunto de datos de entrenamiento en un dataframe de Pandas dfTrain = pd.DataFrame({'SepalLength':train[:, 0], 'SepalWidth':train[:, 1], 'PetalLength':train[:, 2], 'PetalWidth':train[:, 3], 'Species':train[:, 4]}) dfTrain['Species'].replace(0, 'Iris-setosa', inplace=True) dfTrain['Species'].replace(1, 'Iris-versicolor', inplace=True) dfTrain['Species'].replace(2, 'Iris-virginica', inplace=True) # Escribe los datos de pruebas en un dataframe de Pandas dfTest = pd.DataFrame({'SepalLength':test[:, 0], 'SepalWidth':test[:, 1], 'PetalLength':test[:, 2], 'PetalWidth':test[:, 3], 'Species':test[:, 4]}) dfTest['Species'].replace(0, 'Iris-setosa', inplace=True) dfTest['Species'].replace(1, 'Iris-versicolor', inplace=True) dfTest['Species'].replace(2, 'Iris-virginica', inplace=True) ### 3. Especifica los nombres de las tablas temporales dtTrain = "TRAIN02" dtTest = "TEST02" ### 4. Crea 2 tablas temporales - puedes probar eliminar las tablas temporales y volver a crearlas una y otra vez curs.execute("Create Table %s (%s DOUBLE, %s DOUBLE, %s DOUBLE, %s DOUBLE, %s VARCHAR(100))" %(dtTrain,dfTrain.columns[0],dfTrain.columns[1],dfTrain.columns[2], dfTrain.columns[3], dfTrain.columns[4])) curs.execute("Create Table %s (%s DOUBLE, %s DOUBLE, %s DOUBLE, %s DOUBLE, %s VARCHAR(100))" %(dtTest,dfTest.columns[0],dfTest.columns[1],dfTest.columns[2],dfTest.columns[3],dfTest.columns[4])) ### 5. Escribe el conjunto de Entrenamiento y el conjunto de Prueba en las tablas. Se puede intentar borrar cada vez el registro anterior y luego insertarlo otra vez. curs.fast_executemany = True curs.executemany( "INSERT INTO %s (SepalLength, SepalWidth, PetalLength, PetalWidth, Species) VALUES (?, ?, ?, ? ,?)" % dtTrain, list(dfTrain.itertuples(index=False, name=None)) ) curs.executemany( "INSERT INTO %s (SepalLength, SepalWidth, PetalLength, PetalWidth, Species) VALUES (?, ?, ?, ? ,?)" % dtTest, list(dfTest.itertuples(index=False, name=None)) ) ### 6. Cierra y limpia - ¿si la conexión ya no es necesaria? #curs.close() #conn.close() Ahora, si cambiamos a la Consola de Administración de IRIS, o a la Consola del Terminal SQL, deberíamos ver 2 tablas temporales creadas: TRAIN02 con 120 filas y TEST02 con 30 filas. Tendré que detenerme aquí, ya que supuestamente este artículo es una nota rápida muy breve. ## 4. Advertencias * El contenido anterior puede ser modificado o perfeccionado.  ## 5. Siguiente Simplemente reemplazaremos las secciones 3.3 y 3.4 con PyODBC, PySPark y la API nativa de Python para IRIS, a menos que a alguien no le importe contribuir con una nota rápida - también lo agradeceré.    
Artículo
Marcio Pereira · 23 feb, 2022

Implementación de patrones de proyecto utilizando el lenguaje Caché Object Script

En proyectos de software orientados a objetos son utilizados comúnmente patrones de proyecto para resolución de problemas. Si usted desarrolla en COS, este artículo tendrá sentido para su día a día. En proyectos de software orientados a objetos son utilizados comúnmente patrones de proyectos para la resolución de problemas que pueden ocurrir con frecuencia en determinados contextos. El presente artículo aborda la implementación de patrones de proyecto utilizando el lenguaje Caché Object Script, teniendo en cuenta que existen limitaciones de la tecnología para implementaciones técnicas de programación orientada a objetos. Serán utilizados algunos de los principales patrones abordados en la literatura para comprender las dificultades encontradas para implementaciones de patrones de proyectos utilizando el lenguaje en cuestión, así como existen posibilidades de entorno para implementaciones imposibilitadas por las limitaciones del lenguaje durante el desarrollo. Por fin, destaca la capacidad del lenguaje utilizado para implementaciones complejas de proyectos de software orientados a objetos. Palabras llave: Caché, Cache Object Script, Patrones de proyecto, programación orientada a objetos. Introducción Intersystems Caché es una base de datos de alto desempeño que entrega un conjunto completo de servicios para la construcción de sistemas complejos de gerenciamiento de bases de datos. Dentro de esos servicios, podemos citar el almacenamiento de datos, gerenciamiento de concurrencias, transacciones y gerenciamiento de procesos. Dentro de Caché, los datos pueden ser modelados y almacenados como tablas, objetos o arreglos multidimensionales (jerarquías). Diferentes modelos pueden acceder a datos de forma ininterrumpida -sin la necesidad de mapeamiento de desempeño entre modelos. Soporte embutido a objetos de datos dinámicos (como XML y JSON) posibilitan fácil interoperabilidad y desarrollo rápido de aplicaciones web. (INTERSYSTEMS CACHÉ, 2019) Figura 1 Arquitectura de la base de datos InterSystems Caché, por (INTERSYSTEMS CACHÉ, 2019) Object Script es un lenguaje de programación de objetos proyectado para desarrollar rápidamente aplicaciones de negocios complejos. (INTERSYSTEMS CACHÉ, 2019) Según la documentación, COS es un lenguaje de programación completo que ofrece recursos para manipulación de cadenas (strings), entradas y salidas, soporte para matrices espaciales y multidimensionales, soporte para SQL embarcados, comandos de direccionamiento de flujo de control dentro de un aplicativo y soporte nativo para objetos, incluyendo métodos, propiedades y polimorfismo. Para sistemas complejos basados y proyectados con orientación a objetos, es común depararse con problemas de determinados contextos. Para la resolución de esos problemas, proyectistas de software se apoyan en la utilización de patrones de proyecto (Design Patterns). Design Patterns (Patrones de proyecto) son patrones de modelado de software representados por principios de modelado de estructuras y códigos fuente que fueron probados diversas veces en diversos escenarios y obtuvieran éxito. (GUERRA, 2015) Para Buschman (1996), un patrón describe una solución para un problema que ocurre con frecuencia durante el desarrollo de software, pudiendo ser considerado como un par “problema/solución”. Según Gamma (1995), en general un patrón tiene cuatro elementos esenciales: El nombre del patrón, que es una referencia para describir un problema de proyecto, sus soluciones y consecuencias. El problema, que describe en que situación aplicar el patrón. La solución, que describe los elementos que componen el patrón de proyecto, sus relacionamientos, sus responsabilidades y colaboraciones. Las consecuencias, que son los resultados y análisis de las ventajas y desventajas de la aplicación patrón. Figura 2 El espacio de los patrones de proyecto (Gamma, 1995) Gamma (1995). Catalogó 23 patrones de diseño, conforme presentado en la figura 2, de forma de organizarlos, facilitar y aprender los patrones con mayor rapidez, así como direccionar esfuerzos en el descubrimiento de nuevos. Los patrones fueron clasificados a partir de dos criterios: Finalidad, reflectando lo que hace el patrón. Los patrones pueden tener finalidad de creación, estructural o comportamental. Los patrones estructurales lidian con la composición de clases u objetos. Los patrones comportamentales caracterizan las formas por las cuales clases u objetos interactúan y distribuyen las responsabilidades. (GAMMA y otros, 2015). Alcance, especificando si el patrón se aplica primariamente a clases o a objetos. Los patrones para clases lidian con los relacionamientos entre clases y subclases. Esos relacionamientos son establecidos a través del mecanismo de herencia, así ellos son estáticos -fijados en tiempo de compilación. Los patrones para objetos lidian con el relacionamiento entre objetos que pueden ser alterados en tiempo de ejecución y son más dinámicos. Casi todos utilizan la herencia en cierta medida. (GAMMA y otros, 2015). El presente artículo tiene como objetivo implementar patrones de proyecto en el lenguaje COS y verificar si la tecnología soporta implementaciones basadas en patrones para resolución de problemas. Con la búsqueda, será posible identificar posibles limitaciones y fuerzas de lenguaje para las implementaciones de proyectos de software complejo. Para tal, serán utilizados algunos patrones con complejidad de implementación más baja clasificados con la finalidad de creación, estructural y de alcance de clases y de objetos. Los patrones utilizados están descritos abajo, así como una breve descripción según Gamma (1995): Factory Method, define una interface para crear un objeto, pero deja que las subclases decidan cual será instanciada. El Factory Method permite a una clase postergar (aplazar) la instanciación de las subclases. Builder, separa la construcción de un objeto complejo de su representación, de modo que el mismo proceso de construcción pueda crear diferentes representaciones. Prototype, especifica los tipos de objetos a ser creados usando una instancia prototípica y crea nuevos objetos copiando ese prototipo. Singleton, garantiza que una clase tenga solamente una instancia y proporciona un punto global para su acceso. Facade, proporciona una interface unificada para un conjunto de interfaces en un subsistema. Facade define una interface de nivel mas alto que vuelve el subsistema más fácil de usar. Desarrollo La práctica utilizó la versión 2018.1.3 de Caché, implementando y evaluando los patrones de proyectos en los siguientes tópicos utilizando el lenguaje de programación COS. Factory Method Las clases abajo ejemplifican la utilización del patrón Factory Method para tratar un problema de negocio relacionado en que es necesario especificar el tipo de ejecución que es lanzada. Fueron definidos tres tipos de excepciones: de negocio, inesperada y no tratada. Figura 3 Implementación de la clase excecao.ExcecaoFactory (por el autor) La figura 3 demuestra la implementación de la clase excecao.ExcecaoFactory, que efectivamente implementa el patrón. El método GetObjetoExcecao(), creado en la línea 6, recibe la instancia de un objeto de tipo AbstractException y con base en las implementaciones de los métodos auxiliares, decide cual objeto debe ser retornado de acuerdo con el tipo. Las figuras 4, 5 y 6, representan la implementación básica de las clases para cada tipo de excepción utilizada en el ejemplo. Figura 4 Implementación de la clase excecao.ExcecaoNegocio (por el autor) Figura 5 Implementación de la clase excecao.ExcecaoInesperada (por el autor) Figura 6 Implementación de la clase excecao.ExcecaoNaoTratada (por el autor) Builder Para la implementación del patrón Builder, fue utilizada la entidad Pessoa Jurídica como base. Esa entidad, de forma simple, es compuesta por las propiedades Razão Social, Nome Fantasia y Cnpj. La implementación de la clase puede ser analizada en la figura 7 abajo: Figura 7 Implementación de la clase builder.PessoaJuridica (por el autor) La implementación del patrón Builder, demostrado en la figura 8, define una propiedad de clase de tipo builder.PessoaJuridica, que es utilizada para almacenar las informaciones que son definidas para el objeto y para la clase utilizada en el Builder. Los métodos setters, creados en las líneas 21, 28 y 35, reciben un valor por parámetro y hacen el set en la propiedad de la clase y luego retornan la instancia del objeto. Ese retorno es necesario para que, en la utilización de la clase, demostrado en la figura 9, sea posible hacer llamadas a los métodos setter de forma funcional y secuencial, siendo posible en una única línea definir el objeto que es requerido al patrón de forma simple. Figura 8 Implementación de la clase builder.PessoaJuridicaBuilder (por el autor) Figura 9 Ejemplo de utilización del patrón builder (por el autor) Protoype La clase prototype.CasaPrototype, presentada en la figura 10 abajo, implementa el patrón prototype creando el método abstracto getPrototipe() en la línea 6: Figura 10 Implementación de la clase prototype.CasaPrototype (pelo autor) Las figuras 11 y 12 presentan la implementación de las clases prototype.SimplesPrototype e prototype.SobradoPrototype, ambas heredando la clase prototype.CasaPrototype. En las dos clases el método getPrototype() es implementado retornando una nueva instancia de si mismo, una copia del objeto prototipado. Figura 11 Implementación de la clase prototype.SimplesPrototype Figura 12 Implementación de la clase prototype.SobradoProtoype (por el autor) Singleton El patrón Singleton permite la creación de objetos únicos y con apenas una instancia, ofreciendo un punto de acceso global dentro de la aplicación. Con eso, el patrón tiene como definición garantizar que una clase tenga apenas una instancia de sí y que exista solamente un punto global de acceso, gerenciando la propia instancia y evitando que cualquier clase cree una instancia de ella. De esta forma, ninguna otra clase puede instanciarla además de ella misma. Como ejemplo, desarrollando en el lenguaje Java y utilizando el modificador synchronized garantiza que el patrón sea implementado, impidiendo que dos instancias de la clase sean creadas. En COS, ese modificador no existe. Para acceso global utilizando COS, puede accederse directamente a las globales de la base, pero no es posible guardar instancias de objetos en globales. Por presentar las limitaciones mencionadas arriba, no fue posible implementar el patrón Singleton en el lenguaje COS. Facade El patrón Facade tiene la intención de simplificar la utilización de subsistemas complejos implementando una interface única y razonable. Figura 13 Implementación de la clase facade.SistemasFacade (por el autor) En la imagen 13, la clase facade.SistemasFacade implementa el patrón creando una interfaz única para la utilización de sistemas de audio e imagen. El constructor instancia los objetos de las dos clases y ejecuta los métodos para definir las configuraciones necesarias. Para las clases que la utilizan, están expuestos los métodos para reproducción de video y audio, unificados y simplificados en una única clase. Análisis de los resultados Los resultados encontrados con las búsquedas se mostraron satisfactorios. Algunas dificultades fueron encontradas por falta de algunos modificadores para definiciones de alcance global, como interfaces. Además de los modificadores, técnicas como la sobrecarga de métodos no fueron posibles de ser utilizados por las limitaciones del lenguaje. Sin embargo, el desarrollo en el lenguaje COS es simple, lo que vuelve el trabajo y organización de las clases productiva. Conclusiones Es posible afirmar que, para la mayoría de los patrones catalogados, el lenguaje COS ofrece los recursos necesarios para la implementación orientada a objetos.
Artículo
Luis Angel Pérez Ramos · 11 jul, 2023

Instalación y adaptación de EMPI en modo Standalone - Alimentando a la bestia con FHIR - Configuración

¡Volvemos al ataque con nuestro EMPI! En artículos anteriores hemos visto como configurar y personalizar nuestro EMPI, hemos visto como podemos como podemos incluir nuevos pacientes en nuestro sistema mediante mensajería HL7, pero claro, ¡no todo es HL7 v.2 en esta vida! ¿Cómo podríamos configurar nuestra instancia del EMPI para trabajar con mensajería FHIR? ¿Qué es FHIR? Para los que no estéis demasiado familiarizados con el término FHIR únicamente indicar que son las iniciales de Fast Healthcare Interoperability Resource. FHIR es un estándar de interoperabilidad sanitario desarrollado por HL7 en el que, basándose en el formato JSON y comunicaciones REST, se establecen una serie de "recursos" que portan diferentes tipos de información (desde datos del paciente, centros hospitalarios, diagnósticos, citas médicas...) podéis echar un vistazo a todos estos recursos en su página oficial InterSystems y FHIR Desde InterSystems somos conscientes de la utilidad que FHIR proporciona para el mundo de la interoperabilidad sanitaria y por ello disponemos de una amplia gama de productos y funcionalidades que permiten aprovechar y exprimir todo el potencial del mismo: InterSystems FHIR Server que permite el almacenamiento y gestión de los recursos FHIR. InterSystems FHIR SQL Builder que pone al alcance de los usuarios toda la información almacenada en el repositorio FHIR mediante consultas SQL. InterSystems FHIR Adapter que permite el envío y recepción de mensajería FHIR en nuestras producciones. Pues bien, para nuestro artículo vamos a aprovechar la funcionalidad que nos proporciona el Adapter para recibir mensajería FHIR. Configuración del EMPI Instalación del adaptador de FHIR Repasemos en primer lugar de qué servicios disponemos en nuestra instalación standalone del EMPI: Cómo podéis observar, tras la instalación del EMPI en modo standalone se creó un servicio de Registry con todas las opciones necesariarias para configurar nuestro EMPI, así como un namespace que llamamos HSPIDATA y en el que disponemos de una producción para gestionar las funcionalidades de interoperabilidad que necesitamos. Para nuestro caso hemos creado un nuevo namespace denominado WEBINAR en el que desplegaremos una producción de interoperabilidad normal y corriente. Será en este namespace donde instalaremos el FHIR Interoperability Adapter, esta instalación nos publicará una aplicación web que será a la que enviemos nuestras llamadas REST con los mensajes FHIR. Para ello ejecutaremos los siguientes comandos desde el terminal: zn "WEBINAR" set status = ##class(HS.FHIRServer.Installer).InteropAdapterConfig("/csp/healthshare/webinar/fhir/r4") Una vez instalado comprobaremos desde el portal de gestión la nueva aplicación creada: Comprobemos nuestra producción en el namespace WEBINAR: Por defecto se han creado dos nuevos Business Components: InteropService: en nuestra producción le hemos cambiado el nombre a su clase HS.FHIRServer.Interop.Service que será el Business Service encargado de la recepción del mensaje FHIR remitido a nuestro endpoint /csp/healthshare/webinar/fhir/r4 InteropOperation: que hemos cambiado a HS.FHIRServer.Interop.Operation. Para nuestro ejemplo hemos ignorado este Business Operation ya que vamos a enviar la información del mensaje FHIR vía TCP a nuestra producción del EMPI Creación de Business Components para gestionar la mensajería FHIR Muy bien, ya tenemos nuestro Business Service habilitado para recibir los mensajes FHIR, ahora lo que nos interesa es extraer la información de dicho mensaje para poder remitírla a nuestra producción del EMPI. Echemos un ojo al tipo de mensaje que va a recibir HS.FHIRServer.Interop.Service: Include HS.FHIRServer /// FHIRServer REST Business Service Class HS.FHIRServer.Interop.Service Extends (Ens.BusinessService, HS.HC.Util.Trace.Helper) { Parameter SETTINGS = "TargetConfigName:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},Timeout:Basic"; /// Configuration item to which to send inbound messages. Property TargetConfigName As Ens.DataType.ConfigName [ InitialExpression = "HS.FHIRServer.Interop.Operation" ]; /// Timeout for dispatch (so we don't hold up the HTTP service too long or hang up a production shutdown). Property Timeout As %Integer [ InitialExpression = 25 ]; /// Process an incoming message into the production; dispatch it to the configured target. /// The Interoperability contract requires that errors be returned as %Status here. Method OnProcessInput(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status El mensaje es del tipo HS.FHIRServer.Interop.Request y si consultamos la documentación veremos que lleva asociado una propiedad denominada QuickStreamId la cual utilizaremos para extraer el mensaje FHIR asociado. Para ello redirigiremos el mensaje recibido en el Business Service al Business Process Webinar.BP.FHIRToHubRequest Este Business Process sólo tendrá como función la de redirigir el mensaje al Business Operation FromFHIRToMPI encargado del envío del mensaje vía TCP a la producción del EMPI. Method OnRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { Set tSC = ..SendRequestSync("FromFHIRToMPI", pRequest, .pResponse) Quit $$$OK } Echemos un vistazo a la clase Webinar.BO.ToMPI y expliquemos como obtiene el mensaje de FHIR y se transforma en un String que enviaremos a nuestra producción del EMPI: Include HS.FHIRServer Class Webinar.BO.ToMPI Extends Ens.BusinessOperation { Parameter ADAPTER = "EnsLib.TCP.CountedOutboundAdapter"; Property Adapter As EnsLib.TCP.CountedOutboundAdapter; Parameter INVOCATION = "Queue"; Parameter SETTINGS = "FHIRMetadataSet::selector?context={HS.FHIRServer.Util.ContextSearch/FHIRMetadataSets}"; /// FHIR Metadata Set. These are defined in HS_FHIRServer.FHIRMetadataSet. Property FHIRMetadataSet As %String(MAXLEN = 256); Method SendFHIRRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { // Get version of FHIR message configured Set tFHIRMetadataSetKey = $ZStrip($Piece(..FHIRMetadataSet, "/", 1), "<>W") // We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'="" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class(HS.SDA3.QuickStream).%OpenId(pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'="" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError(##class(HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat="json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat="xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj(tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (($IsObject(pRequest.Request.Json))&&(pRequest.Request.Json.%GetIterator().%GetNext())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj(pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus($$$ERROR($$$GeneralError, "FHIR interop request message missing FHIR content")) } // Transform Dynamic Object to string set tRequest = tDynObj.%ToJSON() // Send message to EMPI production Set tSC= ..Adapter.SendMessageString(tRequest,.tResponse) $$$ThrowOnError(pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC } ClassMethod GetDynObj(stream As %Stream.Object, fhirVersion As %String, fhirFormat As %String) As %DynamicObject { set schema = ##class(HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}.%FromJSON(stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class(HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj } XData MessageMap { <MapItems> <MapItem MessageType="HS.FHIRServer.Interop.Request"> <Method>SendFHIRRequest</Method> </MapItem> </MapItems> } } Analicemos paso a paso nuestra clase: El primer punto será conocer la versión de nuestro mensaje FHIR que habremos configurado en las opciones de nuestro Business Operation. Set tFHIRMetadataSetKey = $ZStrip($Piece(..FHIRMetadataSet, "/", 1), "<>W") Recuperamos el tipo de mensaje FHIR que estamos tratando, en nuestro caso será R4. A continuación extraemos el contenido del mensaje FHIR: // We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'="" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class(HS.SDA3.QuickStream).%OpenId(pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'="" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError(##class(HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat="json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat="xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj(tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (($IsObject(pRequest.Request.Json))&&(pRequest.Request.Json.%GetIterator().%GetNext())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj(pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus($$$ERROR($$$GeneralError, "FHIR interop request message missing FHIR content")) } Obtenemos el %DynamicObject de nuestro mensaje FHIR: ClassMethod GetDynObj(stream As %Stream.Object, fhirVersion As %String, fhirFormat As %String) As %DynamicObject { set schema = ##class(HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}.%FromJSON(stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class(HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj } Finalmente lo transformamos en un String y se lo enviamos vía TCP a nuestra producción del EMPI, devolviendo un 200 al sistema que nos envió el mensaje original: // Transform Dynamic Object to string set tRequest = tDynObj.%ToJSON() // Send message to EMPI production Set tSC= ..Adapter.SendMessageString(tRequest,.tResponse) $$$ThrowOnError(pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC Pues ya tendríamos nuestro mensaje remitido a nuestra producción del EMPI. Observemos la traza de un mensaje de ejemplo: En la siguiente entrega recuperaremos el String enviado, lo transformaremos nuevamente a un %DynamicObject y lo introduciremos en el EMPI para que ejecute la operación de registro/actualización. ¡Espero que os sea de utilidad!
Anuncio
David Reche · 17 mayo, 2019

Lanzamiento de HealthShare 2019.1

Nos complace anunciar la disponibilidad de HealthShare 2019.1.Con esta versión, HealthShare ofrece:Soluciones sanitarias, incluyendo mejoras sustanciales en el Visor Sanitario en el teléfono móvil, mejoras en las Infografías sanitarias, y la primera de una nueva línea de respuestas a cuestiones importantes de índole operativa y sanitaria, empezando por el Panel de control para el usuario frecuente del Departamento de Urgencias Health Insight. Mejora de los procesos con la nueva página para los usuarios sanitarios y comerciales, Inicio de sesión único (SSO) con historias clínicas electrónicas (HCE), y mejoras en los formularios para la recopilación de datos de los pacientes.Una base superior que ofrece nuevas herramientas para controlar, gestionar, e informar sobre la calidad de datos, flujo de datos, interacción del usuario y muchas más.Herramientas que incentivan la innovación, incluyendo intercambios flexibles de Arquitectura de Documentos Clínicos y Recursos de Interoperabilidad Sanitaria Rápida mejorados, así como servicios de proveedor de identidad compatibles con aplicaciones de terceros que trabajan conjuntamente con Personal Community.Soporte Internacional para Patient Index y Personal CommunityAsimismo, nos complace anunciar la disponibilidad del HealthShare Provider Directory en Estados Unidos, una solución para la gestión de datos personales maestros desarrollado para ser su única fuente fiable a la hora de recabar datos demográficos y profesionales.Por último, en reconocimiento de la naturaleza básica del registro completo y longitudinal de atención sanitaria, hemos cambiado el nombre del producto conocido como HealthShare Information Exchange: ahora es HealthShare Unified Care Record. Este nombre aparecerá en las Notas de la Versión ("Release notes") y en cualquier referencia futura del producto. Para conseguir todos los detalles de este producto, por favor conéctese al Portal de E-Learning de InterSystems para ver la Documentación del producto HealthShare 2019.1 y las Notas de la Versión ("Release notes"):Unified Care Record 2019.1 NotasHealth Insight 2019.1 NotasPatient Index 2019.1 NotasPersonal Community 2019.1 NotasProvider Directory 2019.1 NotasPara conseguir HealthShare 2019.1, visite la web del WRC Centro de Soporte Internacional.
Artículo
Mathew Lambert · 31 jul, 2020

ToolBox-4-Iris, caja de herramientas para IRIS

¡Hola desarrolladores! Después de trabajar un poco con IRIS, queremos compartir con vosotros la "caja de herramientas" para InterSystems IRIS: ToolBox-4-Iris. ¿En qué consiste? ToolBox-4-Iris es una API para IRIS que incluye un conjunto de herramientas muy útiles, no disponibles en IRIS y que simplifican enormemente el desarrollo de aplicaciones. Permite ahorrar tiempo y esfuerzo en las "herramientas típicas" que todo desarrollador necesita. Esto incluye clases adicionales, métodos individuales o incluso macros más eficientes, que se describen en sus respectivos paquetes. Contenido MacrosMacros generales de ObjectScript, macros de estado, macros de National Language Support y macros de JavaScript. Tipos de datosTipos de datos dinámicos adicionales para fechas, horas y registro de horas, que incluyen formato, ocupación de memoria, SQL, rendimiento y más. Entrada y salida de datosFunciones útiles para trabajar con archivos y directorios, incluyendo funciones generales para archivos y directorios, buscar contenidos en archivos y directorios, eliminar archivos y directorios, serializar instancias de objetos hacia/desde JSON y más. Gestión de eventos del lado del servidorFuncionalidad que permite desarrollar aplicaciones que reaccionen de forma dinámica a eventos del sistema o de los datos. Si bien IRIS cuenta con gestión de eventos basados en procesos, no tiene capacidad para responder de forma dinámica a eventos del sistema o de los datos (sólo mediante la implementación de disparadores).Incluye la gestión sincrónica o asincrónica de eventos por procesos del usuario o cola de eventos, uso sincrónico o asincrónico de eventos del sistema o gestión de eventos de datos persistentes. Utilidades varias Utilidades como funciones de calendario (año bisiesto, semana, día de la semana, días festivos, etc.), formateo libre de fecha, hora y registros de hora, funciones para ejecutar comandos del sistema operativo y alguna utlidad JSON (para copiar, revisar, comparar, exportar, importar, leer, escribir, etc.).. ¡Esperamos ansiosos vuestros comentarios! Y que ToolBox-4-Iris os resulte útil.
Anuncio
Esther Sanchez · 26 oct, 2020

Sesiones de Preguntas y Respuestas ("Live Q&A Sessions") en el Virtual Summit 2020

¡Hola Comunidad! Como sabéis, mañana martes 27 de octubre comienzan las Sesiones Técnicas ("Focus Sessions") del Virtual Summit. Más de 100 sesiones, de 20 minutos cada una, durante tres días, sobre mejores prácticas, nuevas tecnologías y hojas de ruta. Queremos comentaros que, al final de cada jornada de Sesiones Técnicas (martes, miércoles y jueves), habrá una sesión de Preguntas y Respuestas con los Product Managers de InterSystems. Y cualquiera puede participar en las sesiones y enviar sus preguntas. ¿Te animas? Para inscribirte a las sesiones de Preguntas y Respuestas ("Live Q&A"): 1. Regístrate o inicia sesión en el Virtual Summit. Irás a un lobby virtual. 2. Haz clic en el botón de "Focus Sessions" para entrar en el auditorio virtual. 3. Haz clic en Data Platform 1. Allí verás el horario de todas las sesiones de Preguntas y Respuestas ("Live Q&A sessions"): ➡️ Día 1: Martes, 27 de octubre NA/LATAM/EMEA UTC Time Boston Time Live Q&A with Data Platform Team 4:40 PM 12:40 PM ➡️ Día 2: Miércoles, 28 de octubre NA/LATAM/EMEA UTC Time Boston Time Live Q&A with Data Platform Team 4:40 PM 12:40 PM ➡️ Día 3: Jueves, 29 de octubre NA/LATAM/EMEA UTC Time Boston Time Live Q&A with Data Platform Team 4:40 PM 12:40 PM Nota: Puedes enviar tus preguntas por adelantado a VS2020questions@InterSystems.com Aprovechad para resolver vuestras dudas (Si aún no os habéis registrado en el Virtual Summit 2020, podéis hacerlo aquí >>)
Pregunta
Bernabé Martín · 9 dic, 2020

Cache sobre Virtualbox

Hola Comunidad, Tengo un Cache 5.2 instalado en una maquina Virtualbox con XP y otra con WIN7, para programas personales. En las dos maquinas de tanto en tanto me sale este error (adjunto al final) cuando ejecuto desde el CUBO “Portal de Gestión de Sistema”. Para solucionarlo reinstalas todo y vuelve a funcionar hasta que decide dejar de funcionar. Solicito de la comunidad algún atajo para no tener que gestionar toda la engorrosa maniobra de reinstalarlo todo de nuevo, por suerte no se utiliza muy a menudo la Gestión del Sistema. La version que tienes instalada es bastante antigua - lo adecuado sería el upgrade a la version más reciente de InterSystems IRIS. La puedes descargar desde Docker Hub. Es totalmente gratuita y tiene la funcionalidade de Cache actualizada y ampliada. Con respecto al caso que comentas lo más usual es que tenga que ver con el web server o bien con alguna dll relacionada.Lo que podrías hacer es:1) reiniciar el servicio 'web server for ...' desde las herramientas administrativas de windows;2) Si no te va con el 1), baja el servicio herramientas administrativas. Abre la consola de administrador y reinicia el apache con el listner en el puerto que usas. Algo como: httpd -k start -n INSTANCIACACHEhttpd -c "Listen 8972"Si tras hacer todo esto sigues con el error, te recomiendo una consulta al soporte WRC. Gracias, No funciona he parado el servicio apache "web server for cache" Yo no soy especialista en el sistema toda mi vida profesional he sido lo antiguamente llamado "analista/programador", lo cual quiere decir que ya tengo unos años y por ello tengo esta versión. Miraré el tema del IRIS para utilizarlo como Cache. Reitero gracias y salud. Miraré Ya he comentado antes que yo me quedé en MUMPS. He instalado el contenedor con IRIS (en un MAC), funciona pero tengo una primera dificultad, para mis necesidades necesito utilizar un TERMINAL más potente (por ejemplo Wrq Reflection), u otro de licencia libre. No he llegado aún a la importacion de RUTINAS Y GLOBALES y para ella tendré que generar los NAMESPACE. Dada mi situación de jubilado, todo esto, solo tiene el sentido de la inquietud y la invstigación personal. Gracias y salud
Artículo
Muhammad Waseem · 15 nov, 2021

Cómo guardar datos en Caché usando la conexión ODBC de Appeon PowerBuilder

En mis artículos anteriores, mostré los pasos para conectar y recuperar datos de Caché desde Appeon PowerBuilder usando ODBC. En este artículo, mostraré cómo guardar datos en Caché con Appeon PowerBuilder (https://www.appeon.com/products/powerbuilder) usando ODBC. Estoy usando Company.cls de Samples-Data (https://github.com/intersystems/Samples-Data/tree/master/cls/Sample) ¡Empecemos! Paso 1 : En primer lugar, debemos establecer una conexión (https://community.intersystems.com/post/connecting-cach%C3%A9-appeon-powerbuilder-using-odbc)Paso 2 : Necesitamos crear un objeto de ventana de datos (datawindow object), que se vinculará a la clase Company. En el menú File, selecciona New y elige el objeto de ventana de datos Freeform en la pestaña DataWindow. Paso 3: Selecciona SQL Select de la lista de fuentes de datos. Paso 4: Selecciona sample.company de la lista de tablas: Paso 5 : Selecciona las columnas deseadas en la lista de columnas y haz clic en Return. Paso 6: Esto abrirá una vista de diseño. Guarda la ventana de datos (datawindow) como d_company_entry después de los ajustes deseados. Paso 7: Asegúrate de anular la selección de la columna id en la lista de columnas sin fecha (Updatable columns) y selecciona id en la lista desplegable del campo Identity Column, ya que la identificación se generará automáticamente desde Caché. Paso 8: En el control de ventana, añade el objeto de ventana de datos (d_company_entry) que ya creamos al control de ventana de datos Paso 9: Ahora necesitamos insertar una fila en el control de ventana de datos usando la función Insertrow(0) del control de ventana de datos. Paso 10: Añade algunos datos usando el control de ventana de datos de PowerBuilder. Paso 11: Guarda los datos en el control de la ventana de datos utilizando la función update() del control de la ventana de datos. Eso es todo. Se añade el ID 21
Artículo
Ricardo Paiva · 18 nov, 2021

Descripción de la aplicación fhir-integratedml-example

Hablando con mi amigo @Renato.Banzai, especialista en Machine Learning, me expuso uno de los mayores retos a los que se enfrentan actualmente las empresas: la implementación del Machine Learning (ML) y la Inteligencia Artificial (IA) en entornos reales. Intersystems IRIS ofrece IntegratedML. IntegratedML es una excelente herramienta para practicar, probar y realizar implementaciones de modelos de ML e IA. La parte más complicada de crear ML/IA es procesar los datos, depurarlos y hacerlos fiables. ¡Ahí es donde podemos aprovechar el estándar FHIR! La idea del proyecto muestra cómo podemos crear/practicar/validar modelos ML/IA con FHIR y utilizarlos con datos de distintas fuentes. Creemos que este proyecto tiene un gran potencial y también hay algunas ideas que pueden analizarse: * Reutilizar/ampliar las transformaciones DTL en otras bases de datos FHIR para modelos de ML personalizados * Utilizar las transformaciones DTL para normalizar los mensajes FHIR y publicar los modelos ML como servicios * Crear un tipo de modelos + un repositorio con las reglas de las transformaciones para utilizarlos en cualquier conjunto de datos FHIR Si exploramos nuevas posibilidades de este proyecto, imaginemos datos de distintas fuentes. ![](/sites/default/files/inline/images/whatsapp_image_2021-07-21_at_09.55.32.jpeg)   Como se muestra en la imagen anterior, el recurso FHIR, que consume la API REST, se puede utilizar con un FHIRaaS. Y no solo es posible utilizar FHIRaaS en AWS, sino que además podemos aprovechar el nuevo servicio de [HealthShare Message Transformation Services](https://aws.amazon.com/marketplace/pp/prodview-q7ryewpz75cq2?sr=0-9&ref_=beagle&applicationId=AWSMPContessa), que automatiza la conversión de HL7v2 a FHIR® para ingresar datos en Amazon HealthLake, donde podrás extraer más valor para tus datos. Con estas sencillas demostraciones, creo que estos recursos pueden utilizarse muy bien en escenarios más grandes, permitiendo implementaciones más sencillas en entornos de producción verdaderamente novedosos, como el de [AWS Healthlake](https://aws.amazon.com/healthlake/). ¿Por qué no? 😃 
Artículo
Henrique Dias · 25 mayo, 2022

El explorador de ZPM (zpm-explorer)

¡Hola Comunidad! @José.Pereira y yo queremos presentaros ZPM Explorer, nuestra interfaz gráfica para explorar las excelentes aplicaciones que hay en InterSystems Package Manager. ## La idea La idea de un explorador de ZPM es facilitar a las personas encontrar lo que ZPM ofrece. Cada semana, cada día, una nueva app se une al mundo ZPM, así que... ¿por qué no ayudar a los desarrolladores y no-desarrolladores a aprovechar las ventajas de este increíble mundo?! ## La aplicación ZPM es sencillo y potente, así que intentamos trasladar esa sencillez a algo fácil de usar, ofreciendo una búsqueda potente, para hacer más fácil que nunca localizar una aplicación específica, descubrir nuevas apps, encontrar nuevas soluciones... con un simple clic. La página principal del Explorador de ZPM es una tabla de datos con la información ofrecida por el *endpoint* [https://pm.community.intersystems.com/packages/-/all](https://pm.community.intersystems.com/packages/-/all) Los campos son: - Name: el nombre de la aplicación - Descripción: descripción de lo que hace la aplicación - Repository: enlace al repositorio en Github - Version: versión actual de la aplicación dentro de Package Manager ![](https://raw.githubusercontent.com/diashenrique/zpm-explorer/master/images/zpmexplorer.png) ## Cómo instalar nuevas apps El uso de la app es bastante sencillo. 1. Busca la aplicación que necesitas 2. Selecciona la aplicación 3. Haz clic en el botón Install 4. Confirma 5. Hecho 6. Empieza a hacer un buen uso de ella ## Cómo gestionar las aplicaciones instaladas ![](https://raw.githubusercontent.com/diashenrique/zpm-explorer/master/images/installedApps.png) ZPM Explorer ofrece una página para gestionar las apps existentes instaladas a través de ZPM. Puedes actualizar, eliminar e incluso usar un asistente para que sea más sencillo crear tu propio module.xml, especialmente la etiqueta Dependencies. ![](https://raw.githubusercontent.com/diashenrique/zpm-explorer/master/images/export.png) ## Demo [ZPM Explorer Demo](https://www.youtube.com/watch?v=24nlYIH83iY) .
Artículo
Alberto Fuentes · 11 abr, 2025

Configurar una conexión de tabla vinculada ODBC/JDBC a MySQL desde Iris

Debido a que la interpretación de SCHEMA por parte de MySQL difiere de la comprensión interpretación común en SQL (como se ve en IRIS, SQL Server u Oracle), nuestro asistente automático de tablas vinculadas puede encontrar errores al intentar recuperar la información de metadatos para construir la tabla vinculada. (Esto también se aplica a procedimientos y vistas vinculadas) Al intentar crear una tabla vinculada mediante el asistente, os encontraréis con un error que se parece a esto: ERROR #5535: SQL Gateway catalog table error in 'SQLPrimaryKeys'. Error: ' SQLState: (HY000) NativeError: [0] Message: [MySQL][ODBC 8.3(a) Driver][mysqld-5.5.5-10.4.18-MariaDB]Support for schemas is disabled by NO_SCHEMA option Para crear una tabla vinculada a una base de datos MySQL que emplea una estructura “sin esquema” (comportamiento predeterminado), seguid las instrucciones que aparecen a continuación: Cread una conexión SQL Gateway: Configurad la conexión SQL Gateway como de costumbre. Aseguraos de que la casilla "No usar identificadores delimitados por defecto" esté marcada. Haced clic en "Probar conexión" para confirmar que la conexión es exitosa. Usad la API basada en Terminal para crear la tabla vinculada: Utilizad la siguiente API: $SYSTEM.SQL.Schema.CreateLinkedTable() El método CreateLinkedTable() utiliza los siguientes parámetros: CreateLinkedTable(dsn As %String, externalSchema As %String, externalTable As %String, primaryKeys As %String, localClass As %String = "User.LinkedClass", localTable As %String, ByRef columnMap As %String = "") Ejemplo: En este ejemplo, usamos la tabla del sistema de MySQL help_keyword con name como clave primaria USER>do $SYSTEM.SQL.Schema.CreateLinkedTable("MyDSN", "", "help_keyword", "name", "User.LinkedClass", "LocalTable") Aseguraos de que todos los parámetros estén especificados correctamente para evitar cualquier error durante el proceso de configuración. En cualquier caso, ¿estáis utilizando actualmente tablas vinculadas? os recomiendo que echéis un vistazo su evolución en InterSystems IRIS, las Foreign Tables.
Artículo
Ricardo Paiva · 14 nov, 2019

Despliegue de aplicaciones con %Installer

¡Hola Comunidad! Suponga que desarrolló su propia aplicación con la tecnología de InterSystems y ahora quiere realizar varias implementaciones en sus distintos clientes. Durante el proceso de desarrollo usted escribió una guía de instalación detallada para aplicarla, ya que no solo necesita importar clases, también configurar el entorno de acuerdo a sus necesidades. Para atender esta tarea específica, InterSystems creó una herramienta especial llamada %Installer. Siga con la lectura para saber cómo utilizarla. %Installer Con esta herramienta, podrá definir el manifiesto de instalación, que describe la configuración de Caché que se desea, en lugar de los pasos para su instalación. Lo único que debe hacer es describir lo que quiere, y Caché generará automáticamente el código necesario para modificar el entorno por usted. Por lo tanto, solo debe distribuir el manifiesto en sí, mientras que todo el código de instalación se generará para el servidor específico de Caché en el momento de la compilación. Para definir un manifiesto, cree un nuevo bloque XData con una descripción detallada de la configuración de destino e implemente un método para generar el código en Caché ObjectScript para este bloque XData (este código siempre es el mismo). Una vez que el manifiesto esté listo, puede acceder a él desde la consola/terminal o bien desde código Caché ObjectScript, o automáticamente durante la instalación de Caché. El manifiesto debe ejecutarse en el namespace %SYS. Los manifiestos pueden manejar tanto parámetros del sistema (superport, OS, directorio mgr, etc.) como parámetros arbitrarios proporcionados por el usuario. En general, cada clase de instalación debe cumplir los siguientes requisitos: Contener un enlace para %occInclude.inc Contener un bloque XData con la configuración del servidor de Caché El bloque puede tener cualquier nombre que sea válido Agregar [XMLNamespace = INSTALLER] después del nombre del bloque, si es necesario consulte las indicaciones de Studio Llamar al elemento raíz (solo debe tener uno) <Manifest> que incluye a todos los demás elementos También debe implementar el método setup(), el cual generará el código que necesite el programa para el bloque XData. Conocimientos básicos sobre el instalador Puede ejecutar un manifiesto de instalación de varias formas: En el namespace %SYS desde la consola/terminal o desde código en Caché ObjectScript do ##class(MyPackage.MyInstaller).setup() Se realiza automáticamente durante la instalación de Caché. Para ello, exporte la clase del instalador en DefaultInstallerClass.xml que está almacenada en la carpeta con el paquete de instalación de Caché (por ejemplo, donde se almacenan setup_cache.exe o cinstall). Durante la instalación de Caché, esta clase se importará al namespace %SYS y se ejecutará mediante el método setup(). Ejemplo Consideremos un ejemplo sencillo. Establezca la clase App.Installer que contiene un instalador, el cual generará un nuevo namespace con el nombre definido por el usuario, después creará la aplicación web predeterminada e importará el código a este nuevo namespace: Include %occInclude Class App.Installer { /// You can see generated method in zsetup+1^App.Installer.1 XData Install [ XMLNamespace = INSTALLER ] { <Manifest> <If Condition='(##class(Config.Namespaces).Exists("${Namespace}")=0)'> <Log Text="Creating namespace ${Namespace}" Level="0"/> <Namespace Name="${Namespace}" Create="yes" Code="${Namespace}" Ensemble="0" Data="${Namespace}"> <Configuration> <Database Name="${Namespace}" Dir="${MGRDIR}${Namespace}" Create="yes"/> </Configuration> </Namespace> <Log Text="End Creating namespace ${Namespace}" Level="0"/> </If> <Role Name="AppRole" Description="Role to access and use the App" Resources="%DB_CACHESYS:RW,%Admin_Secure:U" /> <Namespace Name="${Namespace}" Create="no"> <CSPApplication Url="/csp/${Namespace}" Directory="${CSPDIR}${Namespace}" AuthenticationMethods="64" IsNamespaceDefault="true" Grant="AppRole" /> <IfDef Var="SourceDir"> <Log Text="SourceDir defined - offline install from ${SourceDir}" Level="0"/> <Import File="${SourceDir}"/> </IfDef> </Namespace> </Manifest> } ///Entry point method, you need to call /// At class compile time it generate Caché ObjectScript code from the manifest /// After that you can run this installer from a terminal: /// Set pVars("Namespace")="NewNamespace" /// Set pVars("SourceDir")="C:\temp\distr\" /// Do ##class(App.Installer).setup(.pVars) ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 0, pInstaller As %Installer.Installer) As %Status [ CodeMode = objectgenerator, Internal ] { Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "Install") } } En este ejemplo, el instalador realiza las siguientes acciones: Comprueba si existe un namespace con el mismo nombre que el valor de la variable Namespace (para que quede claro, especificaremos que la variable Namespace se estableció en NewNamespace) Si no es así, entonces registrará la creación de un nuevo namespace llamado NewNamespace Definir un nuevo namespace: El nombre es NewNamespace Crea un nuevo namespace La base de datos para las rutinas es NewNamespace No activa Ensemble La base de datos para globales es NewNamespace Crea una nueva base de datos Su nombre es NewNamespace; La crea en la carpeta mgr/NewNamespace (tenga en cuenta que la variable MGRDIR está disponible de forma predeterminada) La creación del namespace está completa y registrada Crea una nueva función: AppRole (con los recursos %DB_CACHESYS:RW y %Admin_Secure:U) Crea una nueva aplicación web predeterminada /csp/NewNamespace (también asigna AppRole de forma automática) Si la variable SourceDir está definida, entonces importa todos los archivos desde allí a NewNamespace Para que este instalador se inicie en un terminal, ejecute los siguientes comandos: Set pVars("Namespace")="NewNamespace" Set pVars("SourceDir")="C:\temp\distr\" Do ##class(App.Installer).setup(.pVars) Durante la ejecución el terminal muestra información importante: 2016-02-17 19:26:17 0 App.Installer: Installation starting at 2016-02-17 19:26:17, LogLevel=0 2016-02-17 19:26:17 0 : Creating namespace NewNamespace 2016-02-17 19:26:17 0 : End Creating namespace NewNamespace 2016-02-17 19:26:17 0 : SourceDir defined - offline install from C:\temp\distr\ 2016-02-17 19:26:18 0 App.Installer: Installation succeeded at 2016-02-17 19:26:18 2016-02-17 19:26:18 0 %Installer: Elapsed time .545148s Para recibir aún más información sobre lo que está sucediendo, especifique LogLevel (de 0 (default) a 3 (raw); más alto = más información). Do ##class(App.Installer).setup(.pVars, 3) Ahora hablaremos sobre las cosas que pueden hacerse en el manifiesto de instalación. Nodos disponibles Un manifiesto se compone de los siguientes elementos: Nodo Nodo padre Atributos (valores predeterminados) Descripción Arg Invoke, Error Value – es el valor de un argumento Pasa un argumento hacia un método que se llama mediante Invoke o Error ClassMapping Configuration Package - un paquete que debe mapearse From - nombre de la base de datos de origen que se utilizó para el mapeo Crea un mapeo de clases desde una base de datos hacia el namespace que contiene el elemento Configuration Compile Namespace Class - son los nombres de las clases para compilación Flags - son marcas de compilación (ck) IgnoreErrors - se utiliza para ignorar errores (0) Son la clases de los compiladores. Llama a $System.OBJ.Compile(Class, Flags) Configuration Namespace Se utiliza para crear namespaces y bases de datos. Cierra las etiquetas que activan los mapeos y actualiza el archivo CPF CopyClass Namespace Src - es la clase de origen Target - es la clase de destino Replace - elimina la clase de origen (0) Copia o desplaza la definición de la clase de origen a la de destino CopyDir Manifest Src - es el directorio fuente Target - es el directorio de destino IgnoreErrors - se utiliza para ignorar errores (0) Copia un directorio CopyFile Manifest Src - es el archivo de origen Target - es el archivo de destino IgnoreErrors - se utiliza para ignorar errores (0) Copia un archivo Credential Production Name - es el nombre de las credenciales de acceso Username - es el nombre de usuario Password - es la contraseña de usuario Overwrite - sobrescribe si la cuenta ya existe Crea o sobreescribe las credenciales de acceso CSPApplication Namespace AuthenticationMethods - activa los métodos de autenticación AutoCompile - realiza compilaciones automáticas (en la configuración CSP) CSPZENEnabled - la marca CSP/ZEN ChangePasswordPage - es la ruta para cambiar la contraseña de la página CookiePath - ruta hacia la sesión de cookies CustomErrorPage - ruta para personalizar la página de error DefaultSuperclass - es una superclase personalizada DefaultTimeout - se agotó el tiempo en espera de la sesión Description - realiza descripciones Directory - ruta hacia los archivos CSP EventClass - nombre de la clase del evento Grant - lista de funciones asignadas en el momento que el sistema actualiza su registro GroupById - realiza agrupaciones mediante las propiedades del Id InboundWebServicesEnabled - marca de los servicios web entrantes IsNamespaceDefault - es la marca de la aplicación para un Namespace predeterminado LockCSPName - Bloqueo de la marca del nombre CSP LoginClass - ruta para acceder a la página de inicio de sesión PackageName - nombre del paquete de propiedades PermittedClasses - clases de propiedades permitidas Recurse - indicador de recursión (sirve a los subdirectorios) (0) Resource - recurso requerido para acceder a la aplicación web ServeFiles - propiedad de los archivos de servicio ServeFilesTimeout - es el tiempo, en segundos, que le toma a Chaché almacenar los archivos estáticos. TwoFactorEnabled - es una autenticación de dos pasos Url - es el nombre de la aplicación web UseSessionCookie - utiliza las cookies para la sesión Crea o modifica una aplicación web. Para obtener más detalles, consulte la documentación y la clase Security.Applications Database Configuration BlockSize - tamaño del bloque en bytes de la base de datos ClusterMountMode - se encarga de organizar la base de datos como parte del cluster Collation - orden de la clasificación Create - si desea crear una nueva base de datos (yes/no/overwrite (yes)) Dir - es el directorio Encrypted - cifrado de la base de datos EncryptionKeyID - ID de la clave de cifrado ExpansionSize - tamaño en MB que puede expandirse InitialSize - tamaño inicial MaximumSize - tamaño máximo MountAtStartup - organización después del lanzamiento MountRequired - especifica que la base de datos DEBE organizarse con éxito en el momento del inicio Nombre - es el nombre de la base de datos PublicPermissions - son los permisos públicos Resource - es el recurso StreamLocation - es el directorio a donde se dirigen los flujos que están asociados a esta base de datos. Crea o modifica una base de datos. Para obtener más información, consulte la documentación y las clases Config.Databases y SYS.Database Default Manifest Name - es el nombre de la variable Value - es el valor de la variable Dir - valor de la variable (si es una ruta hacia una carpeta/archivo) Establece el valor de la variable (si aún no se estableció) Else Manifest, Namespace Puede ejecutarse cuando la sentencia if es falsa Error Manifest Status - código de error Source - origen del error Envía una excepción. Tenga en cuenta que la sintaxis para ${} y #{} no está disponible ForEach Manifest Index - es el nombre de la variable Values - una lista con los valores para la variable Collection-es un bucle controlado GlobalMapping Configuration Global - es el nombre del global From - es el nombre de la base de datos para el mapeo Collation - orden de la clasificación (Caché por defecto) Mapea un global If Manifest, Namespace Condition - es una sentencia condicional Sentencia condicional if IfDef Manifest, Namespace Var – nombre de la variable La sentencia condicional if se utiliza cuando la variable ya fue establecida IfNotDef Manifest, Namespace Var – nombre de la variable La sentencia condicional if se utiliza cuando la variable aún no fue establecida Import Namespace File - archivo/carpeta para importación Flags - marcas de compilación (ck) IgnorarErrores - se utiliza para ignorar errores (0) Recurse - importar recursivamente (0) Importa archivos. Llama a: $System.OBJ.ImportDir(File,,Flags,,Recurse) y $System.OBJ.Load(File, Flags) Invoke Namespace Class - nombre de la clase Method - nombre del método CheckStatus - comprueba el estado de la respuesta Return - escribe el resultado en una variable Hace una llamada a un método de una clase con varios argumentos y devuelve los resultados de la ejecución LoadPage Namespace Name: ruta a la página CSP Dir - es una carpeta con páginas CSP Flags - indicadores de compilación (ck) IgnoreErrors - se utiliza para ignorar errores (0) Carga archivos CSP mediante $System.CSP.LoadPage(Name, Flags) y $System.CSP.LoadPageDir(Dir, Flags) Log Manifest Level - nivel de registro desde 0 (mínimo) hasta 3 (detallado) Text - cadena con una longitud de hasta 32,000 caracteres Añade un mensaje al registro cuando el nivel de registro es mayor o igual al atributo "level" Manifest Elemento de la raíz. Es el único elemento de la raíz en un manifiesto, contiene todos los demás elementos Namespace Manifest Name - nombre del namespace Create - si se debe crear un nuevo namespace (yes/no/overwrite (yes)) Code - base da datos para el código del programa Data - base de datos Ensemble - activa Ensemble para el namespace Todos los demás atributos pueden utilizarse con las aplicaciones web de Ensemble Define el alcance del instalador Production Namespace Nombre - nombre de la producción AutoStart - lanzamiento automático de la producción Configura la producción en Ensemble Resource Manifest Name - nombre del recurso Description - descripción del recurso Permission - permisos públicos Crea o modifica un recurso. Role Manifest Name - nombre del role Description - descripción de la función (no debe contener comas) Resources - son los recursos asignados a la función, se representan como "MyResource:RW,MyResource1:RWU" RolesGranted - si se otorgan las funciones correspondientes Crea un nuevo role RoutineMapping Configuration Routines - nombre de la rutina Type - tipos de rutinas (MAC, INT, INC, OBJ o ALL) From - base de datos de origen Crea un nuevo mapeo para las rutinas Setting Production Item - elemento que puede configurarse Target - tipos de parámetros: Item, Host, Adapter Setting - nombre del parámetro Value - valor del parámetro Configura un elemento en la producción de Ensemble. Hace una llamada al método Ens.Production:ApplySettings SystemSetting Manifest Name - class.property del paquete Config Value - valor del atributo Establece los valores para los atributos del paquete Config (usando el método Modify) User Manifest Username - nombre de usuario PasswordVar - variable que contiene la contraseña Roles - lista de funciones del usuario Fullname - nombre completo Namespace - espacio de nombres de inicio Routine - rutina de inicio ExpirationDate - fecha después de la cual el usuario será desactivado ChangePassword - cambie la contraseña al iniciar sesión por última vez en el sistema Enabled - si el usuario está activo Crea o modifica un usuario. Var Manifest Name - nombre de la variable Value - valor de la variable Asigna un valor a la variable Variables Variables suministradas por el usuario Algunos atributos pueden contener expresiones (cadenas) que aumentan cuando el manifiesto se ejecuta. Existen tres tipos de expresiones que aumentarían y son parecidas a las siguientes: ${<Variable_name>} – es el valor de la variable (definida por el usuario o una variable de entorno, consulte más adelante) que se calcula durante la ejecución del manifiesto, ${#<Parameter_name>} – se sustituirá por el valor del parámetro especificado desde la clase del instalador durante la compilación, #{<Caché_ObjectScript_code>} — es el valor de la sentencia que se especificó en Caché ObjectScript y se calculará durante la ejecución del manifiesto. Asegúrese de poner comillas según sea necesario. Los valores de los parámetros se definen durante la compilación y, por lo tanto, pueden formar parte de una variable, o de una sentencia de Caché ObjectScript. Dado que las variables se interpretan antes que el código de Caché ObjectScript, puede utilizarlas en las sentencias de Caché ObjectScript, por ejemplo: #{$ZCVT("${NAMESPACE}","L")}. Variables del sistema Las siguientes variables siempre están disponibles: Variable Descripción Ejemplo del valor SourceDir (Disponible solo cuando se ejecuta el instalador) Directorio desde el que se ejecuta la instalación (setup_cache.exe o cinstall). /InterSystems/distr/ ISCUpgrade (Disponible solo cuando se ejecuta el instalador) Indica si se trata de una instalación nueva o de una actualización. Cuando esta variable es 0 se considera una instalación nueva, o 1 cuando es actualización. 0 (instalación) 1 (actualización) CFGDIR Consulte INSTALLDIR. /InterSystems/Cache/ CFGFILE Ruta al archivo CPF /InterSystems/Cache/cache.cpf CFGNAME Es el nombre de la instancia CACHE CPUCOUNT Número de núcleos en el CPU 4 CSPDIR Es el directorio CSP /InterSystems/Cache/csp/ HOSTNAME Es el nombre del servidor web SCHOOL15 HTTPPORT Es el puerto del servidor web 80 INSTALLDIR Es el directorio donde se instaló Caché /InterSystems/Cache/ MGRDIR Es el directorio de administración (mgr) /InterSystems/Cache/mgr/ PLATFORM Es el sistema operativo UNIX PORT Es el puerto del super servidor de Caché 1972 PROCESSOR Es el nombre de la plataforma x86-64 VERSION Es la versión de Caché 2015.1.1 Depuración de errores Algunas veces es difícil entender qué valores pueden asignarse como valores de atributos en los nodos. Para averiguarlo, compruebe el código int generado para el método de instalación. En la mayoría de los casos, la llamada principal se realiza a tInstaller.<ElementName> que es un objeto de la clase %Installer.Installer que, a su vez, hará llamadas directas a los métodos del sistema. Alternativamente, puede comprobar el código de %Installer.class<ElementName> en la que los atributos del nodo son propiedades de la clase. El código del programa se genera en los métodos %OnBeforeGenerateCode, %OnGenerateCode y %OnAfterGenerateCode. Con fines de depuración, recomiendo que coloque una llamada en una transacción dentro del instalador. Por ejemplo, puede utilizar los comandos TSTART/TROLLBACK para deshacer fácilmente todos los cambios realizados dentro de Caché (sin embargo, los cambios externos, como crear un archivo nuevo para la base de datos, no se revertirán). Por último, no olvide configurar LogLevel en 3. Ejemplos El proyecto MDX2JSON proporciona un instalador. Para instalar el proyecto, importe el archivo installer.xml que contiene la clase MDX2JSON.Installer en cualquiera de los siguientes formatos namespace. Puede realizar la importación desde SMP o arrastrando y soltando el archivo en Studio. Entonces ejecute el siguiente comando en un terminal: do ##class(MDX2JSON.Installer).setup() Como resultado, Caché cargará los archivos de la aplicación desde el repositorio GitHub y luego realizará la instalación en la base de datos predeterminada MDX2JSON namespace/MDX2JSON, mapeará el paquete MDX2SJON a %All y SAMPLES, mapeará el ^MDX2SJON global a %All y SAMPLES, creará la aplicación REST llamada /MDX2JSON, y así sucesivamente, verá todos estos pasos en el terminal. Para obtener información más detallada sobre el instalador de MDX2JSON, consulte el proyecto Léame. Ejemplos adicionales Ejemplo de los documentos de apoyo . La clase Sample.Installer en el namespace Samples. Los proyectos CacheGitHubCI proporcionan un instalador . El proyecto SYSMON que se encuentra en el panel de controles proporciona un instalador . El proyecto DeepSee Audit proporciona un instalador. Resumen %Installer es una herramienta conveniente para distribuir e implementar aplicaciones basadas en InterSystems Caché y Ensemble. Referencias Documentos de apoyo