Using custom components - communication from client to server not working

Hi,

I am trying to use a custom developed gwt widget which shall be used within a vaadin based web ui. I have followed the description in the book of vaadin, which suggests to create a server class and a client class. So far, the client is recieving data from the server via the updateFromUIDL method, that is working fine. However, all attempts from the client sending data to the server are not working. To elaborate:

My client side class is extending the original gwt widget. In the original widget, there is a method stub, which is called whenever a “save button” is pressed by the user of the widget. The vaadin client class is overriding (and implemeting) the method stub of its parent class, that means it is sending the content to the server via the updateVariable() method. So from what I unterstand, whenever the user presses the “save button” the (implemented) method in the vaadin client class should be called, and subsequently, the changeVariables method of the server class should be called, at least theoretically. However that is not the case. I have added logging information to the server class, and my logs tell me the changeVariables method of the server class is never called.

I hope someone can help me with this problem and point me in the right direction.

My original GWT component:

public class ModelingToolWidget extends Composite implements
        HasAllMouseHandlers {

    private static Messages messages;

    private static HashMap<Long, String> users;

    private static ModelingToolWidget instance;

    public static ModelingToolWidget getInstance() {
        if (instance == null) {
            instance = new ModelingToolWidget();
        }
        return instance;
    }

    public ModelingToolWidget() {
        super();

        VerticalPanel panel = new VerticalPanel();

        // create menu
        Menu menu = new Menu(this);
        panel.add(menu);

        // create workflow and add to the root panel.
        Workflow workflow = Workflow.getInstance();
        panel.add(workflow);

        initWidget(panel);

        /* Internationalization: "Instantiate" the Message interface class. */
        messages = GWT.create(Messages.class);

    }

    public static Messages getMessages() {
        if (messages == null) {
            messages = GWT.create(Messages.class);
        }
        return messages;
    }

    public void setDWDL(String dwdl) {
        DWDLParser parser = new DWDLParserImpl();
        WorkflowModel workflowModel = parser.parse(dwdl);
        try {
            Command createWorkflowCmd = new CreateWorkflowCommand(workflowModel);
            createWorkflowCmd.execute();
        } catch (IncompleteModelDataException e) {
            Window.alert(e.getMessage());
        }
    }


    public void setUsers(String userxml) {
        users = new HashMap<Long, String>();

        Document doc = XMLParser.createDocument();
        doc = XMLParser.parse(userxml);
        NodeList list = doc.getElementsByTagName("user");

        for (int i = 0; i < list.getLength(); i++) {
            Element element = (Element) list.item(i);
            Long id = new Long(element.getAttribute("id"));
            String name = element.getAttribute("name");
            users.put(id, name);
        }
    }

    public HashMap<Long, String> getUsers() {
        return users;
    }

    public void sendDWDLtoServer(String dwdl) {
        /*
         * This method is intentionally left emtpy. The implementation is done
         * by the child class in the WebPortal.
         */
    }

Menu with the save Button

public class Menu extends MenuBar {

    public Menu(ModelingToolWidget modelingToolWidget) {
        // argument false = non vertical
        super(false);

        this.addStyleName("menu-std");

        MenuImageBundle imgBundle = GWT.create(MenuImageBundle.class);

        addItem(imgBundle.save().getHTML(), true, new SaveMenuItem(modelingToolWidget));


        addSeparator();

        addItem(imgBundle.email().getHTML(), true,
                new CreateEmailInvokeNodeMenuItem());
        addItem(imgBundle.humantask().getHTML(), true,
                new CreateHumanTaskInvokeNodeMenuItem());

        addSeparator();

        addItem(imgBundle.flowcontainer().getHTML(), true,
                new CreateFlowContainerMenuItem());
        addItem(imgBundle.ifcontainer().getHTML(), true,
                new CreateIfContainerMenuItem());
        addItem(imgBundle.foreachcontainer().getHTML(), true,
                new CreateForEachContainerMenuItem());

        addSeparator();

        /*
         * Message class needs to be created here, accessing the
         * ModelingToolWidget creates null pointer exceptions
         */
        Messages msg = GWT.create(Messages.class);
        addItem(msg.variablesMenuItem(), new VariablesMenuItem());
        addItem(msg.propertiesMenuItem(), new PropertiesMenuItem());
    }

}

The save button itself:

public class SaveMenuItem implements Command {

    private ModelingToolWidget modelingToolWidget;

    /**
     * TODO: add comment
     * 
     * @param modelingToolWidget
     */
    public SaveMenuItem(ModelingToolWidget modelingToolWidget) {
        super();
        this.modelingToolWidget = modelingToolWidget;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.google.gwt.user.client.Command#execute()
     */
    @Override
    public void execute() {
        /* Invoke parser and send resulting dwdl document to server */
        WorkflowParser parser = new WorkflowParserImpl();
        String dwdl = parser.parse(Workflow.getInstance().getModel());
        modelingToolWidget.sendDWDLtoServer(dwdl);
    }

}

My vaadin client class

public class VModelingTool extends
        de.decidr.modelingtool.client.ModelingToolWidget implements Paintable {


    /** Set the tagname used to statically resolve widget from UIDL. */
    public static final String TAGNAME = "modelingtool";

    /** Set the CSS class name to allow styling. */
    public static final String CLASSNAME = "v-" + TAGNAME;

    /** Component identifier in UIDL communications. */
    String uidlId;

    /** Reference to the server connection object. */
    ApplicationConnection client;

    /**
     * The constructor should first call super() to initialize the component and
     * then handle any initialization relevant to Vaadin.
     */
    public VModelingTool() {
        super();
        // This method call of the Paintable interface sets the component
        // style name in DOM tree
        setStyleName(CLASSNAME);
    }

    @Override
    public void sendDWDLtoServer(String dwdl) {

        // Updating the state to the server can not be done
        // before the server connection is known, i.e., before
        // updateFromUIDL() has been called.
        if ((uidlId == null) || (client == null)) {
            return;
        }

        // Communicate the user interaction parameters to server.
        // This call will initiate an AJAX request to the server.
        client.updateVariable(uidlId, "dwdl", dwdl, true);

    }

    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
        // This call should be made first. Ensure correct implementation,
        // and let the containing layout manage caption, etc.
        if (client.updateComponent(this, uidl, true)) {
            return;
        }

        // Save reference to server connection object to be able to send
        // user interaction later
        this.client = client;

        // Save the UIDL identifier for the component
        uidlId = uidl.getId();

        // Set the DWDL document received from server
        setDWDL(uidl.getStringVariable("dwdl"));

        // Set the user list received from server
        setUsers(uidl.getStringVariable("users"));
    }
}

My vaadin sever class

public class ModelingTool extends AbstractComponent {

    private Logger logger = DefaultLogger.getLogger(ModelingTool.class);

    private HttpSession session = null;
    private Long userId = null;
    private Long tenantId = null;
    private TenantFacade tenantFacade = null;
    private WorkflowModelFacade workflowModelFacade = null;
    private Long workflowModelId = null;
    private HashMap<Long, String> userList = null;

    /**
     * Default constructor which initializes the server side components which
     * are needed to gain access to the database.
     */
    public ModelingTool(Long workflowModelId) {
        super();
        session = Main.getCurrent().getSession();
        userId = (Long) session.getAttribute("userId");
        tenantId = (Long) Main.getCurrent().getSession().getAttribute(
                "tenantId");
        tenantFacade = new TenantFacade(new TenantAdminRole(userId));
        workflowModelFacade = new WorkflowModelFacade(new TenantAdminRole(
                userId));
        this.workflowModelId = workflowModelId;
        this.setSizeFull();
        this.setImmediate(true);
    }

    @Override
    public String getTag() {
        return "modelingtool";
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
     * java.util.Map)
     */
    @SuppressWarnings("unchecked")
    @Override
    public void changeVariables(Object source, Map variables) {
        logger.debug("[Modeling Tool]
 Trying to store the DWDL.");
        if (variables.containsKey("dwdl")) {
            byte[] dwdl = (byte[]
) variables.get("dwdl");
            try {
                workflowModelFacade.saveWorkflowModel(workflowModelId, "", "",
                        dwdl);
                logger.debug("[Modeling Tool]
 DWDL stored successfully.");
            } catch (TransactionException e) {
                Main.getCurrent().addWindow(
                        new TransactionErrorDialogComponent(e));
                logger.debug("[Modeling Tool]
 DWDL storing failed.");
            }
        }else {
            logger.debug("[Modeling Tool]
 Client variables did not contain a dwdl key.");
        }
    }

    private String convertUserHashMapToString(HashMap<Long, String> userList) {

        DocumentBuilder builder = null;
        try {
            builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            // TODO Auto-generated catch block
            // JS
            logger.debug("[Modeling Tool]
: Exception", e);
        }
        Document doc = builder.newDocument();
        Element root = doc.createElement("userlist");

        for (Long userId : userList.keySet()) {
            Element user = doc.createElement("user");
            user.setAttribute("id", userId.toString());
            user.setAttribute("name", userList.get(user));
            root.appendChild(user);
        }

        doc.appendChild(root);

        logger.debug(doc.toString());
        logger.debug(new DOMSource(doc).toString());

        return doc.toString();
    }

    private String getDWDL() {
        try {
            Item workflowModel = workflowModelFacade
                    .getWorkflowModel(workflowModelId);
            logger
                    .debug("[Modeling Tool]
 Retrieving dwdl document was successfull");
            // JS remove this line
            logger.debug("[Modeling Tool]
 DWDL: "
                    + new String((byte[]) workflowModel.getItemProperty("dwdl")
                            .getValue()));
            return new String((byte[]) workflowModel.getItemProperty("dwdl")
                    .getValue());
        } catch (TransactionException e) {
            Main.getCurrent().addWindow(new TransactionErrorDialogComponent(e));
            logger.debug("[Modeling Tool]
 Retrieving dwdl document failed");
            return null;
        }
    }

    private String getUsers() {
        userList = new HashMap<Long, String>();
        try {
            logger.debug("[Modeling Tool]
 Getting user list from server...");
            List<Item> users = tenantFacade.getUsersOfTenant(tenantId, null);
            for (Item item : users) {
                if (item.getItemProperty("username").getValue() == null
                        || item.getItemProperty("username").getValue().equals(
                                "")) {
                    /*
                     * If the user name is empty, we want to display the email
                     * address as user name.
                     */
                    userList.put((Long) item.getItemProperty("id").getValue(),
                            (String) item.getItemProperty("email").getValue());
                } else {
                    /*
                     * user name is not empty, but we want to set the user name
                     * to a more "fancy" string, for example: John Doe (jdoe42)
                     */
                    Long id = (Long) item.getItemProperty("id").getValue();
                    String username = (String) item.getItemProperty("username")
                            .getValue();
                    userList.put(id, username);
                }
            }
            logger.debug("[Modeling Tool]
 Succeded retrieving user list from server.");
            return convertUserHashMapToString(userList);
        } catch (TransactionException exception) {
            Main.getCurrent().addWindow(
                    new TransactionErrorDialogComponent(exception));
            logger.debug("[Modeling Tool]
 Failed retrieving user list from server.");
            return null;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.vaadin.ui.AbstractComponent#paintContent(com.vaadin.terminal.PaintTarget
     * )
     */
    @Override
    public void paintContent(PaintTarget target) throws PaintException {
        super.paintContent(target);
        target.addVariable(this, "dwdl", getDWDL());
        target.addVariable(this, "users", getUsers());
    }
}

Based on a quick look, I don’t see anything obviously wrong with your code.

I would suggest first making sure that the client side method in your subclass is actually called, and its execution completes without errors.

One way to see if the client tries to send a variable change message to the server is to open the debug console (add “?debug” to your application URL) and look for "Making UIDL Request with params: " in the debug window when the variable change should be sent. Also check the rest of the output on the debug console.

For client side component development and debugging, the hosted mode (especially OOPHM) is a useful tool. To set it up, see
OOPHM Step by Step
. While it takes a little effort to configure, it can save a lot of time later in development as you’ll have breakpoints and many other standard debugging tools available.

Henri