Ethos Installation(3)-Install BEP and Messaging Service/Adapter
1. BEP
1. Set up Server TrustStore, copy from RabbitMQ we set up in the previous article. # mkdir /l01/app/oracle/rabbitmqcertificates # cp "emb1.pprd.odu.edu folder"/server/cert.pem -> /l01/app/oracle/rabbitmqcertificates # cp "emb1.pprd.odu.edu folder"/client/keycert.p12 -> /l01/app/oracle/rabbitmqcertificates Create a new truststore file rabbitstore # keytool -import -alias rabbitmqserver -file /l01/app/oracle/rabbitmqcertificates/cert.pem -keystore rabbitstore 2. Install through ESM, and then, configure the application # BannerEventPublisher_configuration.groovy /******************************************************************************* * * * Banner Event Publisher DataSource Configuration * * * *******************************************************************************/ dataSource_cdcadmin { //JNDI configuration for use in 'production' environment jndiName = "java:comp/env/jdbc/cdcadmin" transactional = false } dataSource_events { //JNDI configuration for use in 'production' environment jndiName = "java:comp/env/jdbc/events" transactional = false } dataSource_bannerSsbDataSource { //JNDI configuration for use in 'production' environment jndiName = "java:comp/env/jdbc/bannerSsbDataSource" transactional = false } dataSource_bannerDataSource { //JNDI configuration for use in 'production' environment jndiName = "java:comp/env/jdbc/bannerDataSource" transactional = false } bep { //App Server //Possible values are either "TOMCAT" or "WEBLOGIC" app.server = "TOMCAT" //Message Broker //Possible values are "RABBITMQ" or "WEBLOGIC" or "RABBITMQ/WEBLOGIC" message.broker = "RABBITMQ" //Retry interval to publish to broker in SECONDS publish.retry.interval = 45 } //RabbitMQ configuration rabbitmq { host = "emb1.pprd.odu.edu" port = "5671" userName = "ellucian" password = "password" virtualHostName = "bep_events_host" exchangeName = "bep_events_topic" enableSSL = "true" //Validate rabbit connections validate = true //Put an actual path to a file starting with "file:" otherwise leave the value as NO_FILE keyStoreFileName = "file:/l01/app/oracle/rabbitmqcertificates/keycert.p12" keyStorePassPhrase = "pass" //Put an actual path to a file starting with "file:" otherwise leave the value as NO_FILE trustStoreFileName = "file:/l01/app/oracle/rabbitmqcertificates/rabbitstore" trustStorePassPhrase = "pass" } jms { validate = true } // This configuration needs to be done in milliseconds for the footer to appear in the screen footerFadeAwayTime=2000 // Application Navigator opens embedded applications within an iframe. To protect against the clickjacking vulnerability, // integrating applications will have to set the X-Frame options to protect the "login/auth" URI from loading in the iframe and // set it to denied mode. This setting is needed if the application needs to work inside Application Navigator and // the secured application pages will be accessible as part of the single-sign on solution. grails.plugin.xframeoptions.urlPattern = '/login/auth' grails.plugin.xframeoptions.deny = true // End of configuration 3. Set up Tomcat # server.xml, configure database connection <GlobalNamingResources> <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" /> <Resource name="jdbc/bannerDataSource" auth="Container" type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver" url="jdbc:oracle:thin:@//bannerdb.pprd2.odu.edu:2336/PPRD2" username="banproxy" password="password" initialSize="5" maxTotal="600" maxIdle="-1" maxWaitMillis="30000" validationQuery="select 1 from dual" accessToUnderlyingConnectionAllowed = "true" removeAbandonedOnBorrow = "true" testOnBorrow="true"/> <Resource name="jdbc/bannerSsbDataSource" auth="Container" type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver" url="jdbc:oracle:thin:@//bannerdb.pprd2.odu.edu:2336/PPRD2" username="ban_ss_user" password="password" initialSize="5" maxTotal="600" maxIdle="-1" maxWaitMillis="30000" validationQuery="select 1 from dual" accessToUnderlyingConnectionAllowed = "true" removeAbandonedOnBorrow = "true" testOnBorrow="true"/> <Resource name="jdbc/events" auth="Container" type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver" url="jdbc:oracle:thin:@//database.domain.edu:2336/PPRD2" username="events" password="password" initialSize="5" maxTotal="600" maxIdle="-1" maxWaitMillis="30000" validationQuery="select 1 from dual" accessToUnderlyingConnectionAllowed = "true" removeAbandonedOnBorrow = "true" testOnBorrow="true"/> <Resource name="jdbc/cdcadmin" auth="Container" type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver" url="jdbc:oracle:thin:@//database.domain.edu:2336/PPRD2" username="cdcadmin" password="password" initialSize="5" maxTotal="600" maxIdle="-1" maxWaitMillis="30000" validationQuery="select 1 from dual" accessToUnderlyingConnectionAllowed = "true" removeAbandonedOnBorrow = "true" testOnBorrow="true"/> </GlobalNamingResources> # context.xml, configure datasource <ResourceLink global="jdbc/bannerDataSource" name="jdbc/bannerDataSource" type="javax.sql.DataSource"/> <ResourceLink global="jdbc/bannerSsbDataSource" name="jdbc/bannerSsbDataSource" type="javax.sql.DataSource"/> <ResourceLink global="jdbc/events" name="jdbc/events" type="javax.sql.DataSource"/> <ResourceLink global="jdbc/cdcadmin" name="jdbc/cdcadmin" type="javax.sql.DataSource"/> # The war file which deployed in Tomcat should be named as BannerEventPublisher.war 4. The user who needs to log into BEP should be granted: Grant object BEP_ADMIN_OBJECT / BAN_DEFAULT_M to the user who needs access through INB/GSASECR, then https://ellucian.force.com/clients/s/article/Logging-into-BEP-gives-you-You-are-not-Authorized-to-view-this-page-error SQL> GRANT EXECUTE ON GOKFGAC TO <BEP_USER>; SQL> GRANT EXECUTE ON GB_COMMON TO <BEP_USER>; SQL> GRANT SELECT ON TWGRMENU TO <BEP_USER>; SQL> GRANT SELECT ON TWGRWMRL TO <BEP_USER>; SQL> GRANT SELECT ON TWGRROLE TO <BEP_USER>;
2. Messaging Service
Configure ESM with the configuration of rabbitmq I created before. ./ConfigureEMS ellucian oracle123 bep_events_host
3. Messaging Adapter
Before configure Messaging Adapter, we need:
1. Create two applications in the Ethos cloud, one for student API and another for integration API. Add all resources into them. And get the API keys of them
2. One user in the local banner database, Ethos Cloud will use it to access banner to fetch database. Make sure you grant necessary permission to it.
$ ./ConfigureEMS.sh <?xml version="1.0" encoding="UTF-8"?> <emsConfig> <clientErpType>Banner</clientErpType> <!-- Colleague or Banner --> <configId>ETHOS-INTEGRATION</configId> <!-- Change this to match config name for Colleague (HUB is delivered default), set to ETHOS-INTEGRATION for Banner --> <amqpUsername>ellucian</amqpUsername> <!-- EMS (RabbitMQ) username --> <amqpPassword>oracle123</amqpPassword> <!-- EMS (RabbitMQ) password --> <colleagueApiConfig> <baseUrl>https://server:port/ColleagueApi/</baseUrl> <!-- Colleague Web Api Url --> <username>required</username> <!-- Colleague Web Api username --> <password>required</password> <!-- Colleague Web Api password --> <hubApiKey>required</hubApiKey> <!-- API key from your Ethos Integration application --> </colleagueApiConfig> <bannerStudentConfig> <baseUrl>https://apiserver:7005/studentapi/api</baseUrl> <!-- Banner Student Api Url --> <username>apiuser</username> <!-- Banner Student Api username --> <password>password</password> <!-- Banner Student Api password --> <apiKeys> <!-- Banner MEP clients add new apiKey sections for each VPDI code --> <apiKey> <hubApiKey>apikey</hubApiKey> <!-- API key from your Ethos Integration application --> <vpdiCode></vpdiCode> <!-- Leave blank if MEP is not used --> </apiKey> </apiKeys> <mepSharedDataApiKey></mepSharedDataApiKey> <!-- API key to use when publishing shared data in a MEP environment --> </bannerStudentConfig> <bannerIntegrationConfig> <baseUrl>https://apiserver:7005/intgrationapi/api</baseUrl> <!-- Banner Integration Api Url --> <username>apiuser</username> <!-- Banner Integration Api username --> <password>passwd</password> <!-- Banner Integration Api password --> <apiKeys> <!-- Banner MEP clients add new apiKey sections for each VPDI code --> <apiKey> <hubApiKey>apikey</hubApiKey> <!-- API key from your Ethos Integration application --> <vpdiCode></vpdiCode> <!-- Leave blank if MEP is not used --> </apiKey> </apiKeys> <mepSharedDataApiKey></mepSharedDataApiKey> <!-- API key to use when publishing shared data in a MEP environment --> </bannerIntegrationConfig> <!-- startup logging level...this will be overridden by the value returned from the api config endpoint --> <logLevel>INFO</logLevel> <!-- Number of messages to pull from RabbitMQ for parallel processing api calls --> <amqpBatchSize>80</amqpBatchSize> <!-- Parallel processing settings for making api calls --> <autoConfigurePool>true</autoConfigurePool> <!-- Pool grows as messages come in (max # of threads is limited by batch size). 60 sec idle threads will be evicted --> <processingThreads>4</processingThreads> <!-- Number of connection pool threads to use for parallel api calls. This is not used if 'autoConfigurePool' it true --> <!-- Api timeout retry settings --> <apiTimeoutInterval>300000</apiTimeoutInterval> <!-- value in milliseconds --> <maxRetryAttempts>1</maxRetryAttempts> <!-- Number of messages to publish to ethos integration at once...max of 20 --> <ethosPublishBatchSize>20</ethosPublishBatchSize> <!-- Banner database connection settings. This is only necessary for a Banner MEP environment --> <bannerConnectString>jdbc:oracle:thin:@database.domain.edu:2336:PPRD2</bannerConnectString> <jdbcDriver>oracle.jdbc.pool.OracleDataSource</jdbcDriver> </emsConfig> # set up a key used to encipher communication in the Tomcat's setenv.sh export EMA_CONFIG=oracle123 # The Adapter cannot be deployed to the same Tomcat with API
Ethos Installation(2)-Configure RabbitMQ for BEP
Summay:
1.BEP pushs the data changes which BEP captures from Banner to RabbitMQ.
2.Internally, Messaging Service is a wrapper of RabiitMQ.
3.Messaging Adapter will fetch data out from RabbitMQ and push it to Ethos Cloud.
1. Packages
rabbitmq-server-3.7.17-1.el6.noarch https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.17/rabbitmq-server-3.7.17-1.el7.noarch.rpm erlang-22.0.6-1.el6.x86_64 https://dl.bintray.com/rabbitmq-erlang/rpm/erlang/22/el/6/x86_64/:erlang-22.0.7-1.el6.x86_64.rpm
2. Generate SSL cert & key
Create Self-signing Authority and Root certificate # mkdir testca ; cd testca # mkdir certs; mkdir private; chmod 700 private # touch index.txt; echo 01 > serial # vi openssl.cnf [ ca ] default_ca = testca [ testca ] dir = . certificate = $dir/cacert.pem database = $dir/index.txt new_certs_dir = $dir/certs private_key = $dir/private/cakey.pem serial = $dir/serial default_crl_days = 7 default_days = 365 default_md = sha1 policy = testca_policy x509_extensions = certificate_extensions [ testca_policy ] commonName = supplied stateOrProvinceName = optional countryName = optional emailAddress = optional organizationName = optional organizationalUnitName = optional [ certificate_extensions ] basicConstraints = CA:false [ req ] default_bits = 2048 default_keyfile = ./private/cakey.pem default_md = sha1 prompt = yes distinguished_name = root_ca_distinguished_name x509_extensions = root_ca_extensions [ root_ca_distinguished_name ] commonName = hostname [ root_ca_extensions ] basicConstraints = CA:true keyUsage = keyCertSign, cRLSign [ client_ca_extensions ] basicConstraints = CA:false keyUsage = digitalSignature extendedKeyUsage = 1.3.6.1.5.5.7.3.2 [ server_ca_extensions ] basicConstraints = CA:false keyUsage = keyEncipherment extendedKeyUsage = 1.3.6.1.5.5.7.3.1 Create the Root Certificate for the self-signing authority # openssl req -x509 -config openssl.cnf -newkey rsa:2048 -days 3650 -out cacert.pem -outform PEM -subj /CN=MyTestCA/ -nodes Convert the public key to the DER format # openssl x509 -in cacert.pem -out cacert.cer -outform DER The files of root certificate: cacert.pem / cacert.cer Generate RabbitMQ Server certificate # mkdir server; cd server Generate a key # openssl genrsa -out key.pem 2048 Generate a request # openssl req -new -key key.pem -out req.pem -outform PEM -subj /CN=emb1.pprd.odu.edu/O=server/ -nodes CA sign the server certificate # cd .. # openssl ca -config openssl.cnf -in server/req.pem -out server/cert.pem -notext -batch -extensions server_ca_extensions Generate the keystore # cd server # openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:oreol410g <- keyStorePassPhrase. Generate RabbitMQ Client certificate # mkdir client; # cd client Generate a key # openssl genrsa -out key.pem 2048 Generate a request # openssl req -new -key key.pem -out req.pem -outform PEM -subj /CN=bep.pprd.odu.edu/O=client/ -nodes CA sign the client certificate # cd .. # openssl ca -config openssl.cnf -in client/req.pem -out client/cert.pem -notext -batch -extensions client_ca_extensions Generate the keystore # cd client # openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:oreol410g <- keyStorePassPhrase.
3. Configure
$ cat /etc/rabbitmq/rabbitmq.config|grep -v -E %%|grep -v -E "^$" [ {rabbit, {ssl_listeners, [5671]}, {ssl_options, [{cacertfile, "/l01/app/ca_certs/testca/cacert.pem"}, {certfile, "/l01/app/ca_certs/testca/server/cert.pem"}, {keyfile, "/l01/app/ca_certs/testca/server/key.pem"}, {verify, verify_peer}, {fail_if_no_peer_cert, false}, {versions, ['tlsv1.2', 'tlsv1.1', tlsv1]}, {ciphers, ["ECDHE-ECDSA-AES256-GCM-SHA384","ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-ECDSA-AES256-SHA384","ECDHE-RSA-AES256-SHA384", "ECDHE-ECDSA-DES-CBC3-SHA", "ECDH-ECDSA-AES256-GCM-SHA384","ECDH-RSA-AES256-GCM-SHA384","ECDH-ECDSA-AES256-SHA384", "ECDH-RSA-AES256-SHA384","DHE-DSS-AES256-GCM-SHA384","DHE-DSS-AES256-SHA256", "AES256-GCM-SHA384","AES256-SHA256","ECDHE-ECDSA-AES128-GCM-SHA256", "ECDHE-RSA-AES128-GCM-SHA256","ECDHE-ECDSA-AES128-SHA256","ECDHE-RSA-AES128-SHA256", "ECDH-ECDSA-AES128-GCM-SHA256","ECDH-RSA-AES128-GCM-SHA256","ECDH-ECDSA-AES128-SHA256", "ECDH-RSA-AES128-SHA256","DHE-DSS-AES128-GCM-SHA256","DHE-DSS-AES128-SHA256", "AES128-GCM-SHA256","AES128-SHA256","ECDHE-ECDSA-AES256-SHA", "ECDHE-RSA-AES256-SHA","DHE-DSS-AES256-SHA","ECDH-ECDSA-AES256-SHA", "ECDH-RSA-AES256-SHA","AES256-SHA","ECDHE-ECDSA-AES128-SHA", "ECDHE-RSA-AES128-SHA","DHE-DSS-AES128-SHA","ECDH-ECDSA-AES128-SHA", "ECDH-RSA-AES128-SHA","AES128-SHA"]}, {honor_cipher_order, true} ]} ]}, {kernel, ]}, {rabbitmq_management, ]}, {rabbitmq_management_agent, ]}, {rabbitmq_shovel, [{shovels, ]} ]}, {rabbitmq_stomp, ]}, {rabbitmq_mqtt, ]}, {rabbitmq_amqp1_0, ]}, {rabbitmq_auth_backend_ldap, ]} ].
4. Define resources
# rabbitmqctl add_user ellucian oracle123 # rabbitmqctl add_vhost bep_events_host # rabbitmqctl set_permissions -p bep_events_host ellucian ".*" ".*" ".*" # rabbitmqctl authenticate_user ellucian oracle123 # rabbitmqctl set_user_tags ellucian administrator
Ethos Installation(1)-Install API
1. Structure
Ethos has two data flows:
1. red line, the third party requests data through Ethos from an institution
2. orange line, the Banner push the modifications to the subscribers.
The following components need to be installed if you only use the first one
1. Ethos API DB Upgrade
2. Ethos API Management Center - emb1
3. Banner Student API - emb1
4. Banner Integration API - emb1
If you plan to use subscription, you need to install
1. BEP - bep
2. Ellucian Messaging Service - emb1
3. Ellucian Messaging Adapter - emb1
2. compatibility
We should go to
https://ecommunities.ellucian.com/thread/60814
to check the compatibility matrix at first to plan our installation.
3. Prepare
The password of the following users
1. apiuser – Used by Ethos cloud to access the local banner database.
2. odsstg
3. banproxy
The following user will be created for Ethos
1. cdcadmin
2. events
3. banguidgen
The rabbitmq is required when you install BEP and Ellucian Messaging Service.
I will give you a guide about the install and configure BEP in another article.
4. Ethos API DB Upgrade
Using ESM to install. No application componment
From Ethos 9.9, the GUID generation which is very time consuming was splited from the DB install.
5. Ethos API Management Center
Using ESM to install.
Configuration:
- encipher banproxy passowrd
https://ellucian.force.com/clients/s/article/Ethos-API-Management-Center-login-denied
bash-4.1$ bash ./encrypt.sh input=<banproxy password> password=<encipher password> algorithm=PBEWithMD5AndDES ----ENVIRONMENT----------------- Runtime: Sun Microsystems Inc. OpenJDK 64-Bit Server VM 23.41-b41 ----ARGUMENTS------------------- algorithm: PBEWithMD5AndDES input: <banproxy password> password: <encipher key> ----OUTPUT---------------------- A93QdvdVv5pufChfukAEoor66iZ4FBJJQmJ/7KF+oKtHdQxNUHilZA==
The output here is encrypted password strings.
- application.properties
# ******************************************************************************* # Copyright 2018 Ellucian Company L.P. and its affiliates. # ******************************************************************************* spring.datasource.url=jdbc:oracle:thin:@bannerdb.pprd2.odu.edu:2336:PPRD2 spring.datasource.password=ENC(A93QdvdVv5pufChfukAEoor66iZ4FBJJQmJ/7KF+oKtHdQxNUHilZA==) <- banproxy password app.oracle.useRadiusAuthentication=false
- Tomcat - setenv.sh
export JAVA_OPTS="-server -Xms3g -Xmx5g -XX:MaxPermSize=1024m -Doracle.jdbc.autoCommitSpecCompliant=false -Dlog4j.configuration=config.properties -Djasypt.encryptor.password=<encipher key>" <- The key which used to encipher
- The users you want to use to log into Ethos Management Center, you have to grant BAN_EEAMC_C / BAN_GENERAL_C to them.
6. Banner Student / Integration API
Just use ESM to install
Until now, we have done the installation of Ethos, only for data requesst.
You can go to Ethos Cloud to configuration applications and use postman for testing.
There is a example: https://ecommunities.ellucian.com/docs/DOC-11172
Invoke Banner BDM RESTful API
We are using Banner BDM RESTful API to upload file from the local desktop to the BDM as batch.
The key point here is that we should use form-data with multipe parts as body to post data.
In python, I found requests_toolbelt.MultipartEncoder can achieve this goal.
And there are two API:
- axbatches is used to create a batch, and return the id of the batch.
- axbatchpages, with batch id to add pages to it
import json import os import datetime import requests_toolbelt import requests class AppXtenderReST: site = "https://Server/AppXtenderReST/api/axdatasources/PPRD2" def __init__(self, auth, appid): self.auth = auth self.appid = appid def createHeader(self, contentType): headers = { 'Accept': 'application/vnd.emc.ax+json', 'Authorization': 'Basic ' + self.auth, 'Host': 'webxt.pprd.odu.edu', 'Content-Type': contentType } return headers def getFName(self, filepath): head, tail = os.path.split(filepath) return tail; def axbatches(self, name, filepath): url = self.site + "/" + "axbatches" + "/" + str(self.appid) payload = {'Name': name , 'Description': 'created by Python Automation Script for App ' + str(self.appid) + ' on '+ datetime.datetime.now().strftime('%Y-%m-%d'), 'Private': False } m = requests_toolbelt.MultipartEncoder( fields={'data': (None, json.dumps(payload), 'application/vnd.emc.ax+json; charset=utf-8'), 'bin': (self.getFName(filepath), open(filepath, 'rb'), 'application/bin')} # 'annotation' : () # 'text' : () ) response = requests.post(url, headers=self.createHeader(m.content_type), data=m) info = response.json() if response.status_code == 200: return info['ID'], info['PageCount'] else: raise Exception(response.status_code, info) def axbatchpages(self, batchid, filepath): url = self.site + "/axapps/" + str(self.appid) + '/' + "axbatchpages" + "/" + str(batchid) m = requests_toolbelt.MultipartEncoder( fields={'bin': (self.getFName(filepath), open(filepath, 'rb'), 'application/bin')} ) response = requests.post(url, headers=self.createHeader(m.content_type), data=m) info = response.json() if response.status_code == 200: return info['Page'] else: raise Exception(response.status_code, info) if __name__ == "__main__": # generate key by using b64encode # from base64 import b64encode # auth = b64encode(b"user:password").decode("ascii") rest = AppXtenderReST("key", 509) # replace appid here rest.upload('batch_test_5', 'C:/Users/q1zhang/Downloads/test')
Invoke Banner BDM ApplicationExtender Service API
Due to the situation of Conorvius, all stuff in my university works from home.
In order to forward fax to BDM directly, we asked DSG to convert fax to PDF to a shared folder at first.
And then, I am writing a python code to invoke ApplicationExtender API to index PDFs to a new created app.
I have done the code for the Login, Logout, CreateNewDocument, unlockDocumentByRef, CreateBatchPage, and UploadImageStream.
But unfortunately, CreateBatchPage and UploadImageStream are not working.
The document of ApplicationExtender is ridiculous, full of the error.
And there is no way for debuging.
We are using RESTful API to upload file as batch. In stead of going on the research of CreateBatchPage and UploadImageStream to save the time.
The code will iterator files in one folder, then, call CreateNewDocument to index each PDF with two field: ID and Time.
import xml.dom.minidom from xml.dom.minidom import parse, parseString import requests import base64 import os import datetime import sys import smtplib user = 'user' password = 'password' datasource = 'datasource' class AxServices: # constant variable url = 'https://xxx.odu.edu/AppXtenderServices/axservicesinterface.asmx' xsi = 'http://www.w3.org/2001/XMLSchema-instance' xsd = 'http://www.w3.org/2001/XMLSchema' ax = 'http://www.emc.com/ax' true = "true" false = "false" buffer_size = 8192 # classes class AxObject: def __init__(self, tag, attr): self.doc = xml.dom.minidom.Document() self.data = self.doc.createElement('ax:' + tag) self.data.setAttribute('xmlns:xsi', AxServices.xsi) self.data.setAttribute('xmlns:xsd', AxServices.xsd) for key in attr: self.data.setAttribute(key, attr[key]) self.data.setAttribute('xmlns:ax', AxServices.ax) self.doc.appendChild(self.data) def getData(self): return self.data def toXML(self): return self.doc.toxml('utf-8').decode() class AxDocCrtData(AxObject): def __init__(self, appid, dsn, filepath): attr = {} attr['dsn'] = dsn attr['appid'] = appid attr['filepath'] = filepath attr['filetype'] = 'FT_PDF' # AxTypes.FileType # FT_UNKNOWN = 0 / FT_Text = 1 / FT_CompressedText = 2 # FT_ForeignFile = 3 / FT_OLE = 4 / FT_RTF = 5 # FT_HTML = 6 / FT_PDF = 7 / FT_IMAGE = 8 / FT_Annotation = 255 attr['ignore_dls'] = AxServices.true # Indicates whether to ignore document-level security checking while saving index values attr['ignore_dup_index'] = AxServices.true # Indicates whether to ignore duplicated indexes while saving index values attr['splitimg'] = AxServices.true # Indicates whether to split multi-page image files such as PDF, TIFF, and text. attr['subpages'] = '0' # Number of sub-pages in the image file super().__init__('AxDocCrtData', attr) class QueryItem(AxObject): def __init__(self): attr = {} attr['id'] = '-1' super().__init__('QueryItem', attr) data = super().getData() self.attributes = self.doc.createElement('ax:Attributes') self.fields = self.doc.createElement('ax:Fields') data.appendChild(self.attributes) data.appendChild(self.fields) def addAttribute(self): print('addAttribute') def addField(self, id, val, nul): field = self.doc.createElement('ax:Field') field.setAttribute('id', str(id)) field.setAttribute('value', str(val)) field.setAttribute('isnull', str(nul)) self.fields.appendChild(field) class AxPageUploadData(AxObject): def __init__(self, filepath): attr = {} attr['act'] = 'InsertAfter' attr['filepath'] = filepath attr['filetype'] = 'FT_PDF' attr['position'] = '1' attr['splitimg'] = AxServices.true attr['subpages'] = '0' super().__init__('AxImgUploadData', attr) class AxStreamData(AxObject): def __init__(self, key, fname, data, startpos): print(startpos) self.attr = {} self.attr['encryption'] = AxServices.false self.attr['key'] = key self.attr['origFile'] = fname self.attr['startbyte'] = str(startpos) self.attr['len'] = str(AxServices.buffer_size) super().__init__('AxStreamData', self.attr) sdata = super().getData(); sdata.setAttribute('ImageBytes', str(base64.b64encode(data))) class Request: header = {} action_dict = {'Login': 'Login', 'CreateNewDocument': 'CreateNewDocument', 'CreateBatchPage': 'CreateBatchPage', 'UploadImageStream': 'UploadImageStream', 'Logout':'Logout', 'UnlockDocumentByRef':'UnlockDocumentByRef' } def __init__(self, data): self.header['Content-Type'] = 'text/xml; charset=utf-8' self.header['Host'] = 'xxxx.odu.edu' self.header['SOAPAction'] = "http://documentum.com/AX/WebServices" + '/' + self.action_dict[type(self).__name__] self.doc='' self.response='' self.setBody(data) def setBody(self, data): self.doc = xml.dom.minidom.Document() envelope = self.doc.createElement('soap:Envelope') envelope.setAttribute('xmlns:soap', 'http://schemas.xmlsoap.org/soap/envelope/') envelope.setAttribute('xmlns:xsi', AxServices.xsi) envelope.setAttribute('xmlns:xsd', AxServices.xsd) self.doc.appendChild(envelope) body = self.doc.createElement('soap:Body') envelope.appendChild(body) action = self.doc.createElement(self.action_dict[type(self).__name__]) action.setAttribute('xmlns', 'http://documentum.com/AX/WebServices') body.appendChild(action) for key in data: ele = self.doc.createElement(key) ele.appendChild(self.doc.createTextNode(str(self.data[key]))) action.appendChild(ele) return body def toXML(self): return self.doc.toxml('utf-8').decode() def toPXML(self): return self.doc.toprettyxml(' ') def getBody(self): return self.toXML() def post(self): self.response = requests.post(AxServices.url, headers=self.header, data=self.getBody()) return self.response.status_code def getResponse(self): return self.response; def getResponseText(self): self.response.encoding = 'utf-8' return self.response.text; def getResponseBody(self): xml = self.getResponseText(); dom = parseString(xml) return dom.getElementsByTagName("soap:Envelope")[0].getElementsByTagName("soap:Body")[0] def getResponseResult(self): return self.getResponseBody().firstChild.firstChild.firstChild.nodeValue def getEleByTagFromResponse(self, tag): retxml = self.getResponseResult(); return xml.dom.minidom.parseString(retxml).getElementsByTagName(tag) class Login(Request): def __init__(self, ds, user, password): self.data = {'sessionTicket': '', 'dataSource': ds, 'userId': user, 'password': password, 'features': 1} super().__init__(self.data) def getSessionTicket(self): return super().getResponseResult() class Logout(Request): def __init__(self, ticket): self.data = {'sessionTicket': ticket} super().__init__(self.data) class CreateNewDocument(Request): def __init__(self, ticket, data, idx): self.data = {'sessionTicket': ticket, 'xmlAxDocumentCreationData': str(data), 'xmlDocIndex': str(idx)} super().__init__(self.data) def getDoc(self): return super().getEleByTagFromResponse('ax:Doc')[0] def getDocID(self): return self.getDoc().getAttribute('id') def getRef(self): return self.getDoc().getAttribute('objref') class UnlockDocumentByRef(Request): def __init__(self, ticket, ref): self.data = {'sessionTicket': ticket, 'docReference': ref} super().__init__(self.data) class CreateBatchPage(Request): def __init__(self, ticket, ds, appid, batchname, data): self.data = {'sessionTicket': ticket, 'dataSource': ds, 'appid': appid, 'batchname': batchname, 'xmlPageUploadData': str(data)} super().__init__(self.data) class UploadImageStream(Request): def __init__(self, ticket, ds, data): self.data = {'sessionTicket': ticket, 'dataSource': ds, 'xmlAxStreamData': str(data)} super().__init__(self.data) def getUploadKey(self): body = super().getResponseBody() return body.firstChild.firstChild.firstChild.nodeValue # functions def __init__(self): self.sessionTicket = '' self.dataSource = '' def login(self, ds, user, password): obj = self.Login(ds, user, password) ret = obj.post() if ret == 200: print("login successfully") self.sessionTicket = obj.getSessionTicket() self.dataSource = ds else: raise Exception(ret, obj.getResponseText()) return self.sessionTicket def logout(self): obj = self.Logout(self.sessionTicket) ret = obj.post() if ret != 200: raise Exception(ret, obj.getResponseText()) def getSessionTicket(self): return self.sessionTicket; def getDataSource(self): return self.dataSource; def createnewdocument(self, data, idx): obj = self.CreateNewDocument(self.sessionTicket, data.toXML(), idx.toXML()) ret = obj.post() if ret == 200: print('success, doc id: ' + obj.getDocID() + ' ref: ' + obj.getRef()) return obj.getRef() else: raise Exception(ret, obj.getResponseText()) def unlockdocumentbyref(self, ref): obj = self.UnlockDocumentByRef(self.sessionTicket, ref) ret = obj.post() if ret != 200: raise Exception(ret, obj.getResponseText()) def createbatchpage(self, appid, batchname, data): obj = self.CreateBatchPage(self.sessionTicket, self.dataSource, appid, batchname, data.toXML()) ret = obj.post() if ret == 200: print('success') else: raise Exception(ret, obj.getResponseText()) def uploadimagestream(self, data): obj = self.UploadImageStream(self.sessionTicket, self.dataSource, data.toXML()) ret = obj.post() if ret == 200: return obj.getUploadKey() else: raise Exception(ret, obj.getResponseText()) def uploadfile(self, filename): head, tail = os.path.split(filename) file = open(filename, 'rb', self.buffer_size) idx = 0 key = '' while True: print(key) data = file.read(self.buffer_size) if not data: break sdata = srv.AxStreamData(key, tail, data, idx * self.buffer_size) key = srv.uploadimagestream(sdata) idx = idx+1 return key def indexDocByFolder(self, appid, dir, tpath, fpath): failed = 0 first = True; seq = 0; for file in os.listdir(dir): seq = seq + 1 filePath = str(dir)+'/' + str(file) tgtPath = str(tpath) + '/' + str(file) faildPath = str(fpath) + '/' + str(file) if file.upper().endswith('.PDF'): # t = datetime.datetime.now() t = datetime.datetime.fromtimestamp(os.path.getmtime(filePath)) id = str(appid) + '_' + str(file).replace(' ', '_').replace('.','_').replace('(','').replace(')','') + '_' + str(t.strftime('%Y%m%d%H%M%S')) + '_' + str(seq) print("[START] "+ filePath + "," + id) data = srv.AxDocCrtData(str(appid), self.dataSource, filePath) idx = srv.QueryItem() idx.addField(1, id, AxServices.false) #idx.addField(2, t.strftime('%Y-%m-%d %H:%M:%S'), AxServices.false) idx.addField(2, t.strftime('%m-%d-%Y'), AxServices.false) try: ref = srv.createnewdocument(data, idx) srv.unlockdocumentbyref(ref) os.rename(filePath, tgtPath) except Exception as e: os.rename(os.rename(filePath, failedPath)) failed = failed + 1 print("[END] " + filePath) print(str(seq) + " files processed, " + str(failed) + " failed.") return seq, failed if __name__ == "__main__": if len(sys.argv) != 5: print('Four Args, appid, source path, processed path, and failed path') exit(-1) appid = sys.argv[1] spath = sys.argv[2] tpath = sys.argv[3] fpath = sys.argv[4] failed = 0 try: srv = AxServices() ticket = srv.login(datasource, user, password) processed, failed = srv.indexDocByFolder(appid, spath, tpath, fpath) srv.logout() if failed > 0: failed = -1 raise Exception('400','login failed') except Exception as e: smtp = smtplib.SMTP('smtp.odu.edu') smtp.sendmail('dba-dist@odu.edu','dba-dist@odu.edu','[BDM Automation]: '+ str(failed) + ' failed.') # CreateNewDocument # data = srv.AxDocCrtData('509', srv.dataSource, 'c:/bdm_file/test.pdf') # idx = srv.QueryItem() # import random # idx.addField(1, str(random.randint(80000,90000)), AxServices.false) # idx.addField(2, '45237', AxServices.false) # idx.addField(3, 'REF4', AxServices.false) # idx.addField(14, 'UI', AxServices.false) # srv.createnewdocument(data, idx) # CreateBatchPage # data1 = srv.AxPageUploadData('c:/bdm_file/test.pdf') # print(data1.toXML()) # data2 = srv.AxPageUploadData('c:/bdm_file/test.pdf') # srv.createbatchpage( '509', 'batch1', data1) # UploadImageStream # filekey = srv.uploadfile('C:/Users/q1zhang/Downloads/test2.pdf') # data = srv.AxDocCrtData('509', srv.dataSource, filekey) # idx = srv.QueryItem() # idx.addField(1, '78538', AxServices.false) # idx.addField(2, '45237', AxServices.false) # idx.addField(3, 'REF4', AxServices.false) # idx.addField(14, 'UI', AxServices.false) # srv.createnewdocument(data, idx)