Java tutorial
/** * 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.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.jcr.ItemExistsException; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.observation.Event; import javax.swing.event.TreeModelEvent; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import org.apache.commons.lang.StringUtils; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.form.AjaxButton; import org.apache.wicket.extensions.breadcrumb.IBreadCrumbModel; import org.apache.wicket.extensions.breadcrumb.IBreadCrumbModelListener; import org.apache.wicket.extensions.breadcrumb.IBreadCrumbParticipant; import org.apache.wicket.extensions.markup.html.tree.DefaultTreeState; import org.apache.wicket.extensions.markup.html.tree.ITreeState; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.panel.FeedbackPanel; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.util.io.IOUtils; import org.hippoecm.frontend.dialog.HippoForm; import org.hippoecm.frontend.model.JcrNodeModel; import org.hippoecm.frontend.model.event.IObserver; import org.hippoecm.frontend.model.tree.IJcrTreeNode; import org.hippoecm.frontend.model.tree.JcrTreeModel; import org.hippoecm.frontend.model.tree.JcrTreeNode; import org.hippoecm.frontend.plugin.IPluginContext; import org.hippoecm.frontend.plugins.cms.browse.tree.CmsJcrTree; import org.hippoecm.frontend.plugins.standards.icon.HippoIcon; import org.hippoecm.frontend.plugins.standards.list.resolvers.CssClass; import org.hippoecm.frontend.plugins.standards.panelperspective.breadcrumb.PanelPluginBreadCrumbPanel; import org.hippoecm.frontend.plugins.standards.tree.icon.DefaultTreeNodeIconProvider; import org.hippoecm.frontend.plugins.standards.tree.icon.ITreeNodeIconProvider; import org.hippoecm.frontend.session.UserSession; import org.hippoecm.frontend.skin.Icon; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class UpdaterPanel extends PanelPluginBreadCrumbPanel { private final static Logger log = LoggerFactory.getLogger(UpdaterPanel.class); private static final String UPDATE_PATH = "/hippo:configuration/hippo:update"; private static final String UPDATE_QUEUE_PATH = UPDATE_PATH + "/hippo:queue"; private static final String UPDATE_REGISTRY_PATH = UPDATE_PATH + "/hippo:registry"; private static final String UPDATE_HISTORY_PATH = UPDATE_PATH + "/hippo:history"; private static final Label EMPTY_EDITOR = new Label("updater-editor"); private static final Map<String, String> CUSTOM_NODE_LABELS = createNodeNameMap(); private static Map<String, String> createNodeNameMap() { final Map<String, String> map = new HashMap<>(); map.put("hippo:registry", "Registry"); map.put("hippo:queue", "Queue"); map.put("hippo:history", "History"); return Collections.unmodifiableMap(map); } private final IPluginContext context; private final UpdaterTree tree; private final JcrTreeModel treeModel; private final Label title; private Component editor; private String path; private final Form form; private class UpdaterTree extends CmsJcrTree { public UpdaterTree(final String id, final JcrTreeModel treeModel, final ITreeNodeTranslator treeNodeTranslator, final ITreeNodeIconProvider iconService) { super(id, treeModel, treeNodeTranslator, iconService); } @Override protected void populateTreeItem(final WebMarkupContainer item, final int level) { super.populateTreeItem(item, level); final Component nodeLink = item.get("nodeLink"); if (nodeLink != null) { nodeLink.add(CssClass.append("node-link")); } } @Override protected void onNodeLinkClicked(final AjaxRequestTarget target, final TreeNode clickedNode) { if (clickedNode instanceof IJcrTreeNode) { ITreeState state = getTreeState(); if (state.isNodeExpanded(clickedNode)) { // super has already switched selection. if (!state.isNodeSelected(clickedNode)) { state.collapseNode(clickedNode); } } else { state.expandNode(clickedNode); } IJcrTreeNode treeNodeModel = (IJcrTreeNode) clickedNode; UpdaterPanel.this.setDefaultModel(treeNodeModel.getNodeModel()); } } @Override protected void onJunctionLinkClicked(AjaxRequestTarget target, TreeNode node) { updateTree(target); } @Override protected ITreeState newTreeState() { DefaultTreeState state = new DefaultTreeState(); treeModel.setTreeState(state); state.expandAll(); return state; } @Override public String renderNode(TreeNode treeNode, int level) { final String customNodeName = getCustomNodeName((IJcrTreeNode) treeNode); return StringUtils.isEmpty(customNodeName) ? super.renderNode(treeNode, level) : customNodeName; } private String getCustomNodeName(final IJcrTreeNode treeNode) { Node node = treeNode.getNodeModel().getObject(); if (node != null) { try { final String nodeName = node.getName(); final String label = CUSTOM_NODE_LABELS.get(nodeName); if (label != null) { return label; } } catch (RepositoryException e) { log.error("Failed to render tree node", e); } } return null; } /** * Expand all children of root */ public void expandChildrenOfRoot() { ITreeState state = tree.getTreeState(); final TreeModel thisTreeModel = (TreeModel) getDefaultModel().getObject(); final TreeNode root = (TreeNode) thisTreeModel.getRoot(); if (root != null) { Collections.list(root.children()).forEach(child -> state.expandNode(child)); } } } public UpdaterPanel(final String componentId, final IBreadCrumbModel breadCrumbModel, final IPluginContext context) { super(componentId, breadCrumbModel); this.context = context; form = new HippoForm("new-form"); final AjaxButton newButton = new AjaxButton("new-button") { @Override protected void onSubmit(AjaxRequestTarget target, Form<?> currentForm) { newUpdater(); } }; // customize feedbackpanel to display only messages from hippoform FeedbackPanel feedbackPanel = getFeedbackPanel(); if (feedbackPanel != null) { feedbackPanel.setFilter(message -> { final Component reporter = message.getReporter(); return reporter == UpdaterPanel.this.form; }); } form.add(newButton); add(form); treeModel = new JcrTreeModel(new JcrTreeNode(new JcrNodeModel(UPDATE_PATH), null)) { @Override protected TreeModelEvent newTreeModelEvent(final Event event) throws RepositoryException { if (StringUtils.equals(event.getPath(), getNodePath())) { updateUI(); } return super.newTreeModelEvent(event); } }; context.registerService(treeModel, IObserver.class.getName()); breadCrumbModel.addListener(new IBreadCrumbModelListener() { @Override public void breadCrumbActivated(final IBreadCrumbParticipant previousParticipant, final IBreadCrumbParticipant breadCrumbParticipant) { } @Override public void breadCrumbAdded(final IBreadCrumbParticipant breadCrumbParticipant) { } @Override public void breadCrumbRemoved(final IBreadCrumbParticipant breadCrumbParticipant) { if (breadCrumbParticipant == UpdaterPanel.this) { breadCrumbModel.removeListener(this); context.unregisterService(treeModel, IObserver.class.getName()); } } }); tree = new UpdaterTree("updater-tree", treeModel, newTreeNodeTranslator(), newTreeNodeIconProvider()); tree.setRootLess(true); tree.setOutputMarkupId(true); add(tree); tree.expandChildrenOfRoot(); editor = EMPTY_EDITOR; editor.setOutputMarkupId(true); add(editor); title = new Label("updater-title", new AbstractReadOnlyModel<String>() { @Override public String getObject() { return getUpdaterTitle(); } }); title.setOutputMarkupId(true); add(title); setOutputMarkupId(true); } private ITreeNodeIconProvider newTreeNodeIconProvider() { return new DefaultTreeNodeIconProvider() { @Override public HippoIcon getNodeIcon(final String id, final TreeNode treeNode, final ITreeState state) { final IModel<Node> nodeModel = ((IJcrTreeNode) treeNode).getNodeModel(); if (isUpdater(nodeModel.getObject())) { return HippoIcon.fromSprite(id, Icon.FILE_TEXT); } else { return super.getNodeIcon(id, treeNode, state); } } }; } private CmsJcrTree.ITreeNodeTranslator newTreeNodeTranslator() { return new CmsJcrTree.TreeNodeTranslator(); } private String getUpdaterTitle() { if (isQueuedUpdater()) { return "Monitoring updater run '" + getUpdaterName() + "'"; } if (isRegisteredUpdater()) { return "Editing updater '" + getUpdaterName() + "'"; } if (isArchivedUpdater()) { return "Viewing updater run '" + getUpdaterName() + "'"; } return getTitle(null).getObject(); } public IModel<String> getTitle(final Component component) { return Model.of(getString("updater-editor-title")); } @Override protected void onModelChanged() { super.onModelChanged(); updateUI(); } private void updateUI() { final AjaxRequestTarget target = RequestCycle.get().find(AjaxRequestTarget.class); expandAndSelectNodeInTree(target); updateEditor(target); target.add(title); path = null; } private void updateEditor(final AjaxRequestTarget target) { if (isQueuedUpdater()) { replace(editor = new UpdaterQueueEditor(getDefaultModel(), context, this)); } else if (isRegisteredUpdater()) { replace(editor = new UpdaterRegistryEditor(getDefaultModel(), context, this)); } else if (isArchivedUpdater()) { replace(editor = new UpdaterHistoryEditor(getDefaultModel(), context, this)); } else { if (editor != EMPTY_EDITOR) { replace(editor = EMPTY_EDITOR); } } target.add(editor); } private void expandAndSelectNodeInTree(final AjaxRequestTarget target) { final JcrNodeModel model = (JcrNodeModel) getDefaultModel(); final TreePath treePath = treeModel.lookup(model); final ITreeState treeState = tree.getTreeState(); for (Object n : treePath.getPath()) { final TreeNode treeNode = (TreeNode) n; if (!treeState.isNodeExpanded(treeNode)) { treeState.expandNode(treeNode); } } treeState.selectNode(treePath.getLastPathComponent(), true); tree.updateTree(target); } private String getNodePath() { final Node node = (Node) getDefaultModelObject(); try { if (node != null) { path = node.getPath(); } } catch (RepositoryException e) { log.error("Error while getting node path", e); } return path != null ? path : ""; } public String getUpdaterName() { final Node node = (Node) getDefaultModelObject(); try { if (node != null) { return node.getName(); } } catch (RepositoryException e) { log.error("Failed to get name of updater"); } return ""; } private boolean isRegisteredUpdater() { return getNodePath().startsWith(UPDATE_REGISTRY_PATH + "/"); } private boolean isQueuedUpdater() { return getNodePath().startsWith(UPDATE_QUEUE_PATH + "/"); } private boolean isArchivedUpdater() { return getNodePath().startsWith(UPDATE_HISTORY_PATH + "/"); } private static boolean isUpdater(Node node) { 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; } private void newUpdater() { final Session session = UserSession.get().getJcrSession(); try { final Node registry = session.getNode(UPDATE_REGISTRY_PATH); final Node node = addUpdater(registry, 1); session.save(); setDefaultModel(new JcrNodeModel(node)); } catch (RepositoryException | IOException e) { final String message = "An unexpected error occurred: " + e.getMessage(); showError(message); log.error(message, e); } } private void showError(final String message) { form.error(message); } private Node addUpdater(final Node registry, int index) throws IOException, RepositoryException { try { final Node node = registry.addNode("new-" + index, "hipposys:updaterinfo"); node.setProperty("hipposys:script", IOUtils.toString(UpdaterEditor.class.getResource("UpdaterTemplate.groovy").openStream())); node.setProperty("hipposys:path", "/"); return node; } catch (ItemExistsException e) { return addUpdater(registry, index + 1); } } }