Search code examples
javaspringnullpointerexceptionapplicationcontextfxmlloader

FXMLLoader not injecting fx:id matching members when using Spring application context


I am using Spring to manage my controller instances using setControllerFactory (see below), to enable the controller to care for the persistence of the entities by autowiring a EntityManagerFactory and handling of database transactions. This approach works fine with a couple of other controllers I developed so far. However, lately I wanted to extend the functionality and copied an existing working controller scaffold, adopted it to a different Entity with the according FXML UI. For whatever reason I can’t make it work. What happens is that the controller gets instantiated and the controllers initialize method is called without any of the declared FMXL members being injected upfront – I debugged it, they are all null. Therefore initialize throws a NullPointerException. If I comment the setControllerFactory line out and all persistent related commands, it works fine, but I have no persitence. I spent an hour to compare the working examples with the new one but can’t find the root cause.

Please find following the code snippets

The entity class

@Entity
@Inheritance
@Access(AccessType.FIELD)
public abstract class Term implements Serializable {

    @Transient
    private transient final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final long serialVersionUID = -5585030750290575696L;

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;    

    private String identifier;  
    private String goal = "tbd.";       // what shall be achieved in the given time frame
    @OneToOne
    private Term predecessor;
    @OneToOne
    private Term successor;
    private int myNumber; // order within release

    private int version;
    private LocalDateTime publishingDate;
    private String owner;

    /*
     * Note: Use Period.between(begin, end).getUnits() to determine the years, months, days, hours between begin and end
     */
    private LocalDate begin;
    private LocalDate end;
    @Transient
    private transient DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(Locale.GERMAN);

    /*
     * Terms are structured in hierarchical levels as defined by the following finals
     */
    public final static String RELEASE = "Release"; // top level
    public final static String PHASE = "Phase"; // 
    public final static String BASELINE = "Baseline";
    public final static String ITERATION = "Iteration";
    public final static String TASK = "Task";   // bottom level
    @Transient
    public final static transient ArrayList<String> levels = 
    new ArrayList<String>(Arrays.asList(RELEASE, PHASE, BASELINE, ITERATION, TASK));

    @Transient
    public transient static ObservableList<String> chronoUnits =  FXCollections.observableArrayList(
            EffortEstimate.YEARS, EffortEstimate.MONTHS, EffortEstimate.WEEKS, EffortEstimate.DAYS, EffortEstimate.HOURS);

    public abstract String getLevel(); // return one of the above values indicating the hierarchical level
    protected String defaultTimeUnit;


    /*
     * A Term may be broken down into sub-Terms, called children. A child can not be a Term from a higher hierarchical level.
     * Between parent and child is a bi-directional relationship
     */
    @ManyToOne
    private Term parent;

    @Transient
    private List<Term> children;
    @Transient
    private transient ObservableList<Term> observableChildren = FXCollections.observableArrayList();    // an observable mirror of field children

    /*
     * If a requirement should be implemented during a certain time, but it is unclear by which team, then the
     * requirement may be temporarily assigned to a term. 
     */
    @Transient
    private List<UserRequirement> userRequirements;
    @Transient
    private transient final ListProperty<UserRequirement> observableRequirements = new SimpleListProperty<>(FXCollections.observableArrayList());



    public enum Type {
        Development, Hardening
    }
    @Enumerated(EnumType.STRING)
    protected Type myType;  // see enum Type

    public enum Status {
        Setup, Upcoming, Running, Complete
    }
    @Enumerated(EnumType.STRING)
    protected Status myState;   // see enum Status



    protected Term() {
        children = FXCollections.observableArrayList();
        userRequirements = FXCollections.observableArrayList();
        publishingDate = LocalDateTime.now();
        myState = Status.Setup;
        myType = Type.Development;
    }

    public Term(Term parent, String identifier) {
        this();
        setIdentifier(identifier);
        setParent(parent);
    }

    private Long getId() {
        return id;
    }

