Thursday, May 14, 2009

SAP BI 7.0 TRAINING

Sunday, March 29, 2009

Crystal Reports for Eclipse 2.0 - Sample Codes Package Ted Ueda

New bundle of sample codes for Crystal Reports for Eclipse 2.0, brought to you by SAP Business Objects Customer Assurance Deveveloper Support - Java!

This package highlights basic functionality using the Crystal Reports Java 12.0 SDK, covering:

1. Web Application Viewing and Exporting
2. Report Creation/Modification
3. Designer Extension Points

Available for download here: Crystal Reports Java 12 Sample Codes

Web Intelligence XI 3.x - Reporting off the BusinessObjects Enterprise CMS Via Data Access DDK Ted Ueda

I work for SAP Business Objects Developer Support, helping developers create custom applications using the SDKs supplied with Business Objects products. If you log a SAP Incident concerning an issue with our SDKs, I might be the one calling to help fix the problem!

A not infrequent request I receive is for a way to report off the BusinessObjects Enterprise Central Management Server (CMS) database. There's very compelling business reasons for doing so - to record details of scheduled reports, user activity and server usage and load. The Enterprise Auditing features have come a long way and been greatly expanded since version 10, but there's still some data that's not being recorded in the audit database.
Question: How to Report off the CMS Database?

You cannot connect to and report off the CMS database directly. Much of the info is blobbed in a composite column in the repository tables, and this data cannot be decrypted without having to go through the CMS service.

To access the repository data, you would use the Enterprise SDK or Enterprise Web Services to send an Enterprise query (a SQL-type query) or a path query (an XPath-type query) to the CMS, that then will return a collection of InfoObject objects. An InfoObject is a programmatic, object-oriented representation of data held in the CMS database.

