cancel
Showing results for 
Search instead for 
Did you mean: 

Stateful EJB in Web Dynpro

Former Member
0 Kudos

I hope this is the right forum for this question...my business problem is that I am trying to consume javax.* functionality from an EJB I have written. Everything is working as I expect except that I am confused on just how to ensure that for my Web Dynpro app there is exactly one instance of the bean per session.

My question is: I am obviously misunderstanding how to create / configure stateful EJBs and I would greatly appreciate some assistance based on the code I have below.

For the impatient, I have not found any articles that deal with Web Dynpro and stateful EJBs (other than the managed-bean stuff in the help, which I'm not sure applies here). If you know of any articles that explicitly cover integrating a stateful EJB with a Web Dynpro app that has multiple views then that's exactly what I'm looking for.

For those that made it here, I have a a few parts here...so I'll cover them in order.

1. Per documentation, I created an External Library DC to contain the javax components. This works just fine, so I won't go into detail.

2. Per documentation, I created a J2EE EJB Module DC and added the classes we'll see below. I followed the public parts / permissions instructions carefully, and the EJB itself is working just fine within the Web Dynpro. (Just not the stateful aspect.)

3. Per documentation, I created a J2EE Enterprise App DC and added the J2EE EJB Module DC (as well as the Assembly public part of the External Library DC created in step 1).

4. I created a Web Dynpro app and used the "Create Model" to import the EJB structure.

*The Code*

Since the Web Dynpro app is working fine apart from the stateful-ness, I'm not posting it here (nor the 73 steps required by SAP to fit the EJB in with this MVC thingie they foist on us). Suffice it to say that in the Web Dynpro app I do indeed:

a. Create the model from the EJB

b. Map my app controller context to the model (and set the collection cardinality to 1..1 for each of the referened Request objects such as "Request_Wsdl2Local_getServices").

c. In wdOnInit within the component controller, make the call to putDefaultInstance.

Obviously I'm doing those things right, or I wouldn't have any working calls to the EJB

So let's look at the EJB code / classes. I hope I'm just forgetting a declarative annotation (something on the order of "@YesReallyMakeThisWork" for my classes, Java and EJB seem a great example "by-default-your-wings-fall-off" development mentality).

Since the whole point of my EJB is to insulate the Web Dynpro app from javax functionality, I have a few classes to wrap the information I need so I can send back to SAP. I've attached them below and I would greatly appreciate any thoughts on what I'm missing. You can see below that I'm a fan of emitting log messages to keep up with what's going on. Here's the trace output from a run:


"Debug","2009-01-27","16:24:02:859",
  "We created WSDLFactory OK",
  "mil.army.escc.wsdl.Validator",
  "mil.army.escc.wsdl.Validator.validateWSDL",
  "mil.army.escc/adeociedashapp",
"Debug","2009-01-27","16:24:02:859",
  "We created WSDLReader OK",
  "mil.army.escc.wsdl.Validator",
  "mil.army.escc.wsdl.Validator.validateWSDL",
  "mil.army.escc/adeociedashapp",
"Debug","2009-01-27","16:24:02:859",
  "We set WSDL features OK",
  "mil.army.escc.wsdl.Validator",
  "mil.army.escc.wsdl.Validator.validateWSDL",
  "mil.army.escc/adeociedashapp",
"Debug","2009-01-27","16:24:02:953",
  "WSDL parsed without exception",
  "mil.army.escc.wsdl.Validator",
  "mil.army.escc.wsdl.Validator.validateWSDL",
  "mil.army.escc/adeociedashapp",
"Debug","2009-01-27","16:24:02:968",
  "Stored wsdlDef=Definition: name=null 
    targetNamespace=http://ade.army.mil/Services/OcieDashboard/",
  "mil.army.escc.wsdl.Validator",
  "mil.army.escc.wsdl.Validator.validateWSDL",
  "mil.army.escc/adeociedashapp",
"Error","2009-01-27","16:24:04:906",
  "Error accessing Web services in WSDL; check details",
  "/Applications/AdeOciedash",
  "mil.army.escc.wsdl.Validator.validateWSDL",
  "mil.army.escc/adeociedashapp",
"Fatal","2009-01-27","16:24:04:906",
  "Error at",
  "mil.army.escc.wsdl.Validator",
  "mil.army.escc.wsdl.Validator.validateWSDL",
  "mil.army.escc/adeociedashapp",

The last two lines have to do with a NULL pointer being found for the debug call in the getServices() method of Validator.java (the unfinished-looking entry "Error at" is because the flag variable is still empty at this point):


  LOCATION.debugT(METHOD, 
    "Have stored wsdlDef=" + wsdlDef.toString());

This is why I believe that although I think I should be stateful, I am really stateless in my call from the Web Dynpro app. Here's the failing call from the Web Dynpro side:


public void onPlugIn(
  com.sap.tc.webdynpro.progmodel.api.IWDCustomEvent wdEvent
)
{
  //@@begin onPlugIn(ServerEvent)
  String METHOD = "onPlugIn";
  if(wdContext.currentWsdlElement().getNewWsdl()) {
    try {
      // execute the stateful EJB to perform a WSDL validation and
      // then call the getServices method. this is failing--but
      // since the EJB is stateful, why is it failing?
        // get the WSDL text from the context
      String wsdlText = wdContext.currentWsdlElement().getText();
        // put in the parameter variable and invoke the validateWSDL method 
      wdContext.
        currentRequest_Wsdl2Local_validateWSDLElement().
        setWsdlText(wsdlText);
      wdContext.currentRequest_Wsdl2Local_validateWSDLElement().
        modelObject().execute();
        // invoke the getServices method from the same model
      wdContext.currentRequest_Wsdl2Local_getServicesElement().
        modelObject().execute();
      
      // reset the flag (we've consumed)
      wdContext.currentWsdlElement().setNewWsdl(false);
    } catch(EJBModelExecuteException ex) {
      IWDMessageManager mm = wdComponentAPI.getMessageManager();
      mm.reportException(
        this.getClass().getSimpleName() + "." + METHOD
          + ": failed retrieving Web Service names: " + ex.getMessage());
    } //try
  } //if
  //@@end
}

The remainder of this entry are just the classes I'm using on the EJB side.

*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*

*>>>Wsdl2Local.java -- The interface contract to share back with Web Dynpro.*


package mil.army.escc.wsdl;

import java.util.Collection;
import javax.ejb.Local;

/**
 * @author andy.d.bruce
 *
 */
@Local
public interface Wsdl2Local {
  /**
   * Validate a WSDL text 
   * @param wsdlText Text to validate
   * @return OK if validation was successful else error message
   */
  public String validateWSDL(String wsdlText);
  
  /**
   * Return list of Web services defined in WSDL
   * @return Typesafe list
   */
  public Collection<WrappedService> getServices(); 
}

*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*

*>>>Wsdl2Bean.java (implementation for the Wsdl2Local contract)*


package mil.army.escc.wsdl;

import java.util.Collection;
import javax.ejb.Stateful;
import com.sap.tc.logging.*;

/**
 * @author andy.d.bruce
 *
 */
@Stateful(name="Wsdl2Bean")
public class Wsdl2Bean implements Wsdl2Local {
  private static final Location LOCATION = 
    Location.getLocation(Wsdl2Bean.class);
  private static final Category CATEGORY = 
    Category.getCategory(Category.APPLICATIONS, "AdeOciedash");

  private Validator validator = new Validator();

  /// Stateful WSDL validation
  public String validateWSDL(String wsdlText) {
    String METHOD = "validateWSDL";
    String flag = "";
    try {
      validator.validateWSDL(wsdlText);
      return "OK";
    } catch( Throwable t) {
      CATEGORY.logT(
        Severity.ERROR, LOCATION, METHOD,
        "There was an error parsing the WSDL; please check trace.");
      LOCATION.traceThrowableT(
        Severity.FATAL, METHOD,
        "Error at {0}", new Object[] {flag}, t);
      return t.getMessage();
    } //try
  } // validateWSDL

  /// The validation object
  protected Validator getValidator() {
    return validator;
  }

  /// return list of javax.wsdl.Service objects to SAP
  public Collection<WrappedService> getServices() {
    return validator.getServices();
  }
}

*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*

*>> Validator.java - provides calls into javax stuff*


package mil.army.escc.wsdl;

import java.io.StringReader;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Map;

import javax.wsdl.Definition;
import javax.wsdl.Service;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;

import org.xml.sax.InputSource;

import com.sap.tc.logging.Category;
import com.sap.tc.logging.Location;
import com.sap.tc.logging.Severity;

public class Validator {
  private static final Location LOCATION = Location
      .getLocation(Validator.class);
  private static final Category CATEGORY = Category.getCategory(
      Category.APPLICATIONS, "AdeOciedash");

  private Definition wsdlDef;

  // / Create the object
  public Validator() {
  } // ctor

  // / Simple WSDL validation (saves result in context)
  public void validateWSDL(String wsdlText)
      throws mil.army.escc.wsdl.WSDLException,
      mil.army.escc.wsdl.WrappedWSDLException {
    String METHOD = "validateWSDL";
    String flag = "";
    try {
      flag = "check_sanity";
      if (wsdlText == null || wsdlText.length() == 0) {
        throw new mil.army.escc.wsdl.WSDLException(
            "The WSDL returned an empty definition");
      } // if

      flag = "create_WSDLFactory";
      WSDLFactory wsdlFactory = WSDLFactory.newInstance();
      LOCATION.debugT(METHOD, "We created WSDLFactory OK");

      flag = "create_WSDLReader";
      WSDLReader wsdlReader = wsdlFactory.newWSDLReader();
      LOCATION.debugT(METHOD, "We created WSDLReader OK");

      flag = "set_features";

      // no need for STDOUT, we're running in SAP anyways
      wsdlReader.setFeature("javax.wsdl.verbose", false);

      // yes, we are processing WSDL docs (otherwise I *think* that
      // just plain old XML validation would occur?)
      wsdlReader.setFeature("javax.wsdl.importDocuments", true);
      LOCATION.debugT(METHOD, "We set WSDL features OK");

      flag = "parse_wsdl";
      StringReader reader = new StringReader(wsdlText);
      org.xml.sax.InputSource inputSource = new InputSource(reader);
      Definition def = wsdlReader.readWSDL(null, inputSource);
      LOCATION.debugT(METHOD, "WSDL parsed without exception");
      if (def == null) {
        throw new mil.army.escc.wsdl.WSDLException(
            "The WSDL returned an empty definition");
      } // if

      // store to stateful variable
      wsdlDef = def;
      LOCATION.debugT(METHOD, "Stored wsdlDef=" + wsdlDef.toString());
    } catch (javax.wsdl.WSDLException ex) {
      CATEGORY.logT(Severity.ERROR, LOCATION, METHOD,
          "WSDL exception occurred; check details");
      LOCATION.traceThrowableT(Severity.FATAL, METHOD, "Error at {0}",
          new Object[] { flag }, ex);
      throw new mil.army.escc.wsdl.WrappedWSDLException(ex);
    } // try
  } // validateWSDL

  /// The created WSDL definition (initially NULL)
  public Definition getWsdlDef() {
    return wsdlDef;
  }

  /// The created WSDL definition (initially NULL)
  public void setWsdlDef(Definition value) {
    wsdlDef = value;
  }

  /// return a list of all the Web services in the current WSDL
  public Collection<WrappedService> getServices() {
    String METHOD = "validateWSDL";
    java.util.Collection<WrappedService> result = null;
    String flag = "";
    try {
      LOCATION.debugT(METHOD, "Have stored wsdlDef=" + wsdlDef.toString());
      flag = "access_all_services";
      Map map = getWsdlDef().getAllServices();
      flag = "convert_map_to_typesafe_list";
      result = new ArrayList<WrappedService>(map.size());
      for (Object key : map.keySet()) {
        Object value = map.get(key);
        if (value instanceof Service) {
          Service service = (Service) value;
          WrappedService wrappedService = new WrappedService(service);
          result.add(wrappedService);
          LOCATION.debugT(METHOD, "Stored service object {0}",
              new Object[] { service.getQName().toString() });
        } else {
          LOCATION.warningT(METHOD,
              "Stored object is not a Service object for {0}",
              new Object[] { key.toString() });
        } // if
      } // for
    } catch (Throwable t) {
      CATEGORY.logT(Severity.ERROR, LOCATION, METHOD,
          "Error accessing Web services in WSDL; check details");
      LOCATION.traceThrowableT(Severity.FATAL, METHOD, "Error at {0}",
          new Object[] { flag }, t);
    } finally {
      if (result == null)
        result = new ArrayList<WrappedService>();
    } // try
    return result;
  } //getServices
}

*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*

*>>>WrappedWSDLException.java (OK, this isn't really passed back but it could be...)*


package mil.army.escc.wsdl;

/// Wrapper for a javax.wsdl.WSDLException object (serialization) 
public class WrappedWSDLException extends Exception {
  private String faultCode ;
  private String location ;
  private String message ;

  /// create the control
  public WrappedWSDLException(javax.wsdl.WSDLException ex) {
    // copy what we can
    this.setFaultCode(ex.getFaultCode());
    this.setLocation(ex.getLocation());
    this.setMessage(ex.getMessage());
    this.setStackTrace(ex.getStackTrace());
  } //ctor

  /// The fault code from the javax.wsdl.WSDLException object
  public String getFaultCode() {
    return faultCode;
  }

/// The fault code from the javax.wsdl.WSDLException object
  public void setFaultCode(String faultCode) {
    this.faultCode = faultCode;
  }

  /// The location from the javax.wsdl.WSDLException object
  public String getLocation() {
    return location;
  }

  /// The location from the javax.wsdl.WSDLException object
  public void setLocation(String location) {
    this.location = location;
  }

  /// The message from the javax.wsdl.WSDLException object
  public String getMessage() {
    return message;
  }

  /// The message from the javax.wsdl.WSDLException object
  public void setMessage(String message) {
    this.message = message;
  }
} //WrappedWSDLException

*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*

*>>>WrappedQName.java*

package mil.army.escc.wsdl;


import javax.xml.namespace.QName;

/**
 * A "wrapped" javax.xml.namespace.QName object that can be returned to SAP
 * @author andy.d.bruce
 */
public final class WrappedQName {
  private String localPart;
  private String namespaceURI;
  private String prefix;
  private String origToString;
  
  /**
   * Create the QName
   * @param qname Original object
   */
  public WrappedQName(QName qname) {
    localPart = qname.getLocalPart();
    namespaceURI = qname.getNamespaceURI();
    prefix = qname.getPrefix();
    origToString = qname.toString();
  }

  /**
   * @return the localPart
   */
  public final String getLocalPart() {
    return localPart;
  }

  /**
   * @return the namespaceURI
   */
  public final String getNamespaceURI() {
    return namespaceURI;
  }

  /**
   * @return the prefix
   */
  public final String getPrefix() {
    return prefix;
  }
  
  @Override
  public String toString() {
    return origToString;
  }
}

*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*

*>>>WrappedService.java*


package mil.army.escc.wsdl;

import javax.wsdl.Service;

/**
 * A "wrapped" javax.wsdl.Service object safe to return to SAP
 * @author andy.d.bruce
 */
public final class WrappedService {
  private WrappedQName qName;
  private String origToString;

  /**
   * Create a "wrapped" service from javax.wsdl.Service
   * @param service
   */
  public WrappedService(Service service) {
    qName = new WrappedQName(service.getQName());
    origToString = service.toString();
  }

  /**
   * @return the qname
   */
  public final WrappedQName getQName() {
    return qName;
  }
  
  @Override
  public String toString() {
    return origToString;
  }
}

Edited by: ANDY BRUCE on Jan 27, 2009 10:49 PM

Accepted Solutions (0)

Answers (3)

Answers (3)

Former Member
0 Kudos

Moved to Web Dynpro...

Former Member
0 Kudos

Need to pull out the code...it's still actually answered

Former Member
0 Kudos

Hmm, no answers, just a few views--I guess either I explained this problem incorrectly or nobody has a clue on a solution. So just to fire things up (I'm a coder) I solved this problem in the worst possible way. Although we have stateful EJB, and although Web Dynpro offers full support for statefulness, I do have this problem on just how to create a stateful EJB. Since I can find nothing on how to do it (and obviously my approach outlined in the thread is completely wrong), I wrote my own stateful object.

Yup, baby, statics/locking and all. Don't forget the real possibility of memory leaks and really bad code You see, if we as developers get no support on a question and we have a deadline, then Ugly Solutions get coded. And the rest of you (meaning SAP in particular and third-party vendors who have to work in the AS Java environment) have to live with these terrible solutions. So not answering a question is really saying "I like to share my NetWeaver runtime production environment with crummy solutions from other unknown developers." Is that clear enough?

So here's the solution...(drumroll)...


/**
 * Beyond bogosity, mucking afazing. Having to write our own
 * stateful support since we heard nothing back from SAP or
 * other developers on the SAP SDN NetWeaver forums. Sheesh...
 */
package mil.army.escc.wsdl;

import java.util.Calendar;
import java.util.Date;
import java.util.UUID;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

//import com.sap.tc.logging.Category;
import com.sap.tc.logging.Location;

import mil.army.escc.wsdl.exceptions.*;

/**
 * @author andy.d.bruce
 *
 * Following the (almost undocumented) steps for integrating
 * stateful EJB within Web Dynpro didn't work. So we have to
 * hit the deadline...here's the bogus and ugly hack.
 */
public class CompletelyBogusSession {
  private static final Location LOCATION = Location
      .getLocation(Wsdl2Bean.class);
  /*
  private static final Category CATEGORY = Category.getCategory(
      Category.APPLICATIONS, "AdeOciedash");
   */
  private static Map<String, CompletelyBogusSession> s_sessions =
    new HashMap<String, CompletelyBogusSession>();
  private static Date lastOutdated = Calendar.getInstance().getTime();
  private Date lastTouched = Calendar.getInstance().getTime();
  private UUID uniqueId = UUID.randomUUID();
  private Map<String, Object> storedData = new HashMap<String, Object>();

  /**
   * Generate a new session
   * @return session
   */
  public static CompletelyBogusSession newSession() {
    String METHOD = "newSession";

    // remove any outdated
    cleanupSessions();

    // add a new (empty) session
    synchronized (s_sessions) {
      CompletelyBogusSession result = new CompletelyBogusSession();
      s_sessions.put(result.toString(), result);
      LOCATION.infoT(METHOD, "Created session {0}; now have {1} on file",
          new Object[] { result.toString(), s_sessions.size() });
      return result;
    }
  } //newSession

  /**
   * Access a saved session
   * @param key
   * @return
   * @throws SessionDoesNotExist
   */
  public static CompletelyBogusSession getSession(String key)
      throws SessionDoesNotExist {
    CompletelyBogusSession session = null;
    synchronized (s_sessions) {
      if (!s_sessions.containsKey(key)) {
        throw new SessionDoesNotExist(String.format(
            "The key %s does not exist", key));
      } //if
      session = s_sessions.get(key);
      session.lastTouched = Calendar.getInstance().getTime();
    } //synchronized

    // do this after releasing lock and updating touch status
    cleanupSessions();

    // our session
    return session;
  }

  /***
   * Cleanup any outdated sessions
   */
  public static void cleanupSessions() {
    String METHOD = "cleanupSessions";
    synchronized (s_sessions) {
      // do anything (only check every few minutes)
      Calendar timeout = Calendar.getInstance();
      timeout.setTime(lastOutdated);
      timeout.add(Calendar.MINUTE, 5);
      Calendar now = Calendar.getInstance();
      if (now.before(timeout)) {
        LOCATION.debugT(METHOD, "Ignoring outdating until at least {0}",
            new Object[] { timeout.getTime() });
        return;
      } //if
      LOCATION.debugT(METHOD, "Performing an outdate check");

      Vector<String> outdatedKeys = new Vector<String>();
      for (String key : s_sessions.keySet()) {
        // access session
        CompletelyBogusSession session = s_sessions.get(key);

        // generate timeout (30 minutes)
        timeout.setTime(session.lastTouched);
        timeout.add(Calendar.MINUTE, 30);

        // compare to right now
        if (now.after(timeout)) {
          LOCATION.infoT(METHOD, "Outdating session key {0}",
              new Object[] { key });
          outdatedKeys.add(key);
        } //if
      } //for

      // now remove the outdated keys (no longer in loop)
      if (outdatedKeys.size() > 0) {
        for (String key : outdatedKeys) {
          s_sessions.remove(key);
        } //for
        LOCATION.infoT(METHOD, "Outdating {0} sessions; {1} remain",
            new Object[] { outdatedKeys.size(), s_sessions.size() });
      } //if

      // finally, indicate that we are checked
      lastOutdated = Calendar.getInstance().getTime();
    } //synchronized
  }

  /**
   * Access a stored session object with type-safety 
   * @param key
   * @param type
   * @return
   * @throws NoDataForKey
   * @throws IncorrectDataType
   */
  public synchronized Object get(String key, java.lang.Class<?> type)
      throws NoDataForKey, IncorrectDataType {
    if (!storedData.containsKey(key)) {
      throw new NoDataForKey(String.format("Key %s does not exist in cache",
          key));
    } //if
    Object value = storedData.get(key);
    if (!(type.isInstance(value))) {
      throw new IncorrectDataType(String.format(
          "Key %s references value not of type %s", key, type.getName()));
    } //if
    return value;
  } //get

  /**
   * Set an object in the session
   * @param key
   * @param value
   */
  public synchronized void put(String key, Object value) {
    String METHOD = "setObject";
    if (storedData.containsKey(key)) {
      LOCATION.debugT(METHOD, "Updating existing session key {0}",
          new Object[] { key });
    } else {
      LOCATION.debugT(METHOD, "Adding new session key {0}",
          new Object[] { key });
    } //if
    storedData.put(key, value);
  } //put

  @Override
  public String toString() {
    return uniqueId.toString();
  }

  /**
   * @return the lastTouched
   */
  public final Date getLastTouched() {
    return lastTouched;
  }

  /**
   * @return the uniqueId
   */
  public final UUID getUniqueId() {
    return uniqueId;
  }
}

Former Member
0 Kudos

Hi Andy,

Before posting a rant, did you also consider another reason why you did not receive any replies? That is, this is posted in the wrong forum? As stated, the SAP NetWeaver Platform Forum:

>Strategic SAP NetWeaver© questions, as well as questions that span more than one SAP NetWeaver component, should be posted here

Perhaps you would have better results in the Web Dynpro Java Forum:

>Welcome to the Web Dynpro forum! While you develop applications using SAP Web Dynpro, you may have some questions about using the development environment. Please post your questions and answers here

Best Regards,

Matt

Former Member
0 Kudos

Ah, thank you so much, Matt. While my first sentence was whether I was in the right forum or not, I do appreciate the link to Web Dynpro and I will repost there.

Best,

Andy