2
votes

I will start by saying that:

  • I'm using ODA (godmode,khan,marcel).
  • I'm the only code signer.
  • sessionAsSigner is working the first time I load an XPage that calls it.
  • sessionAsSigner becomes null after I reload a page (cmd + R) but not when I'm subsequently referencing it in any action during in the context of the viewScope lifetime.
  • I'm implementing @Gidgerby 's concept of controller classes

I would also add that sessionAsSigner works consistently if I just prepare a simple XPage that does the following:

<p>
    <xp:text value="session: #{session.effectiveUserName}" />
</p>

<p>
    <xp:text value="sessionAsSigner: #{sessionAsSigner.effectiveUserName}" />
</p>

Now, I am not using SSJS. I'm JSF/EL oriented as much as I can, according to my current knowledge. So, the way I access Domino back-end is unconventional for a Domino XPages programmer.

Where I can't get getSessionAsSigner to work consistently is when I try to do the above mentioned thing...

Here is my XPage controller (backing bean):

 public class TestPageController extends StandardXPageController {

 private static final long serialVersionUID = 1L;

 private AnswerDTO answer;

 public TestPageController() {
    loadQuotation();
 }

 private void loadQuotation() {
     AnswerDAO answerDAO = Factory.createAnswerDAO();

     try {
        answer = answerDAO.read("doc_id");

     } catch (Exception e) {
        e.printStackTrace();
     }
 }

 public AnswerDTO getAnswer() {
     return answer;
 }
}

AnswerDTO is a POJO. I'm using the DAO/DTO design pattern. The AnswerDAO implementation - with simplified code (wrap method is just a mere mapping of fields) - as following:

public class AnswerDAODominoImpl implements AnswerDAO {

 private transient Session s;
 private transient Database db;

 private Session getSession() {
    if (s == null) {
        s = XSPUtil.getCurrentSessionAsSigner();
    }

    return s;
 }

 private Database getDatabase() throws NotesException {
    if (db == null) {
        db = getSession().getDatabase("server_name", "server_path");
    }

    return db;
 }

 public AnswerDTO read(String id) throws Exception {
    Database db = getDatabase();

    return wrap(db.getDocumentByID(id));
 }
}

This is the ViewHandler class:

public class ViewHandler extends ViewHandlerExImpl {

public ViewHandler(final javax.faces.application.ViewHandler delegate) {
    super(delegate);
}

@SuppressWarnings("unchecked")
public UIViewRoot createView(final FacesContext context, final String pageName) {
    try {
        // pageName is the XPage name prefixing the slash (e.g. "/home")
        String pageClassName = pageName.substring(1);
        Class<? extends XPageController> controllerClass = null;

        try {
            controllerClass = (Class<? extends XPageController>) context.getContextClassLoader().loadClass(
                    "com.sea.xsp.controller." + StringUtil.getProperCaseString(pageClassName) + "PageController");
        } catch (ClassNotFoundException cnfe) {
            controllerClass = StandardXPageController.class;
        }

        XPageController controller = controllerClass.newInstance();
        Map<String, Object> requestScope = (Map<String, Object>) context.getApplication().getVariableResolver().resolveVariable(context, "requestScope");
        requestScope.put("controller", controller);
        UIViewRootEx root = (UIViewRootEx) super.createView(context, pageName);
        root.getViewMap().put("controller", controller);
        requestScope.remove("controller");

        //          MethodBinding beforePageLoad = context.getApplication().createMethodBinding("#{controller.beforePageLoad}", new Class[] { PhaseEvent.class });
        //          root.setBeforePageLoad(beforePageLoad);

        return root;
    } catch (Exception e) {
        e.printStackTrace();
    }

    return super.createView(context, pageName);
}
}

Basically, what the viewhandler does is to check the existence of a java class which prefix is the XPage name itself.

eg. test.xsp = something.something.package.TestPageController

This approach allows me to forget about declaring specific XPage related classes as generic managed beans in the faces-config.xml All the XPages will get an easy handle to their corresponding backing bean that will always be named #{controller}

Now, having that being said if I simply write the following in an XPage, everything will work the first time, but not a second time (getSession() is OK, getSessionAsSigner is null), never ever again. I need to push a new change to the database (design update after any change to the java code and xsp.application.forcefullrefresh=true) and it will work again, but, again, just the first time the page is loaded.

<p>
    <xp:text value="answer doc id: #{controller.answer.id}" />
</p>

Ideas?

2

2 Answers

6
votes

This is possibly due to a bug we discovered a bit ago with the XPages runtime, somehow related to ClassLoader#loadClass. It turns out that using that as you do (and as I used to) can cause sessionAsSigner to stop working after the first page load. The fix is to switch to Class.forName(String, true, ClassLoader): https://github.com/jesse-gallagher/XPages-Scaffolding/commit/d65320fd6d98ff2fbaa814a95eb38ce7bad5a81d

0
votes

What happens if you run through in Java debugger? Is it going into XSPUtil.getSessionAsSigner() the second time round?

I tend to use ExtLibUtil.resolveVariable() to get a handle on sessionAsSigner if godmode is enabled. Alternatively, there's org.openntf.domino.utils.Factory.getSessionAsSigner(). Do those give a different result?

(In RC3 that is still be available but is marked deprecated in favour of Factory.getSession(SessionType.SIGNER) because we're supporting implementations outside XPages, so there are more session types involved.)