domingo, 7 de septiembre de 2008

SAP Java Connector

SAP Java Connector

SAP Java Connector (SAP JCo) es un componente middleware que permite el desarrollo en Java de componentes y aplicaciones compatibles con SAP. SAP JCo soporta la comunicación con un servidor SAP en ambas direcciones: llamadas de entrada, en las que un cliente Java externo se comunica con la BAPI (Business Application Programming Interface) o con los RFM (Remote Function Modules) de SAP; y llamadas de salida, donde desde ABAP (Advanced Business Application Programming) se establece una comunicación con un servidor Java externo.

Cuando desde una aplicación Java se invoca un método de la JCo Java API (Application Programming Interface), éste accede a la librería CPI-C siendo convertido en una RFC (Remote Function Call) utilizando JNI (Java Native Interface) y enviado al sistema SAP. CPI-C (Common Programming Interface for Communications) es una interfase estandarizada de comunicación entre programas creada por IBM que consiste en un conjunto de llamadas con las que dos programas pueden comunicarse mutuamente.

SAP Java Connector

Para iniciar el desarrollo con JCo, desde service.sap.com/connectors se puede descargar un archivo .zip que contiene los archivos de instalación de JCo. Se solicita usuario y contraseña registrados en SAP Service Marketplace.

Una vez descargado, el contenido del archivo .zip debe extraerse dentro de un directorio (por ejemplo C:\SAPJCo). El contenido del archivo .zip son los archivos sapjco3.jar y sapjco3.dll (en Linux libsapjco3.so), los javadocs de SAP JCo y algunos ejemplos de uso. El archivo sapjco3.jar debe estar incluido en el classpath de todos los proyectos que deseen utilizar SAP JCo.

Ejemplo de aplicación que accede al listado de materiales de SAP:
import java.io.File;
import java.io.FileOutputStream;
import java.util.Properties;
import com.sap.conn.jco.AbapException;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoDestinationManager;
import com.sap.conn.jco.JCoException;
import com.sap.conn.jco.JCoFunction;
import com.sap.conn.jco.JCoTable;
import com.sap.conn.jco.ext.DestinationDataProvider;

public class ServiceOneSap {
static String ABAP_AS = "ABAP_AS_WITHOUT_POOL";
static String ABAP_AS_POOLED = "ABAP_AS_WITH_POOL";

static {
Properties connectProperties = new Properties();

connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST, "xxx.xxx.xxx.xxx");
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "xx");
connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT, "xxx");
connectProperties.setProperty(DestinationDataProvider.JCO_USER, "xxxxxxxx");
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD, "xxxxxxxx");
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "xx");
createDataFile(ABAP_AS, "jcoDestination", connectProperties);

connectProperties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY, "3");
connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, "10");
createDataFile(ABAP_AS_POOLED, "jcoDestination", connectProperties);
}

static void createDataFile(String name, String suffix, Properties properties) {
File cfg = new File(name + "." + suffix);
if (!cfg.exists()) {
try {
FileOutputStream fos = new FileOutputStream(cfg, false);
properties.store(fos, "for tests only !");
fos.close();
} catch (Exception e) {
throw new RuntimeException("Unable to create the destination file " + cfg.getName(), e);
}
}
}

public static void MaterialList() throws JCoException {
JCoDestination destination = JCoDestinationManager.getDestination(ABAP_AS_POOLED);

JCoFunction function = destination.getRepository().getFunction("BAPI_MATERIAL_GETLIST");
if (function == null) {
throw new RuntimeException("BAPI_MATERIAL_GETLIST not found in SAP.");
}
try {
function.execute(destination);
} catch (AbapException e) {
System.out.println(e.toString());
return;
}

JCoTable selectionTable = function.getTableParameterList().getTable("MATNRSELECTION");

selectionTable.appendRow();
selectionTable.setValue("SIGN", "I");
selectionTable.setValue("OPTION", "CP");
selectionTable.setValue("MATNR_LOW", "*"); 

function.execute(destination);

JCoTable dataTable = function.getTableParameterList().getTable("MATNRLIST");

for (int loop = 0; loop < dataTable.getNumRows(); loop++){
System.out.println(dataTable.getValue("MATERIAL") + "\t" + dataTable.getValue("MATL_DESC"));
dataTable.nextRow();
}
}

