/** * Licensed to the Austrian Association for Software Tool Integration (AASTI) * under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright * ownership. The AASTI licenses this file to you 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.openengsb.ui.admin.wiringPage; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; import org.apache.commons.lang.ArrayUtils; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; import org.apache.wicket.extensions.markup.html.tree.BaseTree; import org.apache.wicket.extensions.markup.html.tree.LabelTree; import org.apache.wicket.extensions.markup.html.tree.LinkTree; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.form.ChoiceRenderer; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.panel.FeedbackPanel; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.util.value.ValueMap; import org.openengsb.core.api.ConnectorManager; import org.openengsb.core.api.Constants; import org.openengsb.core.api.Domain; import org.openengsb.core.api.DomainProvider; import org.openengsb.core.api.OsgiUtilsService; import org.openengsb.core.api.WiringService; import org.openengsb.core.api.model.ConnectorDescription; import org.openengsb.core.api.security.annotation.SecurityAttribute; import org.openengsb.core.util.Comparators; import org.openengsb.core.util.FilterUtils; import org.openengsb.core.workflow.api.RuleManager; import org.openengsb.ui.admin.basePage.BasePage; import org.ops4j.pax.wicket.api.PaxWicketMountPoint; import org.osgi.framework.Filter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SecurityAttribute(key = "org.openengsb.ui.component", value = "WORKFLOW_ADMIN") @PaxWicketMountPoint(mountPoint = "wiring") public class WiringPage extends BasePage { private static final long serialVersionUID = 4196803215701011090L; private static final Logger LOGGER = LoggerFactory.getLogger(WiringPage.class); public static final String PAGE_NAME_KEY = "wiringPage.title"; public static final String PAGE_DESCRIPTION_KEY = "wiringPage.description"; @Inject @Named("wiringService") private WiringService wiringService; @Inject @Named("osgiUtilsService") private OsgiUtilsService serviceUtils; @Inject @Named("serviceManager") private ConnectorManager serviceManager; @Inject @Named("ruleManager") private RuleManager ruleManager; private DropDownChoice<Class<? extends Domain>> domains; private LinkTree globals; private LinkTree endpoints; private TextField<String> txtGlobalName; private TextField<String> txtInstanceId; private CheckedTree contextList; private AjaxSubmitLink wireButton; private FeedbackPanel feedbackPanel; private String globalName = ""; private String instanceId = ""; public WiringPage() { initializeDomainChooseForm(); initializeGlobalNameField(); initializeInstanceIdField(); initializeGlobals(); initializeEndpoints(); initializeWiringForm(); initializeFeedbackPanel(); } @SuppressWarnings("serial") private void initializeDomainChooseForm() { Form<Void> domainChooseForm = new Form<Void>("domainChooseForm"); domains = new DropDownChoice<Class<? extends Domain>>("domains"); domains.setOutputMarkupId(true); domains.setChoiceRenderer(new ChoiceRenderer<Class<? extends Domain>>("canonicalName")); domains.setChoices(createDomainListModel()); domains.setModel(new Model<Class<? extends Domain>>()); domains.add(new AjaxFormComponentUpdatingBehavior("onchange") { @Override protected void onUpdate(AjaxRequestTarget target) { Class<? extends Domain> domainType = domains.getModelObject(); LOGGER.debug("chosen {}", domainType); globals.setModel(createGlobalTreeModel(domainType)); endpoints.setModel(createEndpointsModel(domainType)); resetWiringForm(target); target.add(globals); target.add(endpoints); } }); domainChooseForm.add(domains); add(domainChooseForm); } private void initializeGlobalNameField() { txtGlobalName = new TextField<String>("globalName"); txtGlobalName.setOutputMarkupId(true); txtGlobalName.setMarkupId("globalName"); } private void initializeInstanceIdField() { txtInstanceId = new TextField<String>("instanceId"); txtInstanceId.setOutputMarkupId(true); txtInstanceId.setMarkupId("instanceId"); txtInstanceId.setEnabled(false); } private void initializeGlobals() { globals = new WiringSubjectTree("globals", txtGlobalName); globals.getTreeState().expandAll(); globals.setOutputMarkupId(true); globals.setOutputMarkupPlaceholderTag(true); add(globals); } private void initializeEndpoints() { endpoints = new WiringSubjectTree("endpoints", txtInstanceId); endpoints.getTreeState().expandAll(); endpoints.setOutputMarkupId(true); endpoints.setOutputMarkupPlaceholderTag(true); add(endpoints); } private void initializeFeedbackPanel() { feedbackPanel = new FeedbackPanel("feedbackPanel"); feedbackPanel.setOutputMarkupId(true); add(feedbackPanel); } @SuppressWarnings("serial") private void initializeWiringForm() { Form<Object> wiringForm = new Form<Object>("wiringForm"); wiringForm.setOutputMarkupId(true); wiringForm.setDefaultModel(new CompoundPropertyModel<Object>(this)); wiringForm.add(txtGlobalName); wiringForm.add(txtInstanceId); contextList = new CheckedTree("contextList", createContextModel()); contextList.getTreeState().expandAll(); wiringForm.add(contextList); wireButton = new AjaxSubmitLink("wireButton", wiringForm) { @Override protected void onSubmit(AjaxRequestTarget target, Form<?> form) { LOGGER.debug("Start wiring {} with {}", globalName, instanceId); if (noGlobalNameSet() || noInstanceIdSet() || noContextSet()) { target.add(feedbackPanel); return; } ConnectorDescription description; try { description = serviceManager.getAttributeValues(instanceId); if (!typeOfGlobalAndServiceAreEqual(description.getDomainType())) { target.add(feedbackPanel); return; } } catch (Exception e) { presentAndLogError(new StringResourceModel("wiringInitError", this, null).getString(), e); resetWiringForm(target); return; } try { updateLocations(instanceId, description); } catch (Exception e) { presentAndLogError(new StringResourceModel("wiringError", this, null).getString(), e); } finally { resetWiringForm(target); } } @Override protected void onError(AjaxRequestTarget target, Form<?> form) { LOGGER.warn("Linking Ajax Link produces an error."); } }; wiringForm.add(wireButton); add(wiringForm); } private void updateLocations(String connectorId, ConnectorDescription description) throws Exception { boolean updated = false; ValueMap vmap = new ValueMap(); vmap.put("globalName", globalName); Model<ValueMap> vmapModel = new Model<ValueMap>(vmap); for (String context : contextList.getAllChecked()) { vmap.put("context", context); if (setLocation(globalName, context, description.getProperties())) { updated = true; info(new StringResourceModel("wiringSuccess", this, vmapModel).getString()); LOGGER.info("{} got wired with {} in context {}", new Object[]{ globalName, instanceId, context }); } else { info(new StringResourceModel("doubleWiring", this, vmapModel).getString()); LOGGER.info("{} already wired with {} in context {}", new Object[]{ globalName, instanceId, context }); } } if (updated) { serviceManager.forceUpdate(connectorId, description); } } /** * returns true if location is not already set in the properties, otherwise false */ private boolean setLocation(String global, String context, Map<String, Object> properties) { String locationKey = "location." + context; Object propvalue = properties.get(locationKey); if (propvalue == null) { properties.put(locationKey, global); } else if (propvalue.getClass().isArray()) { Object[] locations = (Object[]) propvalue; if (ArrayUtils.contains(locations, global)) { return false; } Object[] newArray = Arrays.copyOf(locations, locations.length + 1); newArray[locations.length] = global; properties.put(locationKey, newArray); } else { if (((String) propvalue).equals(global)) { return false; } Object[] newArray = new Object[2]; newArray[0] = propvalue; newArray[1] = global; properties.put(locationKey, newArray); } return true; } private boolean typeOfGlobalAndServiceAreEqual(String domainNameOfService) { String domainTypeOfGlobal = getDomainTypeOfGlobal(globalName); String domainTypeOfService = getDomainTypeOfServiceName(domainNameOfService); if (domainTypeOfGlobal != null) { if (!domainTypeOfGlobal.equals(domainTypeOfService)) { info(new StringResourceModel("globalAlreadySet", this, null).getString()); LOGGER.info("cannot wire {} with {}, because {} has type {}", new Object[]{ globalName, instanceId, globalName, domainTypeOfGlobal }); return false; } } else { ruleManager.addGlobal(domainTypeOfService, globalName); LOGGER.info("created global {} of type {}", globalName, domainTypeOfService); } return true; } private String getDomainTypeOfServiceName(String domainName) { Filter filter = FilterUtils.makeFilter(DomainProvider.class, String.format("(%s=%s)", Constants.DOMAIN_KEY, domainName)); DomainProvider dp = (DomainProvider) serviceUtils.getService(filter); if (dp == null || dp.getDomainInterface() == null) { return null; } return dp.getDomainInterface().getCanonicalName(); } private String getDomainTypeOfGlobal(String glob) { return ruleManager.getGlobalType(glob); } private boolean noGlobalNameSet() { if (globalName == null || globalName.trim().isEmpty()) { error(new StringResourceModel("globalNotSet", this, null).getString()); return true; } return false; } private boolean noInstanceIdSet() { if (instanceId == null || instanceId.isEmpty()) { error(new StringResourceModel("instanceIdNotSet", this, null).getString()); return true; } return false; } private boolean noContextSet() { if (contextList.getAllChecked().isEmpty()) { error(new StringResourceModel("contextNotSet", this, null).getString()); return true; } return false; } private void presentAndLogError(String message, Exception e) { error(message + "\n" + e.getLocalizedMessage()); LOGGER.error("Error during wiring", e); } @SuppressWarnings("serial") private IModel<? extends List<? extends Class<? extends Domain>>> createDomainListModel() { return new LoadableDetachableModel<List<? extends Class<? extends Domain>>>() { @Override protected List<? extends Class<? extends Domain>> load() { List<DomainProvider> serviceList = serviceUtils.listServices(DomainProvider.class); Collections.sort(serviceList, Comparators.forDomainProvider()); List<Class<? extends Domain>> domains = new ArrayList<Class<? extends Domain>>(); for (DomainProvider dp : serviceList) { domains.add(dp.getDomainInterface()); } return domains; } }; } @SuppressWarnings("serial") private IModel<TreeModel> createGlobalTreeModel(final Class<? extends Domain> domainType) { return new LoadableDetachableModel<TreeModel>() { @Override protected TreeModel load() { DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Globals"); if (domainType != null) { for (Entry<String, String> e : ruleManager.listGlobals().entrySet()) { if (e.getValue().equals(domainType.getCanonicalName())) { DefaultMutableTreeNode child = new DefaultMutableTreeNode(e.getKey()); rootNode.add(child); } } } return new DefaultTreeModel(rootNode); } }; } @SuppressWarnings("serial") private IModel<TreeModel> createEndpointsModel(final Class<? extends Domain> domainType) { return new LoadableDetachableModel<TreeModel>() { @Override protected TreeModel load() { DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Domain endpoints"); if (domainType != null) { for (Domain d : wiringService.getDomainEndpoints(domainType, "*")) { String id = d.getInstanceId(); if (id != null) { DefaultMutableTreeNode child = new DefaultMutableTreeNode(id); rootNode.add(child); } } } return new DefaultTreeModel(rootNode); } }; } private TreeModel createContextModel() { DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Contexts"); for (String c : getAvailableContexts()) { rootNode.add(new DefaultMutableTreeNode(c)); } return new DefaultTreeModel(rootNode); } private void resetWiringForm(AjaxRequestTarget target) { globalName = ""; instanceId = ""; target.add(txtGlobalName); target.add(txtInstanceId); target.add(feedbackPanel); } public String getGlobalName() { return globalName; } public void setGlobalName(String globalName) { this.globalName = globalName; } public String getInstanceId() { return instanceId; } public void setInstanceId(String instanceId) { this.instanceId = instanceId; } @SuppressWarnings("serial") private class WiringSubjectTree extends LinkTree { private final TextField<String> subject; public WiringSubjectTree(String id, TextField<String> subject) { super(id); this.subject = subject; } @Override protected void onNodeLinkClicked(Object node, BaseTree tree, AjaxRequestTarget target) { DefaultMutableTreeNode mnode = (DefaultMutableTreeNode) node; if (mnode.isRoot()) { return; } subject.setDefaultModelObject(mnode.getUserObject()); target.add(subject); } @Override public boolean isVisible() { if (getModelObject() == null) { return false; } DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModelObject().getRoot(); return root != null && !root.isLeaf(); } } @SuppressWarnings("serial") public static class CheckedTree extends LabelTree { private final Map<String, IModel<Boolean>> checks = new HashMap<String, IModel<Boolean>>(); public CheckedTree(String id, TreeModel model) { super(id, model); } @Override protected Component newNodeComponent(String id, IModel<Object> model) { DefaultMutableTreeNode mnode = (DefaultMutableTreeNode) model.getObject(); if (mnode.isRoot()) { return super.newNodeComponent(id, model); } String name = (String) mnode.getUserObject(); Model<String> labelModel = new Model<String>(); labelModel.setObject(name); Model<Boolean> checkModel = new Model<Boolean>(); checkModel.setObject(Boolean.FALSE); checks.put(name, checkModel); return new CheckedPanel(id, checkModel, labelModel); } @Override protected Component newJunctionLink(MarkupContainer parent, final String id, final Object node) { return new WebMarkupContainer(id) { @Override protected void onComponentTag(ComponentTag tag) { super.onComponentTag(tag); tag.setName("span"); tag.put("class", "junction-corner"); } }; } public Set<String> getAllChecked() { Set<String> checked = new HashSet<String>(); for (Entry<String, IModel<Boolean>> e : checks.entrySet()) { if (Boolean.TRUE.equals(e.getValue().getObject())) { checked.add(e.getKey()); } } return checked; } } }