org.hippoecm.frontend.plugins.cms.admin.updater.UpdaterEditor.java Source code

Java tutorial

Introduction

Here is the source code for org.hippoecm.frontend.plugins.cms.admin.updater.UpdaterEditor.java

Source

/**
 * Copyright 2012-2015 Hippo B.V. (http://www.onehippo.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.hippoecm.frontend.plugins.cms.admin.updater;

import java.util.HashMap;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.Query;

import org.apache.commons.lang.StringUtils;
import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.feedback.ContainerFeedbackMessageFilter;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.RadioGroup;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.StringResourceModel;
import org.hippoecm.frontend.dialog.HippoForm;
import org.hippoecm.frontend.dialog.IDialogService;
import org.hippoecm.frontend.model.JcrNodeModel;
import org.hippoecm.frontend.plugin.IPluginContext;
import org.hippoecm.frontend.plugins.cms.codemirror.CodeMirrorEditor;
import org.hippoecm.frontend.session.UserSession;
import org.hippoecm.repository.api.HippoNodeType;
import org.hippoecm.repository.util.JcrUtils;
import org.hippoecm.repository.util.RepoUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sf.json.JSONException;
import net.sf.json.JSONObject;

public class UpdaterEditor extends Panel {

    private static final long serialVersionUID = 1L;

    protected final static Logger log = LoggerFactory.getLogger(UpdaterEditor.class);

    protected static final String UPDATE_PATH = "/hippo:configuration/hippo:update";
    protected static final String UPDATE_QUEUE_PATH = UPDATE_PATH + "/hippo:queue";
    protected static final String UPDATE_REGISTRY_PATH = UPDATE_PATH + "/hippo:registry";
    protected static final String UPDATE_HISTORY_PATH = UPDATE_PATH + "/hippo:history";

    private static final long DEFAULT_BATCH_SIZE = 10l;
    private static final long DEFAULT_THOTTLE = 1000l;
    private static final String DEFAULT_METHOD = "path";

    protected final IPluginContext context;
    protected final Panel container;
    protected final Form form;
    protected final FeedbackPanel feedback;
    protected String script;

    protected String name;
    protected String description;
    protected String visitorPath;
    protected String visitorQuery;
    protected String method = DEFAULT_METHOD;
    protected String parameters;
    protected String batchSize = String.valueOf(DEFAULT_BATCH_SIZE);
    protected String throttle = String.valueOf(DEFAULT_THOTTLE);
    protected boolean dryRun = false;

    public UpdaterEditor(IModel<?> model, final IPluginContext context, Panel container) {
        super("updater-editor", model);
        this.context = context;
        this.container = container;

        form = new HippoForm("updater-form");

        feedback = new FeedbackPanel("feedback", new ContainerFeedbackMessageFilter(this));
        feedback.setOutputMarkupId(true);
        form.add(feedback);

        final AjaxButton executeButton = new AjaxButton("execute-button") {
            private static final long serialVersionUID = 1L;

            @Override
            protected void onSubmit(AjaxRequestTarget target, Form<?> currentForm) {
                executeUpdater(false);
                tryRenderFeedback(target);
            }

            @Override
            public boolean isEnabled() {
                return isExecuteButtonEnabled();
            }

            @Override
            public boolean isVisible() {
                return isExecuteButtonVisible();
            }
        };
        executeButton.setOutputMarkupId(true);
        form.add(executeButton);

        final AjaxButton undoButton = new AjaxButton("undo-button") {
            private static final long serialVersionUID = 1L;

            @Override
            protected void onSubmit(AjaxRequestTarget target, Form<?> currentForm) {
                executeUndo();
                tryRenderFeedback(target);
            }

            @Override
            public boolean isEnabled() {
                return isUndoButtonEnabled();
            }

            @Override
            public boolean isVisible() {
                return isUndoButtonVisible();
            }
        };
        undoButton.setOutputMarkupId(true);
        form.add(undoButton);

        final AjaxButton dryRunButton = new AjaxButton("dryrun-button") {
            private static final long serialVersionUID = 1L;

            @Override
            protected void onSubmit(AjaxRequestTarget target, Form<?> currentForm) {
                executeUpdater(true);
                tryRenderFeedback(target);
            }

            @Override
            public boolean isEnabled() {
                return isExecuteButtonEnabled();
            }

            @Override
            public boolean isVisible() {
                return isExecuteButtonVisible();
            }
        };
        dryRunButton.setOutputMarkupId(true);
        form.add(dryRunButton);

        final AjaxButton saveButton = new AjaxButton("save-button") {
            private static final long serialVersionUID = 1L;

            @Override
            protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
                saveUpdater();
                target.add(executeButton);
                target.add(dryRunButton);
                tryRenderFeedback(target);
            }

            @Override
            public boolean isEnabled() {
                return isSaveButtonEnabled();
            }

            @Override
            public boolean isVisible() {
                return isSaveButtonVisible();
            }
        };
        form.add(saveButton);

        final AjaxButton stopButton = new AjaxButton("stop-button") {
            private static final long serialVersionUID = 1L;

            @Override
            protected void onSubmit(AjaxRequestTarget target, Form<?> currentForm) {
                stopUpdater();
                tryRenderFeedback(target);
            }

            @Override
            public boolean isEnabled() {
                return isStopButtonEnabled();
            }

            @Override
            public boolean isVisible() {
                return isStopButtonVisible();
            }
        };
        form.add(stopButton);

        final AjaxButton deleteButton = new AjaxButton("delete-button") {
            private static final long serialVersionUID = 1L;

            @Override
            protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
                deleteUpdater();
            }

            @Override
            public boolean isEnabled() {
                return isDeleteButtonEnabled();
            }

            @Override
            public boolean isVisible() {
                return isDeleteButtonVisible();
            }
        };
        form.add(deleteButton);

        final RadioGroup<String> radios = new RadioGroup<>("radios", new PropertyModel<>(this, "method"));
        form.add(radios);

        final LabelledInputFieldTableRow nameField = new LabelledInputFieldTableRow("name", new Model<>("Name"),
                new PropertyModel<>(this, "name")) {
            private static final long serialVersionUID = 1L;

            @Override
            public boolean isEnabled() {
                return isNameFieldEnabled();
            }

            @Override
            public boolean isVisible() {
                return isNameFieldVisible();
            }
        };
        radios.add(nameField);

        final LabelledTextAreaTableRow descriptionField = new LabelledTextAreaTableRow("description",
                new Model<>("Description"), new PropertyModel<>(this, "description")) {
            private static final long serialVersionUID = 1L;

            @Override
            public boolean isEnabled() {
                return isDescriptionFieldEnabled();
            }

            @Override
            public boolean isVisible() {
                return isDescriptionFieldVisible();
            }
        };
        radios.add(descriptionField);

        radios.add(new Label("select", new Model<>("Select node using")));

        final RadioLabelledInputFieldTableRow pathField = new RadioLabelledInputFieldTableRow("path", radios,
                new Model<>("Repository path"), new PropertyModel<>(this, "visitorPath")) {
            private static final long serialVersionUID = 1L;

            @Override
            public boolean isEnabled() {
                return isPathFieldEnabled();
            }

            @Override
            public boolean isVisible() {
                return isPathFieldVisible();
            }

            @Override
            protected boolean isRadioVisible() {
                return UpdaterEditor.this.isRadioVisible();
            }
        };
        radios.add(pathField);

        final RadioLabelledInputFieldTableRow queryField = new RadioLabelledInputFieldTableRow("query", radios,
                new Model<>("XPath query"), new PropertyModel<>(this, "visitorQuery")) {
            private static final long serialVersionUID = 1L;

            @Override
            public boolean isEnabled() {
                return isQueryFieldEnabled();
            }

            @Override
            public boolean isVisible() {
                return isQueryFieldVisible();
            }

            @Override
            protected boolean isRadioVisible() {
                return UpdaterEditor.this.isRadioVisible();
            }
        };
        radios.add(queryField);

        final LabelledTextAreaTableRow parametersField = new LabelledTextAreaTableRow("parameters",
                new Model<>("Parameters"), new PropertyModel<>(this, "parameters")) {
            private static final long serialVersionUID = 1L;

            @Override
            public boolean isEnabled() {
                return isParametersFieldEnabled();
            }

            @Override
            public boolean isVisible() {
                return isParametersFieldVisible();
            }
        };
        radios.add(parametersField);

        final LabelledInputFieldTableRow batchSizeField = new LabelledInputFieldTableRow("batch-size",
                new Model<>("Batch Size"), new PropertyModel<>(this, "batchSize")) {
            private static final long serialVersionUID = 1L;

            @Override
            public boolean isEnabled() {
                return isBatchSizeFieldEnabled();
            }

            @Override
            public boolean isVisible() {
                return isBatchSizeFieldVisible();
            }

        };
        radios.add(batchSizeField);

        final LabelledInputFieldTableRow throttleField = new LabelledInputFieldTableRow("throttle",
                new Model<>("Throttle (ms)"), new PropertyModel<>(this, "throttle")) {
            private static final long serialVersionUID = 1L;

            @Override
            public boolean isEnabled() {
                return isThrottleFieldEnabled();
            }

            @Override
            public boolean isVisible() {
                return isThrottleFieldVisible();
            }
        };
        radios.add(throttleField);

        final LabelledCheckBoxTableRow dryRunCheckBox = new LabelledCheckBoxTableRow("dryrun",
                new Model<>("Dry run"), new PropertyModel<>(this, "dryRun")) {
            private static final long serialVersionUID = 1L;

            @Override
            public boolean isEnabled() {
                return false;
            }

            @Override
            public boolean isVisible() {
                return isDryRunCheckBoxVisible();
            }
        };
        radios.add(dryRunCheckBox);

        script = getStringProperty(HippoNodeType.HIPPOSYS_SCRIPT, null);

        final CodeMirrorEditor scriptEditor = new CodeMirrorEditor("script-editor", getEditorName(),
                new PropertyModel<>(this, "script"));
        scriptEditor.setReadOnly(isScriptEditorReadOnly());
        form.add(scriptEditor);

        final Component updaterOutput = createOutputComponent("updater-output");
        form.add(updaterOutput);

        add(form);

        setOutputMarkupId(true);

        loadProperties();
    }

    private void tryRenderFeedback(AjaxRequestTarget target) {
        if (feedback.findParent(Page.class) != null) {
            target.add(feedback);
        }
    }

    @Override
    protected void onDetach() {
        form.detach();
        super.onDetach();
    }

    protected void loadProperties() {
        script = getStringProperty(HippoNodeType.HIPPOSYS_SCRIPT, null);
        if (isUpdater()) {
            name = getName();
        } else {
            name = null;
        }
        description = getStringProperty(HippoNodeType.HIPPOSYS_DESCRIPTION, null);
        parameters = getStringProperty(HippoNodeType.HIPPOSYS_PARAMETERS, null);
        visitorPath = getStringProperty(HippoNodeType.HIPPOSYS_PATH, null);
        visitorQuery = getStringProperty(HippoNodeType.HIPPOSYS_QUERY, null);
        batchSize = String.valueOf(getLongProperty(HippoNodeType.HIPPOSYS_BATCHSIZE, DEFAULT_BATCH_SIZE));
        throttle = String.valueOf(getLongProperty(HippoNodeType.HIPPOSYS_THROTTLE, DEFAULT_THOTTLE));
        if (visitorQuery != null) {
            method = "query";
        }
        if (visitorPath != null) {
            method = "path";
        }
        dryRun = getBooleanProperty(HippoNodeType.HIPPOSYS_DRYRUN, false);
    }

    private String getName() {
        final Node node = (Node) getDefaultModelObject();
        if (node != null) {
            try {
                return node.getName();
            } catch (RepositoryException e) {
                log.error("Failed to retrieve node name", e);
            }
        }
        return null;
    }

    protected final String getStringProperty(String propertyName, String defaultValue) {
        final Node node = (Node) getDefaultModelObject();
        if (node != null) {
            try {
                return JcrUtils.getStringProperty(node, propertyName, defaultValue);
            } catch (RepositoryException e) {
                log.error("Failed to retrieve property {}", propertyName, e);
            }
        }
        return defaultValue;
    }

    protected final boolean getBooleanProperty(String propertyName, boolean defaultValue) {
        final Node node = (Node) getDefaultModelObject();
        if (node != null) {
            try {
                return JcrUtils.getBooleanProperty(node, propertyName, defaultValue);
            } catch (RepositoryException e) {
                log.error("Failed to retrieve property {}", propertyName, e);
            }
        }
        return defaultValue;
    }

    protected final Long getLongProperty(String propertyName, Long defaultValue) {
        final Node node = (Node) getDefaultModelObject();
        if (node != null) {
            try {
                return JcrUtils.getLongProperty(node, propertyName, defaultValue);
            } catch (RepositoryException e) {
                log.error("Failed to retrieve property {}", propertyName, e);
            }
        }
        return defaultValue;
    }

    protected boolean isUpdater() {
        Node node = (Node) getDefaultModelObject();
        if (node != null) {
            try {
                return node.isNodeType("hipposys:updaterinfo");
            } catch (RepositoryException e) {
                log.error("Failed to determine whether node is updater node", e);
            }
        }
        return false;
    }

    protected Component createOutputComponent(String id) {
        return new Label(id);
    }

    protected void deleteUpdater() {
        IDialogService dialogService = context.getService(IDialogService.class.getName(), IDialogService.class);
        dialogService.show(new DeleteUpdaterDialog((IModel<Node>) getDefaultModel(), container));
    }

    private boolean saveUpdater() {
        final Node node = (Node) getDefaultModelObject();
        try {
            if (!validateName()) {
                return false;
            }
            if (!validateParameters()) {
                return false;
            }
            if (isPathMethod()) {
                if (!validateVisitorPath()) {
                    return false;
                }
                node.setProperty(HippoNodeType.HIPPOSYS_PATH, visitorPath);
                node.setProperty(HippoNodeType.HIPPOSYS_QUERY, (String) null);
            } else {
                if (!validateVisitorQuery()) {
                    return false;
                }
                node.setProperty(HippoNodeType.HIPPOSYS_QUERY, visitorQuery);
                node.setProperty(HippoNodeType.HIPPOSYS_PATH, (String) null);
            }
            node.setProperty(HippoNodeType.HIPPOSYS_DRYRUN, dryRun);
            if (!validateBatchSize()) {
                return false;
            }
            node.setProperty(HippoNodeType.HIPPOSYS_BATCHSIZE, Long.valueOf(batchSize));
            if (!validateThrottle()) {
                return false;
            }
            node.setProperty(HippoNodeType.HIPPOSYS_THROTTLE, Long.valueOf(throttle));
            node.setProperty(HippoNodeType.HIPPOSYS_SCRIPT, script);
            if (!node.getName().equals(name)) {
                rename();
            }
            node.setProperty(HippoNodeType.HIPPOSYS_DESCRIPTION, StringUtils.defaultString(description));
            node.setProperty(HippoNodeType.HIPPOSYS_PARAMETERS, StringUtils.defaultString(parameters));
            node.getSession().save();
            return true;
        } catch (RepositoryException e) {
            form.error(getExceptionTranslation(e));
            if (log.isDebugEnabled()) {
                log.error("An unexpected error occurred", e);
            } else {
                log.error("An unexpected error occurred: {}", e.getMessage());
            }
        }
        return false;
    }

    private boolean validateName() {
        if (name == null || name.isEmpty()) {
            form.error(getString("name-is-empty"));
            return false;
        }
        return true;
    }

    private boolean validateParameters() {
        if (StringUtils.isBlank(parameters)) {
            return true;
        }
        try {
            JSONObject json = JSONObject.fromObject(parameters);
            return json != null;
        } catch (JSONException e) {
            form.error(getString("parameters-invalid-json"));
            return false;
        }
    }

    private boolean validateThrottle() {
        try {
            Long.valueOf(throttle);
            return true;
        } catch (NumberFormatException e) {
            form.error(getString("throttle-must-be-positive"));
            return false;
        }
    }

    private boolean validateBatchSize() {
        try {
            Long.valueOf(batchSize);
            return true;
        } catch (NumberFormatException e) {
            form.error(getString("batchsize-must-be-positive"));
            return false;
        }
    }

    private boolean validateVisitorPath() throws RepositoryException {
        if (visitorPath == null || visitorPath.isEmpty()) {
            form.error(getString("path-is-empty"));
            return false;
        }
        final Session session = UserSession.get().getJcrSession();
        try {
            if (!session.nodeExists(visitorPath)) {
                form.error(getString("path-not-exist"));
                return false;
            }
        } catch (RepositoryException e) {
            String message = getString("path-not-wellformed");
            form.error(message);
            if (log.isDebugEnabled()) {
                log.error(message, e);
            } else {
                log.error(message + ":" + e.getMessage());
            }
            return false;
        }
        return true;
    }

    private boolean validateVisitorQuery() throws RepositoryException {
        if (visitorQuery == null || visitorQuery.isEmpty()) {
            form.error(getString("query-is-empty"));
            return false;
        }
        final Session session = UserSession.get().getJcrSession();
        try {
            session.getWorkspace().getQueryManager().createQuery(RepoUtils.encodeXpath(visitorQuery), Query.XPATH);
        } catch (InvalidQueryException e) {
            final String message = getString("invalid-query");
            form.error(message);
            if (log.isDebugEnabled()) {
                log.error(message, e);
            }
            return false;
        }
        return true;
    }

    private boolean isPathMethod() {
        return method != null && method.equals("path");
    }

    private void rename() {
        final Session session = UserSession.get().getJcrSession();
        try {
            final Node node = (Node) getDefaultModelObject();
            final String destAbsPath = UPDATE_REGISTRY_PATH + "/" + name;
            session.move(node.getPath(), destAbsPath);
            container.setDefaultModel(new JcrNodeModel(session.getNode(destAbsPath)));
        } catch (RepositoryException e) {
            form.error(getExceptionTranslation(e, name).getObject());

            if (log.isDebugEnabled()) {
                log.error("Failed to rename updater", e);
            } else {
                log.error("Failed to rename updater: {}", e.getMessage());
            }
        }
    }

    private void executeUpdater(boolean dryRun) {
        this.dryRun = dryRun;
        if (!saveUpdater()) {
            return;
        }
        final Node node = (Node) getDefaultModelObject();
        final Session session = UserSession.get().getJcrSession();
        if (node != null) {
            try {
                final String srcPath = node.getPath();
                if (srcPath.startsWith(UPDATE_REGISTRY_PATH)) {
                    final String destPath = UPDATE_QUEUE_PATH + "/" + node.getName();
                    JcrUtils.copy(session, srcPath, destPath);
                    final Node queuedNode = session.getNode(destPath);
                    queuedNode.setProperty(HippoNodeType.HIPPOSYS_STARTEDBY, session.getUserID());
                    session.save();
                    container.setDefaultModel(new JcrNodeModel(queuedNode));
                }
            } catch (RepositoryException e) {
                final String message = "An unexpected error occurred: " + e.getMessage();
                form.error(message);
                if (log.isDebugEnabled()) {
                    log.error(message, e);
                }
            }
        }
    }

    private void executeUndo() {
        final Node node = (Node) getDefaultModelObject();
        final Session session = UserSession.get().getJcrSession();
        if (node != null) {
            try {
                final String srcPath = node.getPath();
                if (srcPath.startsWith(UPDATE_HISTORY_PATH)) {
                    final String destPath = UPDATE_QUEUE_PATH + "/undo-" + node.getName();
                    JcrUtils.copy(session, srcPath, destPath);
                    final Node queuedNode = session.getNode(destPath);
                    queuedNode.setProperty(HippoNodeType.HIPPOSYS_STARTEDBY, session.getUserID());
                    queuedNode.setProperty(HippoNodeType.HIPPOSYS_REVERT, true);
                    queuedNode.setProperty(HippoNodeType.HIPPOSYS_SKIPPED, (Value) null);
                    queuedNode.setProperty(HippoNodeType.HIPPOSYS_SKIPPEDCOUNT, -1l);
                    queuedNode.setProperty(HippoNodeType.HIPPOSYS_FAILED, (Value) null);
                    queuedNode.setProperty(HippoNodeType.HIPPOSYS_FAILEDCOUNT, -1l);
                    queuedNode.setProperty(HippoNodeType.HIPPOSYS_LOG, (Value) null);
                    queuedNode.setProperty(HippoNodeType.HIPPOSYS_LOGTAIL, (String) null);
                    session.save();
                    container.setDefaultModel(new JcrNodeModel(queuedNode));
                }
            } catch (RepositoryException e) {
                final String message = "An unexpected error occurred: " + e.getMessage();
                form.error(message);
                if (log.isDebugEnabled()) {
                    log.error(message, e);
                }
            }
        }
    }

    private void stopUpdater() {
        final Node node = (Node) getDefaultModelObject();
        final Session session = UserSession.get().getJcrSession();
        if (node != null) {
            try {
                node.setProperty(HippoNodeType.HIPPOSYS_CANCELLED, true);
                node.setProperty(HippoNodeType.HIPPOSYS_CANCELLEDBY, session.getUserID());
                session.save();
            } catch (RepositoryException e) {
                final String message = getExceptionTranslation(e).getObject();
                form.error(message);
                if (log.isDebugEnabled()) {
                    log.error(message, e);
                }
            }
        }
    }

    protected boolean isStopButtonVisible() {
        return true;
    }

    protected boolean isSaveButtonVisible() {
        return true;
    }

    protected boolean isExecuteButtonVisible() {
        return true;
    }

    protected boolean isUndoButtonVisible() {
        return true;
    }

    protected boolean isDeleteButtonVisible() {
        return true;
    }

    protected boolean isStopButtonEnabled() {
        return true;
    }

    protected boolean isSaveButtonEnabled() {
        return true;
    }

    protected boolean isExecuteButtonEnabled() {
        return true;
    }

    protected boolean isUndoButtonEnabled() {
        return true;
    }

    protected boolean isDeleteButtonEnabled() {
        return true;
    }

    protected boolean isNameFieldEnabled() {
        return true;
    }

    protected boolean isNameFieldVisible() {
        return true;
    }

    protected boolean isDescriptionFieldEnabled() {
        return true;
    }

    protected boolean isDescriptionFieldVisible() {
        return true;
    }

    protected boolean isPathFieldEnabled() {
        return true;
    }

    protected boolean isPathFieldVisible() {
        return true;
    }

    protected boolean isQueryFieldEnabled() {
        return true;
    }

    protected boolean isQueryFieldVisible() {
        return true;
    }

    protected boolean isParametersFieldEnabled() {
        return true;
    }

    protected boolean isParametersFieldVisible() {
        return true;
    }

    protected boolean isBatchSizeFieldEnabled() {
        return true;
    }

    protected boolean isBatchSizeFieldVisible() {
        return true;
    }

    protected boolean isThrottleFieldEnabled() {
        return true;
    }

    protected boolean isThrottleFieldVisible() {
        return true;
    }

    protected boolean isRadioVisible() {
        return true;
    }

    protected boolean isDryRunCheckBoxVisible() {
        return true;
    }

    protected boolean isScriptEditorReadOnly() {
        return false;
    }

    protected String getEditorName() {
        return "updater-editor";
    }

    protected IModel<String> getExceptionTranslation(final Throwable t, final Object... parameters) {
        String key = "exception,type=${type},message=${message}";
        HashMap<String, String> details = new HashMap<>();
        details.put("type", t.getClass().getName());
        details.put("message", t.getMessage());
        return new StringResourceModel(key, UpdaterEditor.this, new Model<>(details), t.getLocalizedMessage(),
                parameters);
    }
}