public static void main(String[] args) throws JCoException {
MaterialList();
}
}
En el ejemplo, la configuración de acceso al servidor SAP se almacena en un archivo que es utilizado por el programa. Por razones de seguridad, es recomendable evitar esta práctica. Se configuran dos accesos o destinos, uno directo (ABAP_AS) y otro con pool (ABAP_AS_POOLED).
static String ABAP_AS = "ABAP_AS_WITHOUT_POOL";
static String ABAP_AS_POOLED = "ABAP_AS_WITH_POOL";

static {
Properties connectProperties = new Properties();

connectProperties.setProperty(DestinationDataProvider.JCO_ASHOST, "xxx.xxx.xxx.xxx");
connectProperties.setProperty(DestinationDataProvider.JCO_SYSNR, "xx");
connectProperties.setProperty(DestinationDataProvider.JCO_CLIENT, "xxx");
connectProperties.setProperty(DestinationDataProvider.JCO_USER, "xxxxxxxx");
connectProperties.setProperty(DestinationDataProvider.JCO_PASSWD, "xxxxxxxx");
connectProperties.setProperty(DestinationDataProvider.JCO_LANG, "xx");
createDataFile(ABAP_AS, "jcoDestination", connectProperties);

connectProperties.setProperty(DestinationDataProvider.JCO_POOL_CAPACITY, "3");
connectProperties.setProperty(DestinationDataProvider.JCO_PEAK_LIMIT, "10");
createDataFile(ABAP_AS_POOLED, "jcoDestination", connectProperties);
}

static void createDataFile(String name, String suffix, Properties properties) {
File cfg = new File(name + "." + suffix);
if (!cfg.exists()) {
try {
FileOutputStream fos = new FileOutputStream(cfg, false);
properties.store(fos, "for tests only !");
fos.close();
} catch (Exception e) {
throw new RuntimeException("Unable to create the destination file " + cfg.getName(), e);
}
}
}
Lo primero que hay que hacer, obviamente, es conectar con SAP. En el ejemplo se utiliza la conexión con pool.
JCoDestination destination = JCoDestinationManager.getDestination(ABAP_AS_POOLED);
A continuación, hay que ejecutar una función de la BAPI, en este caso BAPI_MATERIAL_GETLIST.
JCoFunction function = destination.getRepository().getFunction("BAPI_MATERIAL_GETLIST");
if (function == null) {
throw new RuntimeException("BAPI_MATERIAL_GETLIST not found in SAP.");
}
try {
function.execute(destination);
} catch (AbapException e) {
System.out.println(e.toString());
return;
}
Una vez llamada la función de la BAPI, hay que acceder a las tablas correspondientes para obtener la información deseada. En este caso, primero hay que acceder a la tabla de selección (MATNRSELECTION) y después a la tabla de datos (MATNRLIST).
JCoTable selectionTable = function.getTableParameterList().getTable("MATNRSELECTION");

selectionTable.appendRow();
selectionTable.setValue("SIGN", "I");
selectionTable.setValue("OPTION", "CP");
selectionTable.setValue("MATNR_LOW", "*");   

function.execute(destination);

JCoTable dataTable = function.getTableParameterList().getTable("MATNRLIST");
Para acceder a la tabla de selección se deben configurar una serie de parámetros y volver a ejecutar la función de la BAPI. Los parámetros de selección son los siguientes:
  • SIGN. Criterios de inclusión / exclusión para tablas: I (Inclusión), E (Exclusión).
  • OPTION. Operador de selección: EQ (Equal), NE (Not Equal), BT (BeTween), NB (Not Between), LT (Less Than), LE (Less Equal), GT (Greater Than), GE (Greater Equal), CP (Contains Pattern), NP (Not contains Pattern).
  • LOW: Límite menor del rango de datos.
  • HIGH: Límite mayor del rango de datos.
En el ejemplo se consultan todos los materiales, puesto que se utiliza la opción de Inclusión con el operador de selección CP (Contiene patrón) y se está indicando el patrón "*". Se podría también hacer una consulta de un rango de materiales (entre el 1000 y el 2000, por ejemplo) utilizando el operador de selección BT e indicando los rangos inferior y superior.
selectionTable.setValue("SIGN", "I");
selectionTable.setValue("OPTION", "BT");
selectionTable.setValue("MATNR_LOW", "1000"); 
selectionTable.setValue("MATNR_HIGH", "2000");

8 comentarios:

Fotografía dijo...

Excelente ejemplo David!.
Podrías mostrarnos un ejemplo de commit a sap con JCO a través de una bapi sencilla?

Muchas gracias.

Anónimo dijo...

Hola David.

Yo era usuario de JCo 2.x y ahora tengo que usar el 3.0 y no soy capaz de adecuarme a esta nueva librería.

Te importaría si te fusilo con unas cuantas preguntas a ver si logro hacerme a esto?? Es que siguiendo el manual de SAP no llego a ninguna parte y ya no sé si es problema de sistemas, de mi desarrollo o de qué...

cferreira at integra-soluciones dot net

Anónimo dijo...

Hola David,
¡Excelente Ejemplo!, pero tengo una duda, si yo quisiera en vez de obtener datos de SAP enviarlos, via JCO, sin XI. ¿Como tendría que configurar tanto SAP como JAVA para realizar la transferencia?

Si pudieras ayudarme, Muchas gracias!

Bea.

Anónimo dijo...

Hola David

Aunque tomaste el ejemplo de la documentación y transcribiste el párrafo que me llama la atención, quisiera saber si Tú conoces la forma de hacerlo. El párrafo se lee:

"En el ejemplo, la configuración de acceso al servidor SAP se almacena en un archivo que es utilizado por el programa. Por razones de seguridad, es recomendable evitar esta práctica."

Sin embargo, no he encontrado la forma de evitar la creacion del archivo de propiedades, ya que el DestinationManager sólo recibe, precísamente, el nombre del archivo creado.

Te agradeceré si puedes comentar algo, ya que no he encontrado por la Internet la respuesta.

Saludos.

DAVID MAESTRE dijo...

Hola,

Realmente no se puede prescindir del fichero de propiedades, entre otras cosas, porque nos sirve para configurar la conexión, lo que sí se puede conseguir es independizar el fichero de propiedades del código desarrollado.

Podrías crearte un DestinationManager que a partir de un fichero de propiedades ubicado donde consideres, añada y registre una DESTINATION.

public class DestinationManager {
public static JCoDestination getDestination(String configFile) throws JCoException, FileNotFoundException, IOException {
ClassPathResource resource = new ClassPathResource(configFile);
FileInputStream fis = new FileInputStream(resource.getFile());
Properties prop = new Properties();
prop.load(fis);
DestinationProvider provider = new DestinationProvider();
provider.addDestination("DESTINATION", prop);
com.sap.conn.jco.ext.Environment.registerDestinationDataProvider(provider);
registered = true;
return JCoDestinationManager.getDestination("DESTINATION");
}
}

Necesitarás también un DestinationProvider:

public class DestinationProvider implements DestinationDataProvider {
Map<String, Properties> propertiesForDestinationName = new HashMap<String, Properties>();

public void addDestination(String destinationName, Properties properties) {
propertiesForDestinationName.put(destinationName, properties);
}

public Properties getDestinationProperties(String destinationName) {
if (propertiesForDestinationName.containsKey(destinationName)) {
return propertiesForDestinationName.get(destinationName);
} else {
throw new RuntimeException("JCo destination not found: " + destinationName);
}
}

public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
// nothing to do
}

public boolean supportsEvents() {
return false;
}
}

Desde tú código podrás hacer simplemente la llamada:

JCoDestination destination = DestinationManager.getDestination(connectionConfigurationFile);

Indicando el fichero de configuración de la conexión.

Saludos.

Anónimo dijo...

hola david!! mi pregunta es sobre la conexion estatica, el ejemplo de la documentacion, me muestra solo cuando obtengo los atributos de la pestaña export, como se debe de recibir las estructuras que estan en la pestaña tablas en java a traves del metodo addInfo

Anónimo dijo...

Hola David,

Felicitaciones por el ejemplo. Tengo una duda, ya cuento con una aplicación Java usando jco para interactuar con SAP. Pero sucede que ahora tengo otra aplicación que también debe interactuar con SAP, y cuando las publico ambas en el mismo servidor, solo puede funcionar una a la vez la que inicio primero. Me queda la duda si el uso de la libreria jco hace que solo un sistema la pueda usar? Por favor podrias ayudarme a saber si varios sistemas pueden usar la libreria a la vez?

CARLOS PANDURO dijo...

Hola David, muy buen post; solo tenia una pequeña duda.... si la conexión a mi server SAP es con SAPRouter ¿En que campo y/o método lo configuro? y sería necesario poner el "/H/" del SAPRouter "/H/XXX.XXX.XXX.XXX/H/" o no?, espero tu respuesta, desde ya muchas gracias.

Publicar un comentario

 

RSS RSS