Besides all the excellences, built-in WebDynpro OVS component has several drawbacks:
I guess first three statements are self-explanatory. Here are some comments to the latest statement.
You have probably noticed, there are two special classes per every RFC in Adaptive RFC model: one holds all input parameters (primitives, structures) plus table parameters; it has name RFC_NAME_Input. Second holds all output parameters (primitives, structures) plus tables; it is RFC_NAME_Output. Btw, input table parameter in RFC may activate bug in Adaptive RFC - same tables are repeated for output as well though they should not be there. The only "special" about these classes is that they have no corresponded STRUCTURE in ABAP. They are just logical "request" and "response".
Ok, but query in Adaptive RFC is ICMIQuery, and every ICMIQuery has "Result" property of type java.util.Collection. In ESF model this property is actually a collection (IAspect) that holds all result rows. So you can easily bind OVS over ESF query and... you are done!
However, in Adaptive RFC this collection contains only single element - the response. And typically you need to show some table from response. OVS + ICMIQuery does not help here. You have to use a pair of context nodes, bind input to one node, output to second. Binding output assumes that you track onQuery notification of IWDNotificationListener. If your input has complex structure (some input parameters are STRUCTURE in ABAP), then you have to collect all necessary attributes to one node, then copy back and force between node element and model object. Same is true for output. Btw, WDCopyService is of no help here.
Having done OVS+RFC value helps too many times, I finally lost my patience. There are should be a generic way! If it does not exist, then it should be developed!
You may find download of complete library here, below is just usage samples and snippets that outlines features.
So the main idea is to create wrapper that implements ICMQuery in quite specific way. To make description concrete, let us consider BAPI_FLIGHT_GETLIST as an example.
Now take a look at the code that adds OVS help to content attribute Input.Airline, showing departure/arrival city/country and airline/number of rows as filter and outputting flights list (airline, price, date/time) as result:
RFCValueHelp.bind( wdContext.getNodeInfo().getChild("Input").getAttribute("Airline"), RFCQueryClass.wrapQuery( new Bapi_Flight_Getlist_Input(), new Object[]{ RFCQuery.p("Destination_From.City").label("From city").info("Departure city"), RFCQuery.p("Destination_From.Countr").values(svsCountries), "Airline", // -------------------- RFCQuery.p("Destination_To.City").label("To city").info("Arrival city"), RFCQuery.p("Destination_To.Countr").values(svsCountries), "Max_Rows" }, "Flight_List", new String[]{"Airline", "Price", "Flightdate", "Deptime"} ), new WDOVSNotificationAdapter() { public void applyResult(final IWDNodeElement el, final Object row) { final Bapisfldat entry = (Bapisfldat)row; el.setAttributeValue("Airline", entry.getAirlineid() ); } } );
Actually, what you see is just sentence above code written in Java (rather then in bad English 😉
Pay attention, that you can either define field by it's path (String), or wrap it into ICCMIPropertyDef implementation with further customization.
Image 1. Airline value help with Flight_List RFC
If you plan to use number of separate queries of the same Java class, you may use more verbose form:
// Create wrapper "class" final RFCQueryClass rfcQueryClass = RFCQueryClass.compile ( Bapi_Flight_Getlist_Input.class, new Object[]{ RFCQuery.p("Destination_From.City").label("From city").info("Departure city"), RFCQuery.p("Destination_From.Countr").values(svsCountries), "Airline", // -------------------- RFCQuery.p("Destination_To.City").label("To city").info("Arrival city"), RFCQuery.p("Destination_To.Countr").values(svsCountries), "Max_Rows" }, "Flight_List", new String[]{"Airline", "Price", "Flightdate", "Deptime"} ); ... RFCValueHelp.bind( wdContext.getNodeInfo().getChild("Input").getAttribute("Airline"), rfcQueryClass.wrapQuery(new Bapi_Flight_Getlist_Input()), new WDOVSNotificationAdapter() { public void applyResult(final IWDNodeElement el, final Object row) { final Bapisfldat entry = (Bapisfldat)row; el.setAttributeValue("Airline", entry.getAirlineid() ); } } );
Use this only when you are creating several wrappers by separate queries, while this way you save processor time/memory on metadata construction. Actually, I rare see the case for this functionality: it is completely save to use same Adaptive RFC (and hence my wrapper) between several OVS extensions while OVS window is modal. Just do not forget to reset input structure in applyInput hook of IWDNotificationListener!
Having RFCQuery variable may give you additional benefits:
final RFCQueryClass rfcQueryClass = RFCQueryClass.compile(...) final RFCQuery rfcQuery = rfcQueryClass.wrapQuery( new Bapi_Flight_Getlist_Input() ); rfcQuery.setAttributeValueByPath( "Destination_From.Countr", "DE" ); rfcQuery.setAttributeValueByPath( "Destination_To.Countr", "US" ); rfcQuery.setAttributeValueByPath( "Max_Rows", new Integer(20) );
So you can attributes by path, rather then via traversing relations. If you still wondering whether this is useful, consider the fact that RFCQuery will automatically create related objects on first use; and path can be of any depth. Though, you can only refer paths that you use during class construction.
If you want to try Flight_List example, here is code that returns map of countries (quite naive, I know):
private Map countries() { final Locale appLocale = WDResourceHandler.getCurrentSessionLocale(); wdComponentAPI.getMessageManager().reportSuccess( "Current locale: " + appLocale.getDisplayName(appLocale) ); final Collator collator = Collator.getInstance(appLocale); final TreeMap countries = new TreeMap( new Comparator() { public int compare(final Object k1, final Object k2) { return collator.compare(k1, k2); } } ); final Locale[] locs = Locale.getAvailableLocales(); for (int i = 0; i < locs.length; i++) countries.put( countryName(locs[i], appLocale), locs[i].getCountry() ); final LinkedHashMap result = new LinkedHashMap(); for (final Iterator i = countries.entrySet().iterator(); i.hasNext(); ) { final Map.Entry e = (Map.Entry)i.next(); result.put( e.getValue(), e.getKey() ); } return result; } private String countryName(final Locale locale, final Locale targetLocale) { final String name = locale.getDisplayCountry(targetLocale); if (null == name || name.length() == 0) return locale.getDisplayCountry(Locale.ENGLISH); else return name; }
RFCQuery.p("Date_Range.Low").label("Date from") RFCQuery.p("Date_Range.High").label("Date from") ... new WDOVSNotificationAdapter() { public void applyInputValues(IWDNodeElement el, Object query) { final Bapi_Flight_Getlist_Input q = (Bapi_Flight_Getlist_Input)query; // THIS CALL CREATE ALL RELATED OBJECTS ON PATH rfcQuery.setAttributeByPath("Date_Range.Low", null); Bapisfldra range = (Bapisfldra)q.getDate_Range().get(0); range.setOption("BT"); range.setSign("I"); } public void applyResult(final IWDNodeElement el, final Object row) { /* Same as in example* / } }
Download current source code here, rename to *.zip after downloading.