The Query Builder Admin Tool (found on the Enterprise XI 3.x Java web app deployment at http://:/AdminTools/), uses the SDK to send an Enterprise query to the CMS and displays a view of the InfoObject objects and their properties. Query Builder is particularly useful if you slightly modify it to accept path queries as well as Enterprise queries, as described here. You cannot, however, schedule the Query Builder, and it's difficult to translate its HTML output to other formats.

What's required is a way to "hook up" either the Enteprise SDK or Enterprise Web Services to a data source that can be consumed by a report.

One possibility is to use Desktop Intelligence documents with VBA data providers, where the VBA invokes the Enterprise COM SDK to retrieve the InfoObject objects. The VBA then will translate the InfoObject properties to a data cube. The COM version of the SDK, however, has been deprecated with XI Release 2 and is no longer available with XI 3.x

Another possibility is to use the POJO reporting functionality of Crystal Reports. You can write Java code that uses the Enterprise SDK or Web Services to retrieve InfoObject objects and copies the desired InfoObject properties to a POJO collection. This solution, however, is restricted to Crystal Reports reports only.

What of Web Intelligence? Or Query as a Web Service (QaaWS)? Fortunately, there is a way.
POJO Reporting using the DDK

In a previous blog entry, POJO and RSS Feed Reporting with Web Intelligence Using the Data Access Driver Development Kit, I describe how you can use the new Universe Data Access Driver Development Kit (DDK) SDK and the Crystal Reports Java SDK POJO ResultSet Factory to create a Universe Connection that consumes POJO collection data sources.

This blog entry is a continuation of that blog, so that blog is worth a read if you're interested in this current subject.

What I'm going to describe here is how to connect the BusinessObjects Enterprise BIPlatform Web Services to the POJO data driver. The connection will pass a specified path query to Enterprise via Web Services, and fill a POJO collection with returned InfoObject collection properties.

The data flow in this workflow is:

CMS -> Web Services -> CRJ SDK -> DDK -> Universe

To understand this blog, you should be familiar with the following:

1. Java programming
2. BusinessObjects Enterprise Web Services programming
3. BusinessObjects Enterprise Path Queries
4. How to download Crystal Reports for Eclipse 2.0 (CRJ SDK)
5. Configuring the DDK JavaBean Driver
6. Creating Universe connections and Universes

I think the results well worth the effort. Here's a screenshot of what I was able to accomplish:
image



This is a Web Intelligence document that lists all scheduled instances that were created since last September, grouped by name. You can see that many InfoObject properties for Web Intelligence, Crystal Reports and even Program Objects are represented in the available objects.

I'll provide code samples, brief deployment instructions, and possible avenues for extending the code.
Hooking up Enterprise Web Services to the DDK JavaBeans

I'll list the sample code here. The SCN width restrictions make the code not easy to read here, so I recommend copy-and-pasting them to local file and reading them using your favorite text editor.

The POJO JavaBeans code comes in three Java source code files. First is the POJO definition, that is used to hold the InfoObject properties retrieved from the CMS. Second is the JavaBean, that defines the method the Universe calls to retrieve the data as a java.sql.ResultSet object. Third is the JavaBeanInfo, that informs the JavaBean consumer which method in the JavaBean code should be called. There is also the DDK JavaBean configuration file, javabean.sbo, used to configure the connection - location of the Java jar library files and connection parameters.

Here is the backing POJO code:

InfoObjectPOJO.java - the POJO representation of InfoObject data
package com.sap.ddk.javabean.sample; import java.sql.Timestamp; /* * POJO for InfoObject properties, designed as adapter between Universe DDK, * Crystal Reports Java POJO ResultSet Factory and BusinessObjects Enterprise * Web Services. * * Each property maps to a possible property for an InfoObject or its subclass. * * - Universes represent a boolean as Number. * - CRJ POJO does not use java.util.Calendar but java.sql.Timestamp for timestamps. * - CRJ POJO does not handle nested properties, so nested InfoObject properties * are mapped to a property with name reflecting the nesting. For example, * the property InfoObject.getSchedulingInfo().getBeginDate() will map to * InfoObjectPOJO.getSchedulingInfo_BeginDate(). */ public class InfoObjectPOJO { private Timestamp creationTime; private String cuid; private String description; private Timestamp endTime; private String errorMessage; private String fileProperties_Files_FileArray_Name; private Integer id; private Integer instance; private Integer isRecurring; private String keywords; private String kind; private Timestamp lastRunTime; private String lastSuccessfulInstanceCUID; private String name; private Timestamp nextRunTime; private String owner; private String parentCUID; private Timestamp schedulingInfo_BeginDate; private Timestamp schedulingInfo_EndDate; private String schedulingInfo_Flags; private Integer schedulingInfo_RightNow; private String schedulingInfo_ScheduleType; private String schedulingInfo_Status; private String schedulingInfo_TimeZone; private Timestamp startTime; private Timestamp updateTime; public Integer getInstance() { return instance; } public void setInstance(Integer instance) { this.instance = instance; } public Integer getIsRecurring() { return isRecurring; } public void setIsRecurring(Integer isRecurring) { this.isRecurring = isRecurring; } public String getKeywords() { return keywords; } public void setKeywords(String keywords) { this.keywords = keywords; } public String getKind() { return kind; } public void setKind(String kind) { this.kind = kind; } public Timestamp getLastRunTime() { return lastRunTime; } public void setLastRunTime(Timestamp lastRunTime) { this.lastRunTime = lastRunTime; } public String getLastSuccessfulInstanceCUID() { return lastSuccessfulInstanceCUID; } public void setLastSuccessfulInstanceCUID(String lastSuccessfulInstanceCUID) { this.lastSuccessfulInstanceCUID = lastSuccessfulInstanceCUID; } public Timestamp getNextRunTime() { return nextRunTime; } public void setNextRunTime(Timestamp nextRunTime) { this.nextRunTime = nextRunTime; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public String getParentCUID() { return parentCUID; } public void setParentCUID(String parentCUID) { this.parentCUID = parentCUID; } public Timestamp getSchedulingInfo_EndDate() { return schedulingInfo_EndDate; } public void setSchedulingInfo_EndDate(Timestamp schedulingInfo_EndDate) { this.schedulingInfo_EndDate = schedulingInfo_EndDate; } public String getSchedulingInfo_Flags() { return schedulingInfo_Flags; } public void setSchedulingInfo_Flags(String schedulingInfo_Flags) { this.schedulingInfo_Flags = schedulingInfo_Flags; } public Integer getSchedulingInfo_RightNow() { return schedulingInfo_RightNow; } public void setSchedulingInfo_RightNow(Integer schedulingInfo_RightNow) { this.schedulingInfo_RightNow = schedulingInfo_RightNow; } public String getSchedulingInfo_ScheduleType() { return schedulingInfo_ScheduleType; } public void setSchedulingInfo_ScheduleType(String schedulingInfo_ScheduleType) { this.schedulingInfo_ScheduleType = schedulingInfo_ScheduleType; } public String getSchedulingInfo_TimeZone() { return schedulingInfo_TimeZone; } public void setSchedulingInfo_TimeZone(String schedulingInfo_TimeZone) { this.schedulingInfo_TimeZone = schedulingInfo_TimeZone; } public Timestamp getStartTime() { return startTime; } public void setStartTime(Timestamp startTime) { this.startTime = startTime; } public Timestamp getUpdateTime() { return updateTime; } public void setUpdateTime(Timestamp updateTime) { this.updateTime = updateTime; } public String getSchedulingInfo_Status() { return schedulingInfo_Status; } public void setSchedulingInfo_Status(String schedulingInfo_Status) { this.schedulingInfo_Status = schedulingInfo_Status; } public Timestamp getSchedulingInfo_BeginDate() { return schedulingInfo_BeginDate; } public void setSchedulingInfo_BeginDate(Timestamp schedulingInfo_BeginDate) { this.schedulingInfo_BeginDate = schedulingInfo_BeginDate; } public Timestamp getCreationTime() { return creationTime; } public void setCreationTime(Timestamp creationTime) { this.creationTime = creationTime; } public String getCUID() { return cuid; } public void setCUID(String cuid) { this.cuid = cuid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getID() { return id; } public void setID(Integer id) { this.id = id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Timestamp getEndTime() { return endTime; } public void setEndTime(Timestamp endTime) { this.endTime = endTime; } public String getErrorMessage() { return errorMessage; } public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } public String getFileProperties_Files_FileArray_Name() { return fileProperties_Files_FileArray_Name; } public void setFileProperties_Files_FileArray_Name( String fileProperties_Files_FileArray_Name) { this.fileProperties_Files_FileArray_Name = fileProperties_Files_FileArray_Name; } }

here is the JavaBean:

InfoObjectJavaBean.java - the DDK JavaBean
package com.sap.ddk.javabean.sample; import java.lang.reflect.Method; import java.net.URL; import java.sql.ResultSet; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Properties; import com.businessobjects.connectionserver.CSException; import com.businessobjects.connectionserver.datasources.ddk.Context; import com.businessobjects.connectionserver.datasources.ddk.javabean.Bean; import com.businessobjects.dsws.Connection; import com.businessobjects.dsws.biplatform.BIPlatform; import com.businessobjects.dsws.biplatform.ResponseHolder; import com.businessobjects.dsws.session.EnterpriseCredential; import com.businessobjects.dsws.session.Session; import com.businessobjects.enterprise.infoobject.InfoObject; import com.businessobjects.enterprise.infoobject.InfoObjects; import com.crystaldecisions12.sdk.occa.report.application.internal.POJOResultSetHelper; /* * BusinessObjects Enterprise Data Access Driver Development Kit JavaBean * sample for reporting off of the CMS database using the BusinessObjects Enterprise * Web Services. * * Enterprise logon credentials are read from the Universe Connection User and * Password. The Enterprise path query is read from the Connection url. * * CMS name is read from the "CMS Name" Parameter defined for the connection in the * javabean.sbo. * * CMS authentication type is read from the "CMS Authentication Type" Parameter in the * javabean.sbo. * * This sample requires the XI 3 Business Objects Web Services Java Consumer and Crystal * Report Java SDK POJO ResultSet Factory. */ public class InfoObjectJavaBean implements Bean { private String url; private String boUsername; private String boPassword; private String boCMSName; private String boAuthType; private String wsSessionURL; /* * Read and store Enterprise query and logon credentials from the context * and CMS name and authentication type from javabean.sbo parameters. */ public void initialize(Context context, String url, Properties properties) throws CSException { this.url = url; this.boUsername = (String) context.getCredentials().get("USER"); this.boPassword = (String) context.getCredentials().get("PASSWORD"); try { this.boCMSName = context.getConfiguration().getString("CMS Name"); } catch (CSException cse) { this.boCMSName = "localhost"; } try { this.boAuthType = context.getConfiguration().getString("CMS Authentication Type"); } catch (CSException cse) { this.boAuthType = "secEnterprise"; } try { this.wsSessionURL = context.getConfiguration().getString("Session Web Services URL"); } catch(CSException cse) { this.wsSessionURL = "http://localhost:8080/dswsbobje/services/Session"; } } public void close() {} /* * Retrieve InfoObject collection from Enterprise Web Services, copy properties * over to InfoObjectPOJO, and generate and return ResultSet using POJO * ResultSet Factory. */ public ResultSet getInfoObjectResultSet() throws Throwable { Method[] pojoSetters; List pojos; POJOResultSetHelper pojoResultSetHelper; /* * DSWSBobje Web Services Consumer expects a Thread context class loader. Since * DDK does not supply one, we copy over the bean creator class loader. */ Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); /* * Retrieve all bean setters in the InfoObjectPOJO. */ pojoSetters = getPOJOSetters(InfoObjectPOJO.class); pojos = new ArrayList(); try { URL sessionURL; Connection boConnection; Session boSession; EnterpriseCredential boEnterpriseCredential; String[] biPlatformURL; BIPlatform boBIPlatform; /* * Log onto Enterprise Web Services and retrieve the BIPlatform service. */ sessionURL = new URL(this.wsSessionURL); boConnection = new Connection(sessionURL); boSession = new Session(boConnection); boEnterpriseCredential = EnterpriseCredential.Factory.newInstance(); boEnterpriseCredential.setDomain(this.boCMSName); boEnterpriseCredential.setLogin(this.boUsername); boEnterpriseCredential.setPassword(this.boPassword); boEnterpriseCredential.setAuthType(this.boAuthType); boSession.login(boEnterpriseCredential); biPlatformURL = boSession.getAssociatedServicesURL("BIPlatform"); boBIPlatform = BIPlatform.getInstance(boSession, biPlatformURL[0]); /* * Retrieve InfoObject collection. */ ResponseHolder boResponseHolder = boBIPlatform.get(this.url, null); InfoObjects boInfoObjects = boResponseHolder.getInfoObjects(); /* * For each InfoObject, create a POJO. */ for(InfoObject infoObject : boInfoObjects.getInfoObjectArray()) { InfoObjectPOJO pojo; pojo = createInfoObjectPOJO(infoObject, pojoSetters); pojos.add(pojo); } boSession.logout(); } catch(Throwable e_ignore) { // Log.error(e_ignore); throw e_ignore; } /* * Generate java.sql.ResultSet from collection of POJOs. */ pojoResultSetHelper = new POJOResultSetHelper(InfoObjectPOJO.class); return pojoResultSetHelper.createResultSet(pojos); } /* * For a specified class, return all bean setters. */ private Method[] getPOJOSetters(Class aclass) { List pojoSetterList; pojoSetterList = new ArrayList(); for(Method method : aclass.getDeclaredMethods()) { if(!method.getName().startsWith("set") || !Void.TYPE.equals(method.getReturnType()) || (method.getParameterTypes().length != 1)) { continue; } pojoSetterList.add(method); } return pojoSetterList.toArray(new Method[0]); } /* * Return a bean property with specified name from an Object * using reflection. If the entity is an Array object, return * an array containing bean property for each element of the array. */ private Object getValue(Object entity, String propertyName) throws Exception { Object retval; if(!entity.getClass().isArray()) { Method getter; getter = entity.getClass().getMethod("get" + propertyName, (Class[]) null); retval = getter.invoke(entity, (Object[]) null); } else { Object[] entityArray; Object[] retvalArray; entityArray = (Object[]) entity; if(entityArray.length == 0) { return null; } retvalArray = new Object[entityArray.length]; for(int i = 0, m = entityArray.length ; i < m ; i++) { retvalArray[i] = getValue(entityArray[i], propertyName); } retval = retvalArray; } return retval; } /* * Simple string representation of an Array, essentially * "[" + (String) element[0] + ", " + (String) element[1] + ", " ... "]" */ private String arrayToString(Object[] array) { StringBuilder stringer; stringer = new StringBuilder(); stringer.append("["); for(Object item : array) { if(!item.getClass().isArray()) { stringer.append(item); } else { stringer.append(arrayToString((Object[]) item)); } stringer.append(", "); } if(stringer.lastIndexOf(",") > 0) { stringer.delete(stringer.length() - 2, stringer.length()); } stringer .append("]"); return stringer.toString(); } /* * Create InfoObjectPOJO from InfoObject. * For each setter in InfoObjectPOJO, use reflection to retrieve the equivalent * property from the InfoObject. */ public InfoObjectPOJO createInfoObjectPOJO(InfoObject infoObject, Method[] pojoSetters) { InfoObjectPOJO infoObjectPOJO; infoObjectPOJO = new InfoObjectPOJO(); for(Method pojoSetter : pojoSetters) { Object val = null; /* * If Number type, do not pass null, since POJO ResultSet Factory does not * accept null Number or Boolean values. */ if(Integer.class.isAssignableFrom(pojoSetter.getParameterTypes()[0])) { val = new Integer(0); } try { String[] propertyNames; /* * InfoObjectPOJO with property name Parent_One_Two is mapped * to InfoObject getter getParent().getOne().getTwo() * * Note that DSWS Bobje Web Services use primitive types for * properties, so null values for those cannot be represented. */ propertyNames = pojoSetter.getName().substring(3).split("_"); val = getValue(infoObject, propertyNames[0]); for(int i = 1, m = propertyNames.length ; i < m ; i++ ) { val = getValue(val, propertyNames[i]); } /* * Fix up some types: * - POJO ResultSet Factory uses java.sql.Timestamp and not java.util.Calendar * - Universes represent boolean as Number and not java.lang.Boolean * - If DSWS class instance, transform to String. */ if(val != null) { if(val instanceof Calendar) { val = new Timestamp(((Calendar) val).getTimeInMillis()); } else if(val instanceof Boolean) { val = new Integer(Boolean.TRUE.equals(val) ? 1 : 0); } else if(val.getClass().isArray()) { try { val = arrayToString((Object[]) val); } catch(Exception eek) { } } else if(val.getClass().getPackage().getName().startsWith("com.businessobjects")) { val = val.toString(); } } } catch(Exception e_ignore) { // Log.error(e_ignore); } try { pojoSetter.invoke(infoObjectPOJO, new Object[] { val }); } catch(Exception e_ignore) { // Log.error(e_ignore); } } return infoObjectPOJO; } }

and finally the JavaBeanInfo:

InfoObjectJavaBeanInfo.java - the DDK JavaBean Info
package com.sap.ddk.javabean.sample; import java.beans.BeanDescriptor; import java.beans.MethodDescriptor; import java.beans.ParameterDescriptor; import java.beans.SimpleBeanInfo; import java.util.Properties; public class InfoObjectJavaBeanInfo extends SimpleBeanInfo { public InfoObjectJavaBeanInfo() { super(); } @Override public BeanDescriptor getBeanDescriptor() { BeanDescriptor beanDescriptor; beanDescriptor = new BeanDescriptor(InfoObjectJavaBean.class); beanDescriptor.setName("InfoObjectJavaBean"); beanDescriptor.setShortDescription("JavaBean from a InfoObject"); return beanDescriptor; } @Override public MethodDescriptor[] getMethodDescriptors() { MethodDescriptor[] methodDescriptors = null; try { methodDescriptors = new MethodDescriptor[] { new MethodDescriptor (InfoObjectJavaBean.class.getDeclaredMethod ("initialize", new Class[] {com.businessobjects.connectionserver.datasources.ddk.Context.class, String.class, Properties.class}), new ParameterDescriptor[] {new ParameterDescriptor (), new ParameterDescriptor (), new ParameterDescriptor ()}), new MethodDescriptor(InfoObjectJavaBean.class.getDeclaredMethod("close", (Class[])null)), new MethodDescriptor(InfoObjectJavaBean.class.getDeclaredMethod("getInfoObjectResultSet", (Class[])null)) }; methodDescriptors[0].setShortDescription ("initialize JavaBean"); methodDescriptors[0].getParameterDescriptors ()[0].setName ("context"); methodDescriptors[0].getParameterDescriptors ()[0].setShortDescription ("context of JavaBean"); methodDescriptors[0].getParameterDescriptors ()[1].setName ("url"); methodDescriptors[0].getParameterDescriptors ()[1].setShortDescription ("url for InfoObject feed"); methodDescriptors[0].getParameterDescriptors ()[2].setName ("properties"); methodDescriptors[0].getParameterDescriptors ()[2].setShortDescription ("properties for JavaBean"); methodDescriptors[1].setShortDescription ("finalize JavaBean"); methodDescriptors[2].setShortDescription ("Retrieve InfoObject ResultSet"); } catch(Exception e_ignore_at_your_peril){} return methodDescriptors; } }

Here's my copy of the DDK JavaBean Connection configuration file. Note that it references the CRJ SDK jars and the BusinessObjects Enterprise Web Services Consumer jars in the classpath definition, and specifies some runtime parameters. This file is found in the \win32_x86\dataAccess\connectionServer\javabean folder:

javabean.sbo - the Universe JavaBean Connectivity configuration file
com.businessobjects.connectionserver.java.drivers.javabean.JavaBeanDriver Java Beans javabean.setup javabean javabean javabean Procedures javabean False True False No No $ROOT$/beans/bean_infoobject.jar c:/eclipse/plugins/com.businessobjects.crystalreports.crsdk.libs_12.2.200.r443/lib/CrystalReportsRuntime.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/activation-1.1.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/axiom-api-1.2.5.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/axiom-impl-1.2.5.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/axis2-kernel-1.3.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/axis2-saaj-1.3.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/axis2-xmlbeans-1.3.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/backport-util-concurrent-2.2.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/commons-codec-1.3.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/commons-httpclient-3.0.1.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/commons-logging-1.1.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/dsws-bicatalog.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/dsws-biplatform.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/dsws-common.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/dsws-publish.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/dsws-queryservice.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/dsws-reportengine.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/dsws-saveservice.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/dsws-session.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/log4j-1.2.14.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/mail.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/stax-api-1.0.1.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/struts.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/wilog.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/wsdl4j-1.6.2.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/wstx-asl-3.2.1.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/xbean-2.2.0.jar c:/Program Files/Business Objects/BusinessObjects Enterprise 12.0/web services/en/dsws_consumer/data/dswsJavaApi/XmlSchema-1.3.2.jar com.sap.ddk.javabean.sample.InfoObjectJavaBean $DATASOURCE$ localhost secEnterprise http://tueda-bexi3:8080/dswsbobje/services/Session Java Beans define java class for JavaBean driver $DATASOURCE$ Java Beans

The ClassPath Path entities include the CRJ SDK library jar file CrystalReportsRuntime.jar (at the location installed with the Crystal Reports for Eclipse 2.0 All-In-One installation) and the BusinessObjects Enterprise Web Services library jars found in the \web services\en\dsws_consumer\data\dswsJavaApi folder.

The javabean.sbo references the bean_infoobject.jar file, that contains the compiled Java program, copied to the \win32_x86\dataAccess\connectionServer\beans folder.
BusinessObjects Enterprise Web Services Connection

Connecting and logging onto the Enterprise Web Services Provider requires five items of info: the Enterprise user name, password, CMS name, authentication type, and the Enterprise Session Web Services URL. These items will be provided either in the javabean.sbo file or when configuring the connection in the Universe Designer.

The CMS Name, authentication type and Session Web Services URL are specified in the javabean.sbo file, using Parameter entities with Name attributes "CMS Name", "CMS Authentication Type" and "Session Web Services URL".

The Universe Designer presents three configuration fields when creating a JavaBean connection: the username, password and uri. In the sample, the username and password fields are used to specify the Enterprise credentials, and the uri is used to specify the path query.
Deployment

To deploy the sample, compile the Java code where you ensure you have the CRJ SDK library jars, the DDK library jar \classes\connectionServer.jar, and the BusinessObjects Enterprise Web Services Java Consumer jars in the classpath. Jar the compiled object files and place in \win32_x86\dataAccess\connectionServer\beans\bean_infoobject.jar.

Back up the javabean.sbo configuration file in your deployment, and replace it with the file above. Edit it to ensure it reflects where you have the required jar files. Also edit the file to configure your CMS name, authentication type and Session Web Services URL.

Launch the Universe Designer, then navigate to Tools -> Connections. Click "Add" then ensure the "Filter stored procedure network layer" option is selected. Give a name for your connection, then navigate the tree to find Java Beans -> POJO JavaBean datasource -> JavaBean. Click Next.

You will see the authentication panel. Enter your Enterprise credential user name and password, then enter a path query. A simple one to start off would be:

path://InfoObjects/Root Folder/@STATIC,SI_OWNER

to return all the root Public Folders.

Test the connection, then click Finish. Create an Universe off of this connection - JavaBeans are considered "stored procedure" connections, so ensure you enable that when selecting your connection, then insert the "Stored Procedure" into the Universe. Save the Universe.

You should now be able to fire up your copy of the Web Intelligence Rich Client, and create a report off of the Universe you just created.
Code Description

I've placed comments in the Java code, but I'll add a few more notes here.



* Items in a InfoObject object collection returned from Enterprise do not have uniformly the same properties - subclasses such as CrytalReport, Web Intelligence, User, UserGroup, Server, etc, all have a different set of properties. The returned collection may contain a mix of different subclasses.

Since a relational table row-column structure requires consistency, I specify the properties that are returned by the definition of the InfoObjectPOJO class.

The JavaBean code uses Java Reflection to introspect the InfoObjectPOJO class for all defined properties, then uses Reflection on each item in the InfoObject object collection to retrieve the property with the same name. If an InfoObject does not define a property, the property is set to null.

To extend the properties exposed by this driver, add new properties to the InfoObjectPOJO.



* The relational model of Universe tables prohibit automatic handling of nested properties, such as InfoObject.SchedulingInfo.BeginDate. To allow for flattening this to a 1-deep representation, nested properties are declared in the InfoObjectPOJO by separting the class/property names by underscores. For example, the InfoObjectPOJO defines the getter getSchedulingInfo_BeginDate() method to retrieve the InfoObject.SchedulingInfo.BeginDate property.

Also, since the relation model cannot automatically handle indexed properties, array values are translated into comma-delimited strings.



* The BusinessObjects Enterprise Web Services Consumer library uses a Thread-local-context ClassLoader. The DDK uses JNI with custom ClassLoader (used to load the classpath path entities specified in the javabean.sbo) and does not provide a Thread-local ClassLoader.

In the JavaBean code, I retrieve the ClassLoader for the JavaBean instance, and set it as the Thread-local ClassLoader programmatically, similar to what I did for the RSS feed library as described in the previous blog.



* The CRJ SDK POJO ResultSet Factory data type limitation causes all date, date time and timestamp types to be java.sql.Timestamp. Universes represent boolean values as Number and not java.lang.Boolean, but the ResultSet Factory prevents Booleans and Numbers from being nullable. So the default values are set to 0 (but the Web Services Consumer uses primitives for numbers and booleans anyways, so it's not the driver causing this lossage).

Reporting using Query as a Web Service

You're not restricted to just Web Intelligence documents when reporting off the CMS Universe connection. By building a QaaWS, you can expose the data to many other reporting types, including LiveOffice/Excel/Xcelsius and Crystal Reports.

As a simple test, I created the QaaWS from a CMS DDK JavaBean connection with the following query:

query://{Select STATIC, SI_OWNER From CI_INFOOBJECTS Where SI_INSTANCE=1 AND SI_RECURRING=0 AND SI_CREATION_TIME >= '2008.09.01'}

that returns info for all scheduled reports since September of last year (approximately when I installed BusinessObjects Enterprise XI 3.1).

I designed a Crystal Report off of this QaaWS, and focussed on the total processing time - the difference between the schedule run end time and begin time. Grouping by report name, I listed out the Top 6 by total of all scheduled run times, then plotted the result, along with average run time per instance.

Here's a screenshot:
image



A bit of a surprise there - almost half the total scheduled processing time was taken up by a single report! Maybe not suprising - the "World Sales Report" is the one I use most often for testing and creating sample applications.

The "Create Webi From Scratch", on the other hand, was a not-simple Web Intelligence document I created programmatically for load testing.

I find it reassuring that the Top 6 covers a spectrum of reporting types - Crystal Reports, Web Intelligence, Publications, Crystal Reports off of Universes, and even a RAS SDK app running from within a Java Program Object. Good to know I'm covering the field...

So even with a simple report that I whipped up just for this blog, I've gotten considerable insight into how I use my Enterprise installation. I'm thinking I should schedule this report monthly, so I can keep track of my usage.

If this was a production deployment, such info would be invaluable in tuning the system for better performance. Just goes to show how illuminat Business Intelligence (BI), Business Objects, Java Programminging having ready access to the CMS data can be.

As I conclude this blog entry, I'm looking at the report chart again and it brings back memories of very interesting Support Incidents I've worked on. Maybe I'll blog about one or two or three in the near future, before new Incidents pushes them out of my recollection...



Ted Ueda is a Senior Engineer with Technical Customer Assurance, SAP Business Objects.

Polestar in the cloud Web Services API - Sample Codes Ted Ueda

If you're interested in Polestar in the cloud and the recently announced Polestar in the cloud API $20K IDEAtion Challenge, then this blog entry may be of interest to you. I'm going to present here a few sample codes for that API, so you'll quickly get up to speed and have a head start on the competition!

Each of the sample codes follow the same workflow: retrieve data, upload the dataset to OnDemand, then view it in Polestar in the cloud. What differs is the programming language used and the data source. The four samples are:

  1. Python script with inline dataSet.
  2. C# with data retrieved from Query as a Web Service
  3. Xcelsius SDK - Adobe Flex component.
  4. Java with RSS Feed data.
Polestar in the cloud API

Ty Miller, a Techical Director here, sent me email about a month ago asking whether I was interested in checking out a cool new Web Services API for the BusinessObjects Labs project called Polestar in the cloud. I figured whatever Ty's involved in is high on the hot-and-interesting scale - he's sent a few pretty interesting Customer Support cases my way in the past - but at the time I was busy helping customers and working with Crystal Reports for Eclipse 2.0 and Universe Data Access Driver Development Kit SDK, so I had to flag his email for later action.

Rob Horne, who's also into pretty interesting stuff, recently blogged about Polestar in the cloud. Reading his blog certainly encouraged me to check it out (although I have to say I'm far from a Steve Nash fan), and what I found was pretty impressive.

Polestar makes the relationship between you and your data highly personal - I've gotten tremendous insight, and seen relationships I've not thought of, merely by uploading data and working with it in Polestar. Having Polestar as part of the "Cloud" computing on Whohar - the http://create.ondemand.com/ portal - makes it that much more compelling. Now you can upload and store your data on Whohar, and analyze it using different dashboards as well as Polestar.

Programmatic dataset management is accomplished via the Whohar RESTful Web Services API - you can upload, download, update and delete datasets via simple HTTP calls. You can then invoke a URL to view a dataset in Polestar.

The beauty of the API is in its simplicity - you're able to code against it using most programming languages out there, and have a very elegant means to integrate your app with Polestar in the cloud.

Rob kindly sent me a preview of the Whohar API docs - it's now available publicly here - and I've been playing with the API since. I've cleaned up some of my sample codes and present it here for your enjoyment. I do hope it helps you in quickly working out the API workflow required to integrate your stuff with Polestar in the cloud.

Python Script with Inline DataSet

Here's a short Python script that I used to get a 'feel' for the API -

Python script uploading data to Polestar in the cloud

that shows the basics of uploading a dataSet and viewing it in Polestar: the required XML dataset format, setting up the HTTP Basic Authentication, retrieving the dataSet ID and session ID, and redirecting to Polestar in the cloud.

Edit the top of the file to insert your create.ondemand.com logon credentials, and modify the bottom to use either the webbrowser lib or explicit launching of a web browser, and run it using the Python interpreter.

You'll note how simple the API is - the advantage of using a RESTful Web Services design.

C# With Data Retrieved from Query as a Web Service

A bit more useful example, using C# - I wrote it on Visual Studio 2008 - that consumes data from Query as a Web Service (QaaWS), upload them to Whohar and view in Polestar in the cloud. Here's a screenshot of what you get, showing both the QaaWS client with the query, and the Polestar view of the data:

image

The query is based on the eFashion sample Universe, and Polestar is displaying the breakdown of sales revenue, quanitity of merchandise sold, and margin for stores by State. Texas is looking pretty good (more on Texas later)...

Here's the code:

C# 2008 to upload QaaWS-generated data to Polestar OnDemand

You'll note that much of the code is taken up by the QaaWS SOAP Web Service call - dynamically generating stub code from the WSDL, auto-compiling and loading the assembly, and using Reflection to invoke the service - and not by the OnDemand API.

It may be an unfair comparison, but this does suggest the simplicitly of a RESTful Web Service in comparison to SOAP.

Compile the code using Visual Studio 2008, and run it in the command-line:

QaaWS2Polestar.exe

and it'll launch Internet Explorer with Polestar OnDemand view of your QaaWS data.

A couple of comments about the sample code:

  1. It generates the QaaWS stubs from the WSDL using .NET Service Reference rather than Web Reference. A Service-Reference-generated-code makes for easier Reflection into the stub classes, since you can infer use from the generated property names.
  2. It currently doesn't set any QaaWS prompts - you can add to the Reflection routine near the end of the code to introspect any prompts.
Xcelsius SDK - Adobe Flex Component

I created this sample using Adobe Flex Builder 3 and the Xcelsius SDK. Implementation uses MXML that subclasses a mx:Button component that, when clicked, reads a bound 2D Array, constructs the Whohar dataSet XML, uploads the data and displays it in Polestar in the cloud.

You can use it within an Adobe Flash application or embedded in Xcelsius. Here's a screenshot of it embedded within Xcelsius, showing both the Polestar and Xcelsius views of the data:

image

When you click on the "To Polestar OnDemand" button you see on the Xcelsius view, the Polestar OnDemand view is displayed.The data is just some test data I entered into Excel in the Xcelsius designer - in actual practise, you'd be using 'real' Excel data, or a Web Service data source.

Here's the MXML code:

PolestarOnDemand.xml - uploads bound Array to Polestar in the cloud

and the MXML for the Logon dialog box it uses to collect the OnDemand logon credentials:

PolestarOnDemandLogon.xml - Logon dialog box for PolestarOnDemand.xml

Note that the data the component uploads is bound to an 2D array. Here's a very simple Xcelsius Property Sheet MXML that will allow you to do the binding to Excel cell ranges from within Xcelsius:

PolestarOnDemandPropertySheet.mxml - Xcelsius Property Sheet for PolestarOnDemand.xml

You can also use the component outside of Xcelsius and within a Flex application. Here's a short sample of how you'd invoke PolestarOnDemand.mxml from a mx:Application:

PolestarOnDemandApp.mxml - embedding PolestarOnDemand.mxml component

Some comments:

  1. The code passes the parameter suppress_response_codes in the Whohar URL to have responses always return HTTP 200 (other than HTTP 5xx errors). This feature was added specifically for Flex running in Internet Explorer. The normal RESTful HTTP 201 (Created) return on new dataset creation throws an exception in Internet Explorer, preventing Flex from reading the returned new resource location.
  2. Xcelsius SDK currently supports Flex 2 only - so in Flex Builder 3, ensure you set the compiler for your project to use version 2 rather than 3.
  3. Flex 2 does not contain a library class for Base64 encoding - I used one of the many open source Base64 implementations for ActionScript available on the web.
  4. Xcelsius supports MXML components via the Xcelsius SDK only starting with FixPack 1.1 (previously, you had to code using ActionScript). I tested this component using Service Pack 1 + FixPack 1.2.
  5. I didn't Style the component for Xcelsius - if you want to specify fonts and colors for the component in Xcelsius, you'd need to specify styling in the code. I recommend looking at the ComboBox sample for an example on how to property style a Xcelsius SDK component.
Java With RSS Feed Data

Last example - retrieve data from Yahoo! HotJobs RSS Feed, upload and view in PoleStar OnDemand. I always wanted to be a spaceman, so I enter 'astronaut' for the HotJobs keyword, and this is the Polestar in the cloud result:

image

All the astronaut-related jobs are located in Texas! Most in Houston (as in "Houston, we've got a problem") and a couple in Galveston. So that means I have to be willing to relocate if I want to fulfill my childhood career aspiration. Oh, well, I think I better stick to more realistic keywords like 'programmer', 'developer', or 'cowboy'. Maybe I should learn to ride a horse first...

Anyways, here's the Java code:

JobLocationPolestar.java - Polestar in the cloud with data from Yahoo! HotJobs RSS Feed

It's a command-line app where the first argument is the HotJobs keyword and second and third arguments are the OnDeman logon credentials.

It uses the Project ROME RSS Java library to retrieve the feed.

Whohar itself has a RSS feed reader, so you don't necessarily have to use the API to upload RSS entry data into Whohar. For this task, however, I wanted to post-process the data after retrieving it from Yahoo! and before uploading. Specifically, I wanted to parse the Job Title fields to derive the job location city and State.

Summary

The Polestar in the cloud and Whohar API has such ease-of-use that you can call it from almost any application. In this blog entry, I illustrated how you'd upload data and display it in Polestar using four different programming languages.

I'm sure you'll be able to think of much more compelling uses for it than the examples I've given above. If so, I highly recommend immediately going to the Polestar in the cloud API $20K IDEAtion Challenge site and signing yourself up for a chance to win some recognition for your idea! The cash would be a nice bonus, too...

Hope you find this entry informative, and the sample codes allow you to quickly get up-to-speed with the Polestar in the cloud API.

Ted Ueda is a Senior Engineer with Technical Customer Assurance, SAP Business Objects.

sap biw materials,sap biw certification materials,sap biw interview questions,sap biw trainings,sap biw online training,sap biw tutorials,sap biw soft

The game is officially on, and the time is short. On Thursday March 5th we officially launched our Polestar OnDemand Ideation challenge on Innocentive. Innocentive, a partner of the SAP Community Network is hosting this SAP sponsored challenge which will reward $20,000 to the developers of the best Polestar Ondemand mashups. We had originally budgeted 10K but at the last second we decided to go big and donate an extra 10K. This challenge will only last for 30 days, so don't delay register today.

For more about the contest and Polestar OnDemand, see my two previous posts.

Now in the grand scheme of things 20K isn't really a lot of money, but in today's world it's nothing to laugh at either. I think every developer who is considering entering the challenge first wants to know what kind of effort it is going to take to create a Polestar mashup. Well, it all really depends on your level of skill, but I believe we have made it as easy as we possibly could. With the resources that we have provided even a novice developer should be able to create a proof-of-concept within an hour.

The Polestar REST API focuses on two things: adding datasets, and viewing the datasets. There are a few more calls, but most apps will be concerned with sending data to Polestar to be viewed in the Polestar interface. To make it as easy as possible Ted Ueda from our Developer support team has created four code samples: Java, .NET, Python, and Flex. These sample apps should get you up and going very quickly. The documentation is also provided as a Wiki on SDN.

You have the samples and the documentation, now all you have to do is come up with an interesting idea. Here are some questions you might want to ask while brainstorming a possible solution. How can Polestar add value to particularly interesting dataset? How can adding Polestar to a current business workflow change the way we do business? You know I would really love to see a mashup with data from http://recovery.org, or http://recovery.gov, but allas they don't have API datasets yet, I think that would be really interesting personally. The world is not lacking for interesting datasets.

Really the hardest part about this challenge will be converting the external data into the http://create.ondemand.com data XML specification. It is no small thing but it should be solvable fairly simply.

We really want your feedback. Let us know what APIs you would like to see? What are we missing? How could we make this better?

Here's a list of resources that should prove useful to anyone investigating the challenge and the Polestar APIs

Helpful blog posts:

Robert Horne

How to transfer the Data from SAP-System to Non-SAP system without additional Cost/License

How to transfer the Data from SAP-System to Non-SAP system without additional Cost/License

Can we Transfer the Reports/Data from SAP-System to Non-SAP system without additional Cost/License?.

Yes, we can transfer using with simple FTP/SFTP.

Is it required any additional License or additional Cost?

No.

Is there any complex Coding or Configuration required?.

No, it is very simple.

How Can I transfer the Report/Data?.

Here I'm considering SAP-BW system running in UNIX operating system and Windows XP as Non-SAP System.

Source : SAP-BW System running on UNIX.

Destination : SQL-Server running on Windows XP.

Scenario:

I have data in SAP-BW System, e.g. InfoCube 0SD_C03. And 0SD_C03 is having some reports and want to transfer the reports data to SQL-Server running on Windows XP.

Else

You may need to transfer ECC Data to SQL-Server running on Windows XP. For this also you can use the same logic.

Here are the steps...

  1. Set the Variable values using fixed restrictions or Using SAP/Customer Exits variables in BW Report. (If you have any variables.)
  2. Using RSCRM_BAPI TCode schedule the report based on your requirement i.e. daily, weekly...etc. And then dump the report result into one path (Directory) in Application Server. E.g.:
    Directory: /usr/sap/BP1/DVEBMGS00/work/
    You can see this path in AL11 Tcode.
  3. Open the port in BW Application server.

  4. Create FTP/SFTP User ID in Application server level in BW System.
  5. Using Windows Script you call the Report in /usr/sap/BP1/DVEBMGS00/work/ path in BW system to SQL-Server running on Windows XP. This script you can save as a batch file and schedule it based on your requirement in Windows Server.

Sample Windows script:

image

Note: For better understanding or Changing of the above code, please contact your Windows system Administrator. This is just sample code only.

Here I’m using RSCRM_BAPI Tcode to dump the report result into Application Server. You can use your own method also. Our intension is how to transfer the data from SAP to Non-SAP System.

Surendra Reddy Surendra Kumar Reddy Koduru, is a SAP BI consultant currently working with ITC Infotech India Ltd , Bangalore, India. He has over 4+ years of experience in various BW/BI implementation/support projects.

SAP Skills Trends and Podcast - What Are Your Thoughts? Jon Reed

Hey folks - I wrote this blog entry to hopefully get some feedback from you on a recent podcast I hosted on SAP skills trends. (If you haven't heard it yet, you can click on this link to listen). But before we get into that, a bit of context:

I've been involved in some aspect of the SAP services market since 1995, and from what I have seen so far this year, this looks like the most difficult SAP consulting market I have seen to date. The challenge for SAP professionals seems to be the combination of unprecedented economic problems and the increase in offshoring of SAP skills needs - something I plan to cover in a podcast soon.

Of course, some would say that the benefit of such a downturn is that it provokes reform and innovation. I think most of us would agree that the classic SAP consulting model could use a hearty helping of both. But I do feel for the predicaments of folks who have been emailing me these days. I've been hearing from some senior consultants with pretty compelling skill sets, such as a senior FI/CO person, a senior SAP Logistics person, and a senior CRM Mobile specialist - just some quick snapshots of people who have never had as much difficulty finding new projects.

The good side, if there is one, is that the SAP market has not stopped in its tracks; there is still work to be found. There are two areas that always deserve exploration: one is skills expansion, and the second is marketing those skills. Though I would say that self-marketing is rapidly shifting into something much more useful and important: SAP community involvement. I wrote a piece on the keys to marketing yourself as an SAP consultant last summer that goes into self-marketing in more detail.

On the skills side, we can probably throw most of what we historically understood about SAP skills needs out the window and start afresh. We need new insights and practical tips on what skills are actually in use now. And we need feedback from those in the field on what they are seeing. That's a major reason why I agreed to participate in a recent SAP skills podcast that was coordinated by SAP's Ecosystem Workforce Group. This podcast was a joint venture with my site, JonERP.com, PAC, and K2 Partnering Solutions. We had folks on the podcast from different parts of the globe drawing on global research, so it really was an attempt to provide a big picture view of SAP skills demand now.

If you haven't checked out the podcast yet, or want to see a podcast timeline, check that out on Audrey Stevenson's blog. It's also on the University Alliance home page on SCN. I'm hoping that you'll check it out and share some comments with me on what you took from the podcast and if it resonates or conflicts with what you are seeing. One of the big themes of the podcast is the key to remaining marketable as an onsite consultant in the era of global outsourcing.

The skills needed to be a marketable onsite consultant and the so-called "SAP BPX skill set" seem to be more and more connected. For example, in the podcast, Peter Russo, who leads PAC's SAP Research Practice, talked about some shifts in SAP skills needs he is seeing and some new roles that are emerging. Peter talked about how this ties back into the topic of consultant quality, and bringing genuine value to customer sites. In that context, he noted the emergence of the "Business Solutions Architect"" a combination of technical and business skills, which is the result of the broadening of the SAP suite and the possibilities that SOA brings. As Peter defined it, the Business Solution Architect brings both business process know-how and technical expertise to the table. These folks are not necessarily doing hard core coding, but they are doing process orchestration and bringing management skills to the table as well.

Peter noted that this is a difficult skills profile for customers to develop - thus the need for an onsite consulting presence in this area. The Business Solutions Architect has an onsite relevance due to the importance of local experience, understanding the needs of the company in question, and having the industry know-how to properly advise such a customer. I don't know about you, but to me, this sounds a lot like the BPX skills profile we talk about frequently on this site. Another theme that Peter cited was the importance of Business Intelligence skills and the need for data transparency, competitive analysis, and KPI measurement. These skills also tie into the onsite consultant profile, and I'll be exploring this in further detail in future podcasts.

So with that said, I'm interested in hearing your feedback on these comments and/or on the themes of the podcast.

Jon Reed is the President of JonERP.com - he blogs and podcasts on SAP skills trends.