    private void setId(Long id) {
        this.id = id;
    }


    public Term getParent() {
        return parent;
    }

    public void setParent(Term parent) {
        this.parent = parent;
    }

    public ObservableList<Term> getObservableChildren() {
        return observableChildren;
    }

    @Access(AccessType.PROPERTY)
    @OneToMany(mappedBy="parent", orphanRemoval = true, cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
    private List<Term> getChildren() {
        return children;
    }

    @SuppressWarnings("unused")     // Used by JPA
    private void setChildren(List<Term> children) {
        this.children = children;
        observableChildren.setAll(FXCollections.observableArrayList(children));
    }

    public void addChild(Term child) {
        children.add(child);
        observableChildren.add(child);
        Term currentParent = child.getParent();
        if(currentParent != null) {
            currentParent.delChild(child);
        }
        child.setParent(this);
    }

    public boolean delChild(Term child) {
        child.setParent(null);
        observableChildren.remove(child);

        return children.remove(child);  
    }


    public String getIdentifier() {
        return identifier;
    }

    public void setIdentifier(String identifier) {
        this.identifier = identifier;
    }

    public String getGoal() {
        return goal;
    }

    public void setGoal(String goal) {
        this.goal = goal;
    }

    public Term getPredecessor() {
        return predecessor;
    }

    public void setPredecessor(Term predecessor) {
        this.predecessor = predecessor;
    }

    public Term getSuccessor() {
        return successor;
    }

    public void setSuccessor(Term successor) {
        this.successor = successor;
    }

    public LocalDate getBegin() {
        return begin;
    }

    public void setBegin(LocalDate begin) {
        this.begin = begin;
    }

    public LocalDate getEnd() {
        return end;
    }

    public void setEnd(LocalDate end) {
        this.end = end;
    }

    public String getDefaultTimeUnit() {
        return defaultTimeUnit;
    }

    public void setDefaultTimeUnit(String defaultTimeUnit) {
        this.defaultTimeUnit = defaultTimeUnit;
    }


    public ObservableList<UserRequirement> getObservableRequirements() {    
        return observableRequirements;
    }


    /*
    public ListProperty<UserRequirement> observableRequirementsProperty() {
        return observableRequirements;
    }
    */

    @Access(AccessType.PROPERTY)
    @OneToMany(mappedBy="term", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    private List<UserRequirement> getRequirements() {
        return userRequirements;
    }

    @SuppressWarnings("unused")     // Used by JPA
    private void setRequirements(List<UserRequirement> userRequirements) {

        //logger.debug(userRequirements.size() + " reqs to " + this.debug());

        this.userRequirements = userRequirements;
        if(userRequirements != null) {
            observableRequirements.clear();
            for(UserRequirement req: userRequirements) {
                observableRequirements.add(req);
            }
        }
        observableRequirements.setAll(FXCollections.observableList(userRequirements)); 
    }

    public void addRequirement(UserRequirement userRequirement) {

        logger.debug(userRequirement.debug() + " to " + this.debug());

        userRequirements.add(userRequirement);
        observableRequirements.add(userRequirement);
        userRequirement.setTerm(this);
    }

    public void delRequirement(UserRequirement userRequirement) {

        logger.debug(userRequirement.debug() + " from " + this.debug());

        if(userRequirements.contains(userRequirement)) {
            if(!userRequirements.remove(userRequirement)) {
                logger.error("Removal of requirement from list failed! Term: " + this.toString());
            }
            if(!observableRequirements.remove(userRequirement)) {
                logger.error("Removal of requirement from observable list failed! Term: " + this.toString());
            }

            userRequirement.setTerm(null);
        }
    }


    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }


    public LocalDateTime getPublishingDate() {
        return publishingDate;
    }

    public void setPublishingDate(LocalDateTime publishingDate) {
        this.publishingDate = publishingDate;
    }

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }



    public List<String> getAllowedChildClasses() {
        List<String> list = new ArrayList<String>(levels.subList(levels.indexOf(getLevel())+1, levels.size()));
        //list.set(0, "Sub-" + list.get(0));

        return list;
    }

    public int getMyNumber() {
        return myNumber;
    }

    public void setMyNumber(int myNumber) {
        this.myNumber = myNumber;
    }

    public Type getMyType() {
        return myType;
    }

    public void setMyType(Type myType) {
        this.myType = myType;
    }

    public Status getMyState() {
        return myState;
    }

    public void setMyState(Status myState) {
        this.myState = myState;
    }

    @Override
    public String toString() {
        String beginString = begin == null? "tbd." : begin.format(formatter);
        String endString = end == null? "tbd." : end.format(formatter);

        return String.format("%s-%s: %s > %s", beginString, endString, identifier, goal);
    }

    public String debug() {
        String tmp = getRequirements() == null ? "0" : Integer.toString(getRequirements().size());
        String txt = getId() + "@" + getLevel() + ": " + toString() + "; " + tmp + " userRequirements; " + getChildren().size() + " children";
        if(getParent() == null) {
            txt += " as root element";
        } else {
            txt += " owned by " + getParent().getId();
        }
        return txt;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        //result = prime * result + version;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Term other = (Term) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        /*
        if (version != other.version)
            return false;
         */
        return true;
    }


}

The FXML

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>

<GridPane fx:id="gridPane" hgap="10.0" maxHeight="1.7976931348623157E308"
    maxWidth="1.7976931348623157E308" stylesheets="@../stylesheets/controller.css"
    vgap="10.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1"
    fx:controller="com.agiletunes.controllers.terms.TermEditorCtrl">

    <columnConstraints>
        <ColumnConstraints maxWidth="1.7976931348623157E308" />
        <ColumnConstraints maxWidth="1.7976931348623157E308"
            minWidth="10.0" />
        <ColumnConstraints minWidth="10.0" />
        <ColumnConstraints maxWidth="1.7976931348623157E308"
            minWidth="10.0" />
    </columnConstraints>
    <rowConstraints>
        <RowConstraints minHeight="10.0" />
        <RowConstraints minHeight="10.0" />
        <RowConstraints minHeight="10.0" />
        <RowConstraints minHeight="10.0" />
        <RowConstraints />
        <RowConstraints minHeight="10.0" />
        <RowConstraints minHeight="10.0" />
        <RowConstraints minHeight="10.0" />
        <RowConstraints minHeight="10.0" />
        <RowConstraints minHeight="10.0" />
    </rowConstraints>
    <children>
        <DatePicker fx:id="beginDatePicker" onAction="#beginChanged"
            GridPane.columnIndex="1" GridPane.rowIndex="4">
            <GridPane.margin>
                <Insets />
            </GridPane.margin>
        </DatePicker>
        <DatePicker fx:id="endDatePicker" onAction="#endChanged"
            GridPane.columnIndex="3" GridPane.rowIndex="4">
            <GridPane.margin>
                <Insets right="24.0" />
            </GridPane.margin>
        </DatePicker>
        <Label text="Begin" GridPane.halignment="LEFT"
            GridPane.rowIndex="4">
            <GridPane.margin>
                <Insets left="24.0" />
            </GridPane.margin>
        </Label>
        <Label text="End" GridPane.columnIndex="2" GridPane.halignment="LEFT"
            GridPane.rowIndex="4">
            <GridPane.margin>
                <Insets left="12.0" right="8.0" />
            </GridPane.margin>
        </Label>
        <Label maxWidth="1.7976931348623157E308" text="Identifier"
            GridPane.hgrow="ALWAYS" GridPane.rowIndex="2">
            <GridPane.margin>
                <Insets left="24.0" right="12.0" top="24.0" />
            </GridPane.margin>
        </Label>
        <TextField fx:id="identifierTextField" onAction="#identifierChanged"
            onMouseExited="#identifierChanged" GridPane.columnIndex="1"
            GridPane.columnSpan="2147483647" GridPane.rowIndex="2">
            <GridPane.margin>
                <Insets right="24.0" top="24.0" />
            </GridPane.margin>
        </TextField>
        <Label layoutX="34.0" layoutY="80.0" maxWidth="1.7976931348623157E308"
            text="Level" GridPane.rowIndex="5">
            <GridPane.margin>
                <Insets left="24.0" />
            </GridPane.margin>
        </Label>
        <ComboBox fx:id="timeUnitComboBox" maxWidth="1.7976931348623157E308"
            onAction="#timeUnitChanged" GridPane.columnIndex="3" GridPane.hgrow="ALWAYS"
            GridPane.rowIndex="5">
            <GridPane.margin>
                <Insets right="24.0" />
            </GridPane.margin>
        </ComboBox>
        <Separator prefWidth="200.0" GridPane.columnSpan="2147483647"
            GridPane.rowIndex="7" />
        <Label layoutX="34.0" layoutY="39.0" text="Goal"
            GridPane.rowIndex="3">
            <GridPane.margin>
                <Insets left="24.0" />
            </GridPane.margin>
        </Label>
        <TextField fx:id="goalTextField" layoutX="116.0" layoutY="34.0"
            onAction="#goalChanged" onKeyReleased="#goalChanged" onMouseExited="#goalChanged"
            GridPane.columnIndex="1" GridPane.columnSpan="2147483647"
            GridPane.rowIndex="3">
            <GridPane.margin>
                <Insets right="24.0" />
            </GridPane.margin>
        </TextField>
        <Label layoutX="34.0" layoutY="162.0" text="Time unit"
            GridPane.columnIndex="2" GridPane.halignment="RIGHT"
            GridPane.rowIndex="5">
            <GridPane.margin>
                <Insets left="12.0" right="8.0" />
            </GridPane.margin>
        </Label>
        <Label layoutX="34.0" layoutY="162.0" maxWidth="1.7976931348623157E308"
            text="Type" GridPane.rowIndex="6">
            <GridPane.margin>
                <Insets left="24.0" />
            </GridPane.margin>
        </Label>
        <ComboBox fx:id="typeComboBox" layoutX="116.0" layoutY="157.0"
            maxWidth="1.7976931348623157E308" onAction="#typeChanged"
            GridPane.columnIndex="1" GridPane.rowIndex="6" />
        <Label layoutX="391.0" layoutY="162.0" text="State"
            GridPane.columnIndex="2" GridPane.halignment="LEFT"
            GridPane.rowIndex="6">
            <GridPane.margin>
                <Insets left="12.0" right="8.0" />
            </GridPane.margin>
        </Label>
        <ComboBox fx:id="stateComboBox" layoutX="464.0" layoutY="157.0"
            maxWidth="1.7976931348623157E308" onAction="#stateChanged"
            GridPane.columnIndex="3" GridPane.rowIndex="6">
            <GridPane.margin>
                <Insets right="24.0" />
            </GridPane.margin>
        </ComboBox>
        <Separator prefWidth="200.0" GridPane.columnSpan="2147483647"
            GridPane.rowIndex="1" />
        <Label layoutX="34.0" layoutY="99.0" maxWidth="1.7976931348623157E308"
            text="Parent">
            <GridPane.margin>
                <Insets left="24.0" top="24.0" />
            </GridPane.margin>
        </Label>
        <Hyperlink fx:id="parentHyperlink" onMouseClicked="#parentSelected"
            text="Hyperlink" GridPane.columnIndex="1" GridPane.columnSpan="2147483647">
            <GridPane.margin>
                <Insets right="24.0" top="24.0" />
            </GridPane.margin>
        </Hyperlink>
        <ListView fx:id="subTermListView" onMouseClicked="#childSelected"
            prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1"
            GridPane.columnSpan="2147483647" GridPane.rowIndex="8">
            <GridPane.margin>
                <Insets bottom="24.0" right="24.0" />
            </GridPane.margin>
        </ListView>
        <Label layoutX="34.0" layoutY="38.0" maxWidth="1.7976931348623157E308"
            text="Sub-terms" GridPane.hgrow="ALWAYS" GridPane.rowIndex="8"
            GridPane.valignment="TOP">
            <GridPane.margin>
                <Insets left="24.0" />
            </GridPane.margin>
        </Label>
        <Label fx:id="levelLabel" text="Label" GridPane.columnIndex="1"
            GridPane.rowIndex="5" />
    </children>
</GridPane>

The Controller class:

    ..

    @Controller
    public class TermEditorCtrl implements Initializable {

    @FXML    private DatePicker beginDatePicker;
    @FXML    private DatePicker endDatePicker;
    @FXML    private TextField identifierTextField;
    @FXML    private ComboBox<String> timeUnitComboBox;
    @FXML    private TextField goalTextField;
    @FXML    private ComboBox<Term.Type> typeComboBox;
    @FXML    private ComboBox<Term.Status> stateComboBox;
    @FXML   private ListView<Term> subTermListView;
    @FXML   private Label levelLabel;
    @FXML   private Hyperlink parentHyperlink;

    @Autowired private TermService termService;
    @Autowired private EntityManagerFactory emf;
    protected EntityManager entityManager;
    protected EntityTransaction transaction;


    protected boolean isDirty;

    protected Term myTerm;


    public TermEditorCtrl() {

    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        typeComboBox.setItems(FXCollections.observableArrayList(Term.Type.values()));
        stateComboBox.setItems(FXCollections.observableArrayList(Term.Status.values()));
        timeUnitComboBox.setItems(FXCollections.observableArrayList(Term.chronoUnits));
    }

    @Transactional(propagation=Propagation.REQUIRED)
    protected void saveIfNeeded() {
        if(transaction != null) {
            if(isDirty) {
                Alert alert = new Alert(AlertType.CONFIRMATION, "Otherwise all changes will be lost\n\n", ButtonType.YES, ButtonType.NO);
                alert.setTitle("Requirement changed");
                alert.setHeaderText("Save changes?");

                Optional<ButtonType> result = alert.showAndWait();
                if (result.get() == ButtonType.YES){
                    termService.save(myTerm);
                    transaction.commit();
                } 
            } else {
                transaction.rollback();
                transaction = null;
            }   
        }
    }

    @Transactional(propagation=Propagation.REQUIRED)
    public void setTerm(Term aTerm) {
        saveIfNeeded();
        myTerm = aTerm;

        entityManager = emf.createEntityManager();
        transaction = entityManager.getTransaction();
        transaction.begin();

        identifierTextField.setText(aTerm.getIdentifier());
        beginDatePicker.setValue(aTerm.getBegin());
        endDatePicker.setValue(aTerm.getEnd());
        goalTextField.setText(aTerm.getGoal());
        stateComboBox.getSelectionModel().select(aTerm.getMyState());
        typeComboBox.getSelectionModel().select(aTerm.getMyType());
        levelLabel.setText(aTerm.getLevel());
        subTermListView.setItems(aTerm.getObservableChildren());
        timeUnitComboBox.getSelectionModel().select(aTerm.getDefaultTimeUnit());
        Term parent = aTerm.getParent();
        if (parent != null) {
            parentHyperlink.setText(parent.getIdentifier());
        } else {
            parentHyperlink.setText("");
        }

        isDirty = false;

    }

    @FXML
    void beginChanged(ActionEvent event) {
        myTerm.setBegin(beginDatePicker.getValue());
        isDirty = true;
    }

    @FXML
    void endChanged(ActionEvent event) {
        myTerm.setEnd(endDatePicker.getValue());
        isDirty = true;
    }

    @FXML
    void goalChanged(KeyEvent event) {
        myTerm.setGoal(goalTextField.getText());
        isDirty = true;
    }

    @FXML
    void identifierChanged(ActionEvent event) {
        myTerm.setIdentifier(identifierTextField.getText());
        isDirty = true;
    }


    @FXML
    void stateChanged(ActionEvent event) {
        myTerm.setMyState(stateComboBox.getSelectionModel().getSelectedItem());
        isDirty = true;
    }

    @FXML
    void childSelected(MouseEvent event) {
        setTerm(subTermListView.getSelectionModel().getSelectedItem());
    }

    @FXML
    void parentSelected(MouseEvent event) {
        setTerm(myTerm.getParent());
    }


    @FXML
    void timeUnitChanged(ActionEvent event) {
        myTerm.setDefaultTimeUnit(timeUnitComboBox.getSelectionModel().getSelectedItem());
        isDirty = true;
    }

    @FXML
    void typeChanged(ActionEvent event) {
        myTerm.setMyType(typeComboBox.getSelectionModel().getSelectedItem());
        isDirty = true;
    }

    public void setStage(Stage aStage) {
        aStage.setOnCloseRequest(event -> {
            saveIfNeeded();
        });
    }
}

The code that loads the FXML UI and the controller when a Term object is selected in a ListView

private ContextMenu getContextMenu(Term term) {
            ContextMenu cm = new ContextMenu();

            MenuItem editItem = new MenuItem("Edit");
            cm.getItems().add(editItem);
            editItem.setOnAction(event -> {
                try{
                    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/TermEditor.fxml"));
                    fxmlLoader.setControllerFactory(c -> App.springContext.getBean(c));  //  lookup the controller from the spring application context
                    Parent parent = fxmlLoader.load();
                    TermEditorCtrl termCtrl = fxmlLoader.getController();

                    Stage stage = new Stage();
                    Scene scene = new Scene(parent);
                    stage.setScene(scene);
                    stage.show();

                    termCtrl.setStage(stage);
                    termCtrl.setTerm(term);
                }
                catch(IOException e){
                    logger.debug(e.getLocalizedMessage());
                    e.printStackTrace();
                }
            });

The error message

javafx.fxml.LoadException: 
/C:/Users/Alexander/Documents/agiletunes-codespace/agileTunes-implementation/target/classes/fxml/TermEditor.fxml

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2579)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2409)
    at com.agiletunes.controllers.requirement.PlanningPaneCtrl$TermTreeCell.lambda$0(PlanningPaneCtrl.java:674)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.control.MenuItem.fire(MenuItem.java:462)
    at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer.doSelect(ContextMenuContent.java:1405)
    at com.sun.javafx.scene.control.skin.ContextMenuContent$MenuItemContainer.lambda$createChildren$343(ContextMenuContent.java:1358)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:381)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$354(GlassViewEventHandler.java:417)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:416)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NullPointerException
    at com.agiletunes.controllers.terms.TermEditorCtrl.initialize(TermEditorCtrl.java:68)
    at com.agiletunes.controllers.terms.TermEditorCtrl$$FastClassBySpringCGLIB$$1861d384.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:669)
    at com.agiletunes.controllers.terms.TermEditorCtrl$$EnhancerBySpringCGLIB$$bd8c26eb.initialize(<generated>)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2548)
    ... 45 more

Please let me know should you need additional information, code or alike

Thank you very much in advance for any help.


Solution

  • I debuged this further. Eventually I found the root cause in canApply(Pointcut pc, Class targetClass, boolean hasIntroductions) in AopUtils. The problem is that the model of the controller is set, calling the setTerm method. The parameter of setTerm is a Term object and Term is an abstract class. Spring has obviously a problem handling abstract parameters. I replaced the setTerm method with 5 different setter methods, for each concrete Term extensions one. Now it works.

    I wonder whether this is a bug or a feature, since the intercept method in CglibAopProxy gets also a proxy object with all FXML variables set?!