/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.apache.syncope.client.console.topology; import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal; import java.io.Serializable; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.commons.Constants; import org.apache.syncope.client.console.pages.BasePage; import org.apache.syncope.client.console.wizards.resources.AbstractResourceWizardBuilder.CreateEvent; import org.apache.syncope.client.console.rest.BaseRestClient; import org.apache.syncope.client.console.rest.ConnectorRestClient; import org.apache.syncope.client.console.rest.ResourceRestClient; import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal; import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink; import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel; import org.apache.syncope.common.lib.to.ConnInstanceTO; import org.apache.syncope.common.lib.to.ResourceTO; import org.apache.syncope.common.lib.types.StandardEntitlement; import org.apache.wicket.Component; import org.apache.wicket.ajax.AbstractAjaxTimerBehavior; import org.apache.wicket.ajax.AjaxEventBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.IAjaxIndicatorAware; import org.apache.wicket.behavior.Behavior; import org.apache.wicket.event.IEvent; import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.OnDomReadyHeaderItem; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.util.time.Duration; public class Topology extends BasePage { private static final long serialVersionUID = -1100228004207271272L; public static final String CONNECTOR_SERVER_LOCATION_PREFIX = "connid://"; public static final String ROOT_NAME = "Syncope"; private final ResourceRestClient resourceRestClient = new ResourceRestClient(); private final ConnectorRestClient connectorRestClient = new ConnectorRestClient(); private final int origX = 3100; private final int origY = 2800; private final BaseModal<Serializable> modal; private final WebMarkupContainer newlyCreatedContainer; private final ListView<TopologyNode> newlyCreated; private final TopologyTogglePanel togglePanel; private final LoadableDetachableModel<List<ResourceTO>> resModel = new LoadableDetachableModel<List<ResourceTO>>() { private static final long serialVersionUID = 5275935387613157431L; @Override protected List<ResourceTO> load() { return resourceRestClient.list(); } }; private final LoadableDetachableModel<Map<String, List<ConnInstanceTO>>> connModel = new LoadableDetachableModel<Map<String, List<ConnInstanceTO>>>() { private static final long serialVersionUID = 5275935387613157432L; @Override protected Map<String, List<ConnInstanceTO>> load() { final Map<String, List<ConnInstanceTO>> res = new HashMap<>(); for (ConnInstanceTO conn : connectorRestClient.getAllConnectors()) { final List<ConnInstanceTO> conns; if (res.containsKey(conn.getLocation())) { conns = res.get(conn.getLocation()); } else { conns = new ArrayList<>(); res.put(conn.getLocation(), conns); } conns.add(conn); } return res; } }; private final LoadableDetachableModel<Pair<List<URI>, List<URI>>> csModel = new LoadableDetachableModel<Pair<List<URI>, List<URI>>>() { private static final long serialVersionUID = 5275935387613157433L; @Override protected Pair<List<URI>, List<URI>> load() { final List<URI> connectorServers = new ArrayList<>(); final List<URI> filePaths = new ArrayList<>(); for (String location : SyncopeConsoleSession.get().getPlatformInfo().getConnIdLocations()) { if (location.startsWith(CONNECTOR_SERVER_LOCATION_PREFIX)) { connectorServers.add(URI.create(location)); } else { filePaths.add(URI.create(location)); } } return Pair.of(connectorServers, filePaths); } }; protected enum SupportedOperation { CHECK_RESOURCE, CHECK_CONNECTOR, ADD_ENDPOINT; } public Topology() { modal = new BaseModal<>("resource-modal"); body.add(modal.size(Modal.Size.Large)); modal.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() { private static final long serialVersionUID = 8804221891699487139L; @Override public void onClose(final AjaxRequestTarget target) { modal.show(false); } }); body.add(new TopologyWebSocketBehavior()); togglePanel = new TopologyTogglePanel("toggle", getPageReference()); body.add(togglePanel); // ----------------------------------------- // Add Zoom panel // ----------------------------------------- final ActionsPanel<Serializable> zoomActionPanel = new ActionsPanel<>("zoom", null); zoomActionPanel.add(new ActionLink<Serializable>() { private static final long serialVersionUID = -3722207913631435501L; @Override public void onClick(final AjaxRequestTarget target, final Serializable ignore) { target.appendJavaScript("zoomIn($('#drawing')[0]);"); } }, ActionLink.ActionType.ZOOM_IN, StandardEntitlement.RESOURCE_LIST).disableIndicator().hideLabel(); zoomActionPanel.add(new ActionLink<Serializable>() { private static final long serialVersionUID = -3722207913631435501L; @Override public void onClick(final AjaxRequestTarget target, final Serializable ignore) { target.appendJavaScript("zoomOut($('#drawing')[0]);"); } }, ActionLink.ActionType.ZOOM_OUT, StandardEntitlement.RESOURCE_LIST).disableIndicator().hideLabel(); body.add(zoomActionPanel); // ----------------------------------------- // ----------------------------------------- // Add Syncope (root topologynode) // ----------------------------------------- final TopologyNode syncopeTopologyNode = new TopologyNode(ROOT_NAME, ROOT_NAME, TopologyNode.Kind.SYNCOPE); syncopeTopologyNode.setX(origX); syncopeTopologyNode.setY(origY); final URI uri = WebClient.client(BaseRestClient.getSyncopeService()).getBaseURI(); syncopeTopologyNode.setHost(uri.getHost()); syncopeTopologyNode.setPort(uri.getPort()); body.add(topologyNodePanel("syncope", syncopeTopologyNode)); final Map<Serializable, Map<Serializable, TopologyNode>> connections = new HashMap<>(); final Map<Serializable, TopologyNode> syncopeConnections = new HashMap<>(); connections.put(syncopeTopologyNode.getKey(), syncopeConnections); // required to retrieve parent positions final Map<String, TopologyNode> servers = new HashMap<>(); final Map<String, TopologyNode> connectors = new HashMap<>(); // ----------------------------------------- // ----------------------------------------- // Add Connector Servers // ----------------------------------------- final ListView<URI> connectorServers = new ListView<URI>("connectorServers", csModel.getObject().getLeft()) { private static final long serialVersionUID = 6978621871488360380L; private final int size = csModel.getObject().getLeft().size() + 1; @Override protected void populateItem(final ListItem<URI> item) { int kx = size >= 4 ? 800 : (200 * size); int x = (int) Math.round(origX + kx * Math.cos(Math.PI + Math.PI * (item.getIndex() + 1) / size)); int y = (int) Math.round(origY + 100 * Math.sin(Math.PI + Math.PI * (item.getIndex() + 1) / size)); final URI location = item.getModelObject(); final String url = location.toASCIIString(); final TopologyNode topologynode = new TopologyNode(url, url, TopologyNode.Kind.CONNECTOR_SERVER); topologynode.setHost(location.getHost()); topologynode.setPort(location.getPort()); topologynode.setX(x); topologynode.setY(y); servers.put(String.class.cast(topologynode.getKey()), topologynode); item.add(topologyNodePanel("cs", topologynode)); syncopeConnections.put(url, topologynode); connections.put(url, new HashMap<Serializable, TopologyNode>()); } }; connectorServers.setOutputMarkupId(true); body.add(connectorServers); // ----------------------------------------- // ----------------------------------------- // Add File Paths // ----------------------------------------- final ListView<URI> filePaths = new ListView<URI>("filePaths", csModel.getObject().getRight()) { private static final long serialVersionUID = 6978621871488360380L; private final int size = csModel.getObject().getRight().size() + 1; @Override protected void populateItem(final ListItem<URI> item) { int kx = size >= 4 ? 800 : (200 * size); int x = (int) Math.round(origX + kx * Math.cos(Math.PI * (item.getIndex() + 1) / size)); int y = (int) Math.round(origY + 100 * Math.sin(Math.PI * (item.getIndex() + 1) / size)); final URI location = item.getModelObject(); final String url = location.toASCIIString(); final TopologyNode topologynode = new TopologyNode(url, url, TopologyNode.Kind.FS_PATH); topologynode.setHost(location.getHost()); topologynode.setPort(location.getPort()); topologynode.setX(x); topologynode.setY(y); servers.put(String.class.cast(topologynode.getKey()), topologynode); item.add(topologyNodePanel("fp", topologynode)); syncopeConnections.put(url, topologynode); connections.put(url, new HashMap<Serializable, TopologyNode>()); } }; filePaths.setOutputMarkupId(true); body.add(filePaths); // ----------------------------------------- // ----------------------------------------- // Add Connector Intances // ----------------------------------------- final List<List<ConnInstanceTO>> allConns = new ArrayList<>(connModel.getObject().values()); final ListView<List<ConnInstanceTO>> conns = new ListView<List<ConnInstanceTO>>("conns", allConns) { private static final long serialVersionUID = 697862187148836036L; @Override protected void populateItem(final ListItem<List<ConnInstanceTO>> item) { final int size = item.getModelObject().size() + 1; final ListView<ConnInstanceTO> conns = new ListView<ConnInstanceTO>("conns", item.getModelObject()) { private static final long serialVersionUID = 6978621871488360381L; @Override protected void populateItem(final ListItem<ConnInstanceTO> item) { final ConnInstanceTO conn = item.getModelObject(); final TopologyNode topologynode = new TopologyNode( conn.getKey(), conn.getDisplayName(), TopologyNode.Kind.CONNECTOR); // Define the parent note final TopologyNode parent = servers.get(conn.getLocation()); // Set the position int kx = size >= 6 ? 800 : (130 * size); final double hpos; if (conn.getLocation().startsWith(CONNECTOR_SERVER_LOCATION_PREFIX)) { hpos = Math.PI; } else { hpos = 0.0; } int x = (int) Math.round((parent == null ? origX : parent.getX()) + kx * Math.cos(hpos + Math.PI * (item.getIndex() + 1) / size)); int y = (int) Math.round((parent == null ? origY : parent.getY()) + 100 * Math.sin(hpos + Math.PI * (item.getIndex() + 1) / size)); topologynode.setConnectionDisplayName(conn.getBundleName()); topologynode.setX(x); topologynode.setY(y); connectors.put(String.class.cast(topologynode.getKey()), topologynode); item.add(topologyNodePanel("conn", topologynode)); // Update connections final Map<Serializable, TopologyNode> remoteConnections; if (connections.containsKey(conn.getLocation())) { remoteConnections = connections.get(conn.getLocation()); } else { remoteConnections = new HashMap<>(); connections.put(conn.getLocation(), remoteConnections); } remoteConnections.put(conn.getKey(), topologynode); } }; conns.setOutputMarkupId(true); item.add(conns); } }; conns.setOutputMarkupId(true); body.add(conns); // ----------------------------------------- // ----------------------------------------- // Add Resources // ----------------------------------------- final List<String> connToBeProcessed = new ArrayList<>(); for (ResourceTO resourceTO : resModel.getObject()) { final TopologyNode topologynode = new TopologyNode( resourceTO.getKey(), resourceTO.getKey(), TopologyNode.Kind.RESOURCE); final Map<Serializable, TopologyNode> remoteConnections; if (connections.containsKey(resourceTO.getConnector())) { remoteConnections = connections.get(resourceTO.getConnector()); } else { remoteConnections = new HashMap<>(); connections.put(resourceTO.getConnector(), remoteConnections); } remoteConnections.put(topologynode.getKey(), topologynode); if (!connToBeProcessed.contains(resourceTO.getConnector())) { connToBeProcessed.add(resourceTO.getConnector()); } } final ListView<String> resources = new ListView<String>("resources", connToBeProcessed) { private static final long serialVersionUID = 697862187148836038L; @Override protected void populateItem(final ListItem<String> item) { final String connectorKey = item.getModelObject(); final ListView<TopologyNode> innerListView = new ListView<TopologyNode>("resources", new ArrayList<>(connections.get(connectorKey).values())) { private static final long serialVersionUID = 1L; private final int size = getModelObject().size() + 1; @Override protected void populateItem(final ListItem<TopologyNode> item) { final TopologyNode topologynode = item.getModelObject(); final TopologyNode parent = connectors.get(connectorKey); // Set position int kx = size >= 16 ? 800 : (48 * size); int ky = size < 4 ? 100 : size < 6 ? 350 : 750; final double hpos; if (parent == null || parent.getY() < syncopeTopologyNode.getY()) { hpos = Math.PI; } else { hpos = 0.0; } int x = (int) Math.round((parent == null ? origX : parent.getX()) + kx * Math.cos(hpos + Math.PI * (item.getIndex() + 1) / size)); int y = (int) Math.round((parent == null ? origY : parent.getY()) + ky * Math.sin(hpos + Math.PI * (item.getIndex() + 1) / size)); topologynode.setX(x); topologynode.setY(y); item.add(topologyNodePanel("res", topologynode)); } }; innerListView.setOutputMarkupId(true); item.add(innerListView); } }; resources.setOutputMarkupId(true); body.add(resources); // ----------------------------------------- // ----------------------------------------- // Create connections // ----------------------------------------- final WebMarkupContainer jsPlace = new WebMarkupContainerNoVeil("jsPlace"); jsPlace.setOutputMarkupId(true); body.add(jsPlace); jsPlace.add(new Behavior() { private static final long serialVersionUID = 2661717818979056044L; @Override public void renderHead(final Component component, final IHeaderResponse response) { final StringBuilder jsPlumbConf = new StringBuilder(); jsPlumbConf.append(String.format(Locale.US, "activate(%.2f);", 0.68f)); for (String str : createConnections(connections)) { jsPlumbConf.append(str); } response.render(OnDomReadyHeaderItem.forScript(jsPlumbConf.toString())); } }); jsPlace.add(new AbstractAjaxTimerBehavior(Duration.seconds(2)) { private static final long serialVersionUID = -4426283634345968585L; @Override protected void onTimer(final AjaxRequestTarget target) { target.appendJavaScript("checkConnection()"); if (getUpdateInterval().seconds() < 5.0) { setUpdateInterval(Duration.seconds(5)); } else if (getUpdateInterval().seconds() < 10.0) { setUpdateInterval(Duration.seconds(10)); } else if (getUpdateInterval().seconds() < 15.0) { setUpdateInterval(Duration.seconds(15)); } else if (getUpdateInterval().seconds() < 20.0) { setUpdateInterval(Duration.seconds(20)); } else if (getUpdateInterval().seconds() < 30.0) { setUpdateInterval(Duration.seconds(30)); } else if (getUpdateInterval().seconds() < 60.0) { setUpdateInterval(Duration.seconds(60)); } } }); // ----------------------------------------- newlyCreatedContainer = new WebMarkupContainer("newlyCreatedContainer"); newlyCreatedContainer.setOutputMarkupId(true); body.add(newlyCreatedContainer); newlyCreated = new ListView<TopologyNode>("newlyCreated", new ArrayList<TopologyNode>()) { private static final long serialVersionUID = 1L; @Override protected void populateItem(final ListItem<TopologyNode> item) { item.add(topologyNodePanel("el", item.getModelObject())); } }; newlyCreated.setOutputMarkupId(true); newlyCreated.setReuseItems(true); newlyCreatedContainer.add(newlyCreated); } private List<String> createConnections(final Map<Serializable, Map<Serializable, TopologyNode>> targets) { List<String> list = new ArrayList<>(); for (Map.Entry<Serializable, Map<Serializable, TopologyNode>> source : targets.entrySet()) { for (Map.Entry<Serializable, TopologyNode> target : source.getValue().entrySet()) { list.add(String.format("connect('%s','%s','%s');", source.getKey(), target.getKey(), target.getValue().getKind())); } } return list; } private TopologyNodePanel topologyNodePanel(final String id, final TopologyNode node) { final TopologyNodePanel panel = new TopologyNodePanel(id, node); panel.setMarkupId(String.valueOf(node.getKey())); panel.setOutputMarkupId(true); final List<Behavior> behaviors = new ArrayList<>(); behaviors.add(new Behavior() { private static final long serialVersionUID = 1L; @Override public void renderHead(final Component component, final IHeaderResponse response) { response.render(OnDomReadyHeaderItem.forScript(String.format("setPosition('%s', %d, %d)", node.getKey(), node.getX(), node.getY()))); } }); behaviors.add(new AjaxEventBehavior(Constants.ON_CLICK) { private static final long serialVersionUID = -9027652037484739586L; @Override protected String findIndicatorId() { return StringUtils.EMPTY; } @Override protected void onEvent(final AjaxRequestTarget target) { togglePanel.toggleWithContent(target, node); target.appendJavaScript(String.format( "$('.window').removeClass(\"active-window\").addClass(\"inactive-window\"); " + "$(document.getElementById('%s'))." + "removeClass(\"inactive-window\").addClass(\"active-window\");", node.getKey())); } }); panel.add(behaviors.toArray(new Behavior[] {})); return panel; } @Override @SuppressWarnings("unchecked") public void onEvent(final IEvent<?> event) { super.onEvent(event); if (event.getPayload() instanceof CreateEvent) { final CreateEvent resourceCreateEvent = CreateEvent.class.cast(event.getPayload()); final TopologyNode node = new TopologyNode( resourceCreateEvent.getKey(), resourceCreateEvent.getDisplayName(), resourceCreateEvent.getKind()); newlyCreated.getModelObject().add(node); resourceCreateEvent.getTarget().add(newlyCreatedContainer); resourceCreateEvent.getTarget().appendJavaScript(String.format( "window.Wicket.WebSocket.send('" + "{\"kind\":\"%s\",\"target\":\"%s\",\"source\":\"%s\",\"scope\":\"%s\"}" + "');", SupportedOperation.ADD_ENDPOINT, resourceCreateEvent.getKey(), resourceCreateEvent.getParent(), resourceCreateEvent.getKind())); } } private static class WebMarkupContainerNoVeil extends WebMarkupContainer implements IAjaxIndicatorAware { private static final long serialVersionUID = 6883930486048460708L; WebMarkupContainerNoVeil(final String id) { super(id); } @Override public String getAjaxIndicatorMarkupId() { return StringUtils.EMPTY; } } }