Представление данных SAP R/3 в Oracle Database с помощью SAP Java Connector

    В очередной раз возникла необходимость связать две известные системы между собой, теперь это будут Oracle Database и SAP. Возможно, существуют платные методы связывания, но в данном случае речь идёт о необходимости воспользоваться небольшими порциями данных.

    Расскажу о том, как можно отобразить данные, взятые из SAP, с помощью оператора select. Пример будет очень простой, для демонстрации принципиальной возможности. Создан он на основе прилагаемых к SAP JCo или общедоступных исходных текстов.

    Сразу замечу, что SAP JCo выдают только тем, кто платит. А Oracle Database не приветствует вызовов java с использованием бинарных библиотек, поэтому, по умолчанию такая возможность должна быть специально разрешена.

    Необходимые типы для таблицы и ее строк в Oracle Database, sap_table.tps:

    drop type sap_rows;
    /
    drop type sap_row;
    /
    create or replace type sap_row as object(p1 varchar(30), p2 varchar(30), p3 varchar(30), p4 varchar(30), p5 varchar(30), dt varchar(20))
    /
    create or replace type sap_rows as table of sap_row
    /
    

    Основной компонент, в котором происходит процесс получения данных из SAP и подготовки к использованию в select, art0int_sap.java:

    package com.art0int;
    
    import java.util.*;
    import java.io.*;
    import java.math.*;
    import java.util.Calendar;
    import java.text.SimpleDateFormat;
    import java.sql.*;
    
    import oracle.sql.*;
    import oracle.jdbc.driver.OracleDriver;
    
    import java.util.HashMap;
    import java.util.Properties;
    
    import com.sap.conn.jco.AbapException;
    import com.sap.conn.jco.JCoContext;
    import com.sap.conn.jco.JCoDestination;
    import com.sap.conn.jco.JCoDestinationManager;
    import com.sap.conn.jco.JCoException;
    import com.sap.conn.jco.JCoField;
    import com.sap.conn.jco.JCoFunction;
    import com.sap.conn.jco.JCoFunctionTemplate;
    import com.sap.conn.jco.JCoStructure;
    import com.sap.conn.jco.JCoTable;
    import com.sap.conn.jco.ext.DataProviderException;
    import com.sap.conn.jco.ext.DestinationDataEventListener;
    import com.sap.conn.jco.ext.DestinationDataProvider;
    
    public class SAP_TABLE
    {
    
    //массив для наполнения данными
        static Vector vrows;
    //здесь мы будем хранить структуру, обеспечивающую доступ к данным SAP
        static MyDestinationDataProvider myProvider = null;
    
    //собственно, этот метод и отправляет полученные из SAP данные для использования оператором select
        public static oracle.sql.ARRAY SQL_sap_rows (BigDecimal nrows) throws SQLException {
    
            Connection conn = new OracleDriver().defaultConnection();
    
    //пользуемся созданными типами для таблицы и ее строк
            ArrayDescriptor descriptor = ArrayDescriptor.createDescriptor("SAP_ROWS", conn );
            StructDescriptor outDesc = StructDescriptor.createDescriptor("SAP_ROW", conn);
    
            int nrowsval = nrows.intValue();
            vrows = new Vector(nrowsval);
            Object[] out_attr = new Object[6];
    
            try {
    
    	    if (myProvider == null) {
    		myProvider = new MyDestinationDataProvider();
    		try {
    		    com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(myProvider);
    		} catch(IllegalStateException providerAlreadyRegisteredException) {
    		    throw new Error(providerAlreadyRegisteredException);
    		}
                }
    
    //подготавливаем соединение с SAP
                String destName = "ABAP_AS";
                myProvider.changeProperties(destName, getDestinationPropertiesFromUI());
    	    
                JCoDestination dest;
                try {
    //устанавливаем параметры соединения
                    dest = JCoDestinationManager.getDestination(destName);
    //проверяем доступность системы
                    dest.ping();
                    System.out.println("Destination " + destName + " works");
    
    		JCoFunction function = dest.getRepository().getFunction("MY_SAPFUNCTION");
    		try {
    //получаем данные
    		    function.execute(dest);
    		} catch(AbapException e) {
    		    System.out.println(e.toString());
    		    return null;
    		}
    
    //заполняем массив
    		JCoTable out =  function.getTableParameterList().getTable(0);
    		for (int i = 0; i < out.getNumRows(); i++) {
    		    out.setRow(i);
    
    		    out_attr[1-1] = out.getString(1-1);
    		    out_attr[2-1] = out.getString(2-1);
    		    out_attr[3-1] = out.getString(3-1);
    		    out_attr[4-1] = out.getString(4-1);
    		    out_attr[5-1] = out.getString(5-1);
    		    out_attr[6-1] = (Object)new String(now());
    		    vrows.add((Object)new STRUCT(outDesc, conn, out_attr));
    
                        nrowsval--;
                        if (nrowsval==0) break;
    		}
    
                } catch(JCoException e) {
                    e.printStackTrace();
                    System.out.println("Execution on destination " + destName+ " failed");
                }
            
    
            } catch(Exception e) {
                e.printStackTrace();
            }
    
    //отдаем массив для обработки в качестве таблицы Oracle        
            oracle.sql.ARRAY outArray = new oracle.sql.ARRAY(descriptor,conn,vrows.toArray());
    
            return outArray;
    
        }
    
    //этот метод подаёт параметры подключения к SAP
        static Properties getDestinationPropertiesFromUI() {
    
            Properties connectProperties = new Properties();
            connectProperties.setProperty(DestinationDataProvider.JCO_R3NAME, "MY_R3NAME");
            connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST, "MY_IP");
            connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT, "MY_CLIENTNO");
            connectProperties.setProperty(DestinationDataProvider.JCO_USER,   "MY_SAPUSER");
            connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD, "MY_SAPPASSWORD");
            connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR,  "MY_SYSNR");
            connectProperties.setProperty(DestinationDataProvider.JCO_LANG,   "en");
            return connectProperties;
        }
    
    //класс используется как поставщик данных SAP
        static class MyDestinationDataProvider implements DestinationDataProvider
        {
            private DestinationDataEventListener eL;
            private HashMap<String, Properties> secureDBStorage = new HashMap<String, Properties>();
            
            public Properties getDestinationProperties(String destinationName)
            {
                try
                {
                    Properties p = secureDBStorage.get(destinationName);
    
                    if(p!=null)
                    {
                        if(p.isEmpty())
                            throw new DataProviderException(DataProviderException.Reason.INVALID_CONFIGURATION, "destination configuration is incorrect", null);
    
                        return p;
                    }
                    
                    return null;
                }
                catch(RuntimeException re)
                {
                    throw new DataProviderException(DataProviderException.Reason.INTERNAL_ERROR, re);
                }
            }
    
            public void setDestinationDataEventListener(DestinationDataEventListener eventListener)
            {
                this.eL = eventListener;
            }
    
            public boolean supportsEvents()
            {
                return true;
            }
    
            void changeProperties(String destName, Properties properties)
            {
                synchronized(secureDBStorage)
                {
                    if(properties==null)
                    {
                        if(secureDBStorage.remove(destName)!=null)
                            eL.deleted(destName);
                    }
                    else
                    {
                        secureDBStorage.put(destName, properties);
                        eL.updated(destName);
                    }
                }
            }
        }
    
    //вспомогательные методы, они просто могут пригодиться
        public static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss";
    
        public static String now() {
            Calendar cal = Calendar.getInstance();
            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_NOW);
            return sdf.format(cal.getTime());
    
        }
    
    }
    

    Обертка для загрузки исходника java в Oracle Database, sap_table.js:

    create or replace and compile java source named sap_table as
    @art0int_sap.java
    /
    

    Обертки для вызовов java со стороны Oracle Database, SAP_TABLE.spc:

    create or replace package SAP_TABLE is
    
    function get_java_property(prop in varchar2)
        return varchar2 is
        language java name 'java.lang.System.getProperty(java.lang.String) return java.lang.String';
    
    function sap_rows_table(nrows in number)
        return sap_rows
        IS LANGUAGE JAVA
        name 'com.art0int.SAP_TABLE.SQL_sap_rows(java.math.BigDecimal) return oracle.sql.ARRAY';
    
    end SAP_TABLE;
    /
    

    И окончательный скрипт, который всё соберёт воедино. Обратите внимание на выдачу прав для вызова библиотеки sapjco3.

    export ORACLE_SID=MYDB
    
    sqlplus '/ as sysdba' <<EOF
    
    drop user sapacc cascade;
    /
    
    create user sapacc identified by "psapacc" default tablespace users temporary tablespace temp profile monitor;
    grant connect,resource to sapacc;
    
    exec dbms_java.grant_permission('SAPACC', 'SYS:java.net.SocketPermission', 'MY_IP:MY_PORT', 'connect,resolve' );
    exec dbms_java.grant_permission('SAPACC', 'SYS:java.io.FilePermission', '.', 'read' );
    
    --здесь обратите внимание: зависит от версии Oracle Database, нужно получить номер включенного запрета на вызовы библиотек и отключить его временно, выдать разрешение на запуск, затем снова включить
    --select seq, kind, grantee, name, enabled from dba_java_policy where name like '%java.lang.RuntimePermission%';
    --98 RESTRICT PUBLIC 0:java.lang.RuntimePermission#loadLibrary.* ENABLED
    exec dbms_java.disable_permission(98);
    exec dbms_java.grant_permission('SAPACC', 'SYS:java.lang.RuntimePermission', 'loadLibrary.sapjco3', '' );
    exec dbms_java.enable_permission(98);
    
    EOF
    
    #загружаем классы в созданную схему
    dropjava -user sapacc/psapacc -verbose sapjco3.jar
    loadjava -user sapacc/psapacc -order -resolve -verbose -resolver "((* sapacc) (* PUBLIC) (* -))" sapjco3.jar
    
    sqlplus -S /nolog <<EOF
    connect sapacc/psapacc
    set pagesize 1000
    set linesize 160
    
    @sap_table.tps
    show errors
    @sap_table.js
    show errors
    @SAP_TABLE.spc
    show errors
    EOF
    
    export NLS_LANG=American_America.UTF8
    sqlplus -S /nolog<<EOF
    connect sapacc/psapacc
    set serveroutput on
    call dbms_java.set_output(1000000);
    
    set linesize 160
    set pagesize 1000
    set feedback off
    
    col p1 for a5
    col p2 for a3
    col p3 for a3
    col p4 for a5
    col p5 for a25
    col dt for a25
    
    select
     sapacc.SAP_TABLE.get_java_property('java.library.path') as java_library_path
    ,sapacc.SAP_TABLE.get_java_property('java.version') as java_version
    from dual;
    
    select * from table(sapacc.SAP_TABLE.sap_rows_table(15)) order by dt;
    
    exit;
    EOF

    Результат Вы увидите, если положите библиотеки SAP Java Connector соответствующий java.library.path каталог, который отобразится при первом вызове скрипта.
    • +10
    • 3,5k
    • 5
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 5

      0

      Класс точно всегда будет только из одного потока вызываться?
      Почему инстанциируете Calendar, а не Date?

        0
        Это упрощенный пример, он не делался с целью дать готовое приложение. Если что-то очень плохо сделано, например, «учит плохому», Вы скажите об этом явно, т.е. что надо переделать.
          0

          То что сходу бросилось в глаза:


          if (myProvider == null) {
          ...

          Будет некорректно работать в том случае если в эту строку одновременно придут два потока. Сразу по большому количеству причин.
          2 — статики. Я не знаю что у вас за ограничения, но в целом статические переменные — плохая идея.
          3 -


              public static String now() {
                  Calendar cal = Calendar.getInstance();
                  SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_NOW);
                  return sdf.format(cal.getTime());
          
              }
          

          в то время как можно было использовать просто new SimpleDateFormat(DATE_FORMAT).format(new Date())


          Я особенно не вчитывался в код — предметная область мне незнакома, но вот эти штуки сходу глаз режут.


          Кроме того режут глаз комментарии в коде, которые были бы не нужны, если бы гигантский метод был разбит на методы с говорящими названиями.

            0
            По формату даты соглашусь, а за конкурентный доступ можно не беспокоится.
            В недрах СУБД скрывается весьма спецефическая реализация Java — OJVM, живущая исключительно в передлах сессии пользователя базы данных.
        0
        Это упрощенный пример, он не делался с целью дать готовое приложение. Если что-то очень плохо сделано, например, «учит плохому», Вы скажите об этом явно, т.е. что надо переделать.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое