/******************************************************************************* * Copyright (c) 2015-2017 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.openshift.internal.ui.server; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.lang.ObjectUtils; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.wst.server.core.IModule; import org.eclipse.wst.server.core.IServer; import org.eclipse.wst.server.core.IServerWorkingCopy; import org.eclipse.wst.server.core.ServerUtil; import org.jboss.ide.eclipse.as.wtp.core.server.behavior.ServerProfileModel; import org.jboss.tools.openshift.common.core.connection.ConnectionURL; import org.jboss.tools.openshift.common.core.connection.IConnection; import org.jboss.tools.openshift.common.core.utils.ExtensionUtils; import org.jboss.tools.openshift.common.core.utils.ProjectUtils; import org.jboss.tools.openshift.common.core.utils.StringUtils; import org.jboss.tools.openshift.common.core.utils.UrlUtils; import org.jboss.tools.openshift.common.core.utils.VariablesHelper; import org.jboss.tools.openshift.core.connection.Connection; import org.jboss.tools.openshift.core.server.OpenShiftServerBehaviour; import org.jboss.tools.openshift.core.server.OpenShiftServerUtils; import org.jboss.tools.openshift.core.server.adapter.IOpenshiftServerAdapterProfileDetector; import org.jboss.tools.openshift.internal.common.core.util.CollectionUtils; import org.jboss.tools.openshift.internal.core.util.ResourceUtils; import org.jboss.tools.openshift.internal.ui.OpenShiftUIActivator; import org.jboss.tools.openshift.internal.ui.treeitem.ObservableTreeItem; import org.jboss.tools.openshift.internal.ui.utils.ObservableTreeItemUtils; import com.openshift.restclient.OpenShiftException; import com.openshift.restclient.ResourceKind; import com.openshift.restclient.model.IBuildConfig; import com.openshift.restclient.model.IProject; import com.openshift.restclient.model.IResource; import com.openshift.restclient.model.IService; import com.openshift.restclient.model.route.IRoute; /** * @author Andre Dietisheim * @author Jeff Maury */ public class ServerSettingsWizardPageModel extends ServerResourceViewModel implements IResourceChangeListener { public static final String PROPERTY_DEPLOYPROJECT = "deployProject"; public static final String PROPERTY_PROJECTS = "projects"; public static final String PROPERTY_SOURCE_PATH = "sourcePath"; public static final String PROPERTY_POD_PATH = "podPath"; public static final String PROPERTY_USE_INFERRED_POD_PATH = "useInferredPodPath"; public static final String PROPERTY_SELECT_DEFAULT_ROUTE = "selectDefaultRoute"; public static final String PROPERTY_ROUTE = "route"; public static final String PROPERTY_ROUTES = "routes"; public static final String PROPERTY_OC_BINARY_STATUS = "OCBinaryStatus"; private static final String PROFILE_DETECTOR_EP_ID = "org.jboss.tools.openshift.core.serverAdapterProfileDetector"; protected org.eclipse.core.resources.IProject deployProject; protected List<org.eclipse.core.resources.IProject> projects = new ArrayList<>(); private String sourcePath; protected String podPath; protected String inferredPodPath = null; private IServerWorkingCopy server; private boolean selectDefaultRoute = false; private IRoute route; private Map<IProject, List<IRoute>> routesByProject = new HashMap<>(); private List<IRoute> serviceRoutes = new ArrayList<>(); private boolean isLoaded = false; private Map<IProject, List<IBuildConfig>> buildConfigsByProject; private boolean useInferredPodPath = true; private IStatus ocBinaryStatus = Status.OK_STATUS; protected ServerSettingsWizardPageModel(IResource resource, IRoute route, org.eclipse.core.resources.IProject deployProject, Connection connection, IServerWorkingCopy server) { this(resource, route, deployProject, connection, server, Status.OK_STATUS); } protected ServerSettingsWizardPageModel(IResource resource, IRoute route, org.eclipse.core.resources.IProject deployProject, Connection connection, IServerWorkingCopy server, IStatus ocBinaryStatus) { super(resource, connection); this.route = route; this.deployProject = deployProject; this.server = server; this.ocBinaryStatus = ocBinaryStatus; ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); } protected void update(IConnection connection, List<IConnection> connections, org.eclipse.core.resources.IProject deployProject, List<org.eclipse.core.resources.IProject> projects, String sourcePath, String podPath, boolean useInferredPodPath, IResource resource, List<ObservableTreeItem> resourceItems, IRoute route, boolean isSelectDefaultRoute, Map<IProject, List<IRoute>> routesByProject, IStatus ocBinaryStatus) { update(connection, connections, resource, resourceItems); updateProjects(projects); org.eclipse.core.resources.IProject oldDeployProject = this.deployProject; org.eclipse.core.resources.IProject newDeployProject = updateDeployProject(deployProject, projects, resource); sourcePath = updateSourcePath(sourcePath, newDeployProject, oldDeployProject); List<IRoute> newRoutes = updateRoutes(resource, routesByProject); updateRoute(route, newRoutes, resource); updateSelectDefaultRoute(isSelectDefaultRoute); firePropertyChange(PROPERTY_POD_PATH, this.podPath, this.podPath= podPath); updateOCBinaryStatus(ocBinaryStatus); firePropertyChange(PROPERTY_USE_INFERRED_POD_PATH, this.useInferredPodPath, this.useInferredPodPath = useInferredPodPath); firePropertyChange(PROPERTY_POD_PATH, this.podPath, this.podPath = podPath); } private void updateProjects(List<org.eclipse.core.resources.IProject> projects) { if(projects == this.projects) { return; // happens when other properties are changed, avoid unnecessary work } List<org.eclipse.core.resources.IProject> oldProjects = new ArrayList<>(this.projects); // ensure we're not operating on the same list List<org.eclipse.core.resources.IProject> newProjects = new ArrayList<>(); if (projects != null) { newProjects.addAll(projects); } this.projects.clear(); this.projects.addAll(newProjects); firePropertyChange(PROPERTY_PROJECTS, oldProjects, this.projects); } protected org.eclipse.core.resources.IProject updateDeployProject(org.eclipse.core.resources.IProject newDeployProject, List<org.eclipse.core.resources.IProject> projects, IResource resource) { if (newDeployProject == null || !projects.contains(newDeployProject)) { newDeployProject = getDeployProject(resource); if (newDeployProject == null) { newDeployProject = getProjectOrDefault(newDeployProject, projects); } } firePropertyChange(PROPERTY_DEPLOYPROJECT, this.deployProject, this.deployProject = newDeployProject); return this.deployProject; } protected String updateSourcePath(String sourcePath, org.eclipse.core.resources.IProject newDeployProject, org.eclipse.core.resources.IProject oldDeployProject) { if ((StringUtils.isEmpty(sourcePath) || newDeployProject != oldDeployProject) && ProjectUtils.isAccessible(newDeployProject)) { sourcePath = VariablesHelper.addWorkspacePrefix(newDeployProject.getFullPath().toString()); } firePropertyChange(PROPERTY_SOURCE_PATH, this.sourcePath, this.sourcePath = sourcePath); return sourcePath; } /** * Replaces choices in the route selector as needed. * If choices are replaced calls updateRoute() to reset its selected value. * @param routesByProject2 * @param resource * @return */ protected List<IRoute> updateRoutes(IResource resource, Map<IProject, List<IRoute>> routesByProject) { this.routesByProject = routesByProject; List<IRoute> oldRoutes = new ArrayList<>(this.serviceRoutes); List<IRoute> routes = getRoutes(resource); this.serviceRoutes.clear(); this.serviceRoutes.addAll(routes); resetSelectedRoute(); firePropertyChange(PROPERTY_ROUTES, oldRoutes, this.serviceRoutes); return routes; } // workaround needed for a possible (?) bug in databinding private void resetSelectedRoute() { if (this.route != null) { firePropertyChange(PROPERTY_ROUTE, this.route, this.route = null); } } /** * Updates the route that's selected. Chooses the route for the given resource * @param route * @return * @return */ protected IRoute updateRoute(IRoute route, List<IRoute> routes, IResource resource) { if (!isLoaded) { return null; } if (routes == null || routes.isEmpty()) { route = null; } else if(route == null || !routes.contains(route)) { if (resource instanceof IService) { route = ResourceUtils.getRouteFor((IService) resource, routes); } if(route == null || !routes.contains(route)) { route = routes.get(0); } } firePropertyChange(PROPERTY_ROUTE, this.route, this.route = route); return this.route; } private void updateSelectDefaultRoute(boolean selectDefaultRoute) { firePropertyChange(PROPERTY_SELECT_DEFAULT_ROUTE, this.selectDefaultRoute, this.selectDefaultRoute = selectDefaultRoute); } protected org.eclipse.core.resources.IProject getDeployProject(IResource resource) { //TODO: JBIDE-23490 check if association can be done for non service resources if (resource == null) { return null; } IProject openShiftProject = getOpenShiftProject(resource); List<IBuildConfig> buildConfigs = getBuildConfigs(openShiftProject); IBuildConfig buildConfig = ResourceUtils.getBuildConfigFor(resource, buildConfigs); return ResourceUtils.getWorkspaceProjectFor(buildConfig, getProjects()); } public void setDeployProject(org.eclipse.core.resources.IProject project) { update(getConnection(), getConnections(), project, this.projects, this.sourcePath, this.podPath, this.useInferredPodPath, getResource(), getResourceItems(), this.route, this.selectDefaultRoute, this.routesByProject, this.ocBinaryStatus); } public org.eclipse.core.resources.IProject getDeployProject() { return deployProject; } protected void setProjects(List<org.eclipse.core.resources.IProject> projects) { updateProjects(projects); } public List<org.eclipse.core.resources.IProject> getProjects() { return projects; } public void setSourcePath(String sourcePath) { update(getConnection(), getConnections(), this.deployProject, this.projects, sourcePath, this.podPath, this.useInferredPodPath, getResource(), getResourceItems(), this.route, this.selectDefaultRoute, this.routesByProject, this.ocBinaryStatus); } public String getSourcePath() { return sourcePath; } public void setPodPath(String podPath) { update(getConnection(), getConnections(), this.deployProject, this.projects, this.sourcePath, podPath, this.useInferredPodPath, getResource(), getResourceItems(), this.route, this.selectDefaultRoute, this.routesByProject, this.ocBinaryStatus); } public String getPodPath() { return podPath; } public boolean isUseInferredPodPath() { return this.useInferredPodPath; } public void setUseInferredPodPath(boolean useInferredPodPath) { update(getConnection(), getConnections(), this.deployProject, this.projects, this.sourcePath, this.podPath, useInferredPodPath, getResource(), getResourceItems(), this.route, this.selectDefaultRoute, this.routesByProject, this.ocBinaryStatus); } @Override public void setResource(IResource resource) { update(getConnection(), getConnections(), this.deployProject, this.projects, this.sourcePath, this.podPath, this.useInferredPodPath, resource, getResourceItems(), this.route, this.selectDefaultRoute, this.routesByProject, this.ocBinaryStatus); } protected org.eclipse.core.resources.IProject getProjectOrDefault(org.eclipse.core.resources.IProject project, List<org.eclipse.core.resources.IProject> projects) { if (project == null) { project = CollectionUtils.getFirstElement(projects); } return project; } @Override public void loadResources() { loadResources(getConnection()); } @Override public void loadResources(IConnection newConnection) { boolean serviceInitialized = this.resource != null; this.isLoaded = false; setProjects(loadProjects()); super.loadResources(newConnection); List<IProject> openshiftProjects = ObservableTreeItemUtils.getAllModels(IProject.class, getResourceItems()); setBuildConfigs(loadBuildConfigs(openshiftProjects, newConnection)); setProjects(loadProjects()); setRoutes(loadRoutes(getResourceItems())); this.isLoaded = true; if (serviceInitialized) { // initialized via a resource/route (launched via openshift explorer) updateDeployProject(getDeployProject(resource), projects, resource); } else { // initialized via a project (launched via new server wizard) updateResource(getService(deployProject, getResourceItems()), getResourceItems()); } update(getConnection(), getConnections(), this.deployProject, this.projects, this.sourcePath, this.podPath, this.useInferredPodPath, this.resource, getResourceItems(), this.route, selectDefaultRoute, this.routesByProject, this.ocBinaryStatus); } private List<IBuildConfig> getBuildConfigs(IProject project) { if (buildConfigsByProject == null) { return null; } return buildConfigsByProject.get(project); } protected IService getService(org.eclipse.core.resources.IProject project, List<ObservableTreeItem> services) { //JBIDE-23490: check default resource List<IService> allServices = ObservableTreeItemUtils.getAllModels(IService.class, services.stream().flatMap(ObservableTreeItemUtils::flatten).collect(Collectors.toList())); return allServices.stream() .filter(service -> ObjectUtils.equals(project, getDeployProject(service))) .findFirst().orElse(null); } private Map<IProject, List<IBuildConfig>> loadBuildConfigs(List<IProject> projects, IConnection connection) { if (projects == null || projects.isEmpty()) { return Collections.emptyMap(); } Connection c2 = (Connection)connection; return projects.stream() .collect(Collectors.toMap( project -> project, project -> { List<IBuildConfig> buildConfigs = c2.getResources(ResourceKind.BUILD_CONFIG, project.getName()); return buildConfigs; })); } private void setBuildConfigs(Map<IProject, List<IBuildConfig>> buildConfigsByProject) { this.buildConfigsByProject = buildConfigsByProject; } protected List<org.eclipse.core.resources.IProject> loadProjects() { List<org.eclipse.core.resources.IProject> p = ProjectUtils.getAllAccessibleProjects(); return p; } protected Map<IProject, List<IRoute>> loadRoutes(List<ObservableTreeItem> serviceItems) { List<IProject> projects = ObservableTreeItemUtils.getAllModels(IProject.class, serviceItems); return projects.stream() .collect(Collectors.toMap(project -> project, project -> project.getResources(ResourceKind.ROUTE))); } public void updateServer() throws OpenShiftException { updateServer(server); } private void updateServer(IServerWorkingCopy server) throws OpenShiftException { String connectionUrl = getConnectionUrl(getConnection()); String serverName = OpenShiftServerUtils.getServerName(getResource(), getConnection()); String host = getHost(getRoute(), getConnection()); String routeURL = getRouteURL(isSelectDefaultRoute(), getRoute()); String podPath = useInferredPodPath ? "": this.podPath; OpenShiftServerUtils.updateServer( serverName, host, connectionUrl, getResource(), sourcePath, podPath, deployProject, routeURL, server); server.setAttribute(OpenShiftServerUtils.SERVER_START_ON_CREATION, true); // Set the profile String profile = getProfileId(); ServerProfileModel.setProfile(server, profile); updateServerProject(connectionUrl, getResource(), sourcePath, podPath, routeURL, deployProject); IModule[] matchingModules = ServerUtil.getModules(deployProject); if( matchingModules != null && matchingModules.length > 0) { try { server.modifyModules(matchingModules, new IModule[]{}, new NullProgressMonitor()); } catch(CoreException ce) { throw new OpenShiftException(ce, "Could not get add modules to server ", server.getName()); } } } protected void updateServerProject(String connectionUrl, IResource resource, String sourcePath, String podPath, String routeURL, org.eclipse.core.resources.IProject deployProject) { OpenShiftServerUtils.updateServerProject( connectionUrl, resource, sourcePath, podPath, routeURL, deployProject); } protected String getProfileId() { Collection<IConfigurationElement> configurationElements = ExtensionUtils .getExtensionConfigurations(PROFILE_DETECTOR_EP_ID); for (IConfigurationElement configurationElement : configurationElements) { try { Object extension = configurationElement.createExecutableExtension("class"); if (extension instanceof IOpenshiftServerAdapterProfileDetector) { IOpenshiftServerAdapterProfileDetector detector = (IOpenshiftServerAdapterProfileDetector)extension; if (detector.detect(getConnection(), getResource(), getDeployProject())) { return detector.getProfile(); } } } catch (CoreException e) { throw new RuntimeException(e); } } return OpenShiftServerBehaviour.PROFILE_OPENSHIFT3; } protected String getHost(IRoute route) { return getHost(route, null); } protected String getHost(IRoute route, IConnection connection) { if (route != null) { return UrlUtils.getHost(route.getURL()); } else if(connection != null) { return UrlUtils.getHost(connection.getHost()); } return ""; } protected String getRouteURL(boolean isSelectDefaultRoute, IRoute route) { if (!isSelectDefaultRoute || route == null) { return null; } return route.getURL(); } public boolean isSelectDefaultRoute() { return selectDefaultRoute; } public void setSelectDefaultRoute(boolean selectDefaultRoute) { update(getConnection(), getConnections(), this.deployProject, this.projects, this.sourcePath, this.podPath, this.useInferredPodPath, getResource(), getResourceItems(), this.route, selectDefaultRoute, this.routesByProject, this.ocBinaryStatus); } protected void setRoutes(Map<IProject, List<IRoute>> routesByProject) { updateRoutes(resource, routesByProject); } /** * Returns the routes that match the resource that's currently selected. * * @return the routes that match the selected resource * * @see #getResource() * @see #getAllRoutes(IService) */ public List<IRoute> getRoutes() { if (getResource() == null) { return Collections.emptyList(); } return getRoutes(getResource()); } protected List<IRoute> getRoutes(IResource resource) { if (resource instanceof IService) { return ResourceUtils.getRoutesFor((IService) resource, getAllRoutes(resource)); } else { return Collections.emptyList(); } } public IRoute getRoute() { if (!isLoaded) { return null; } // reveal selected route only once model is loaded return route; } public void setRoute(IRoute newRoute) { update(getConnection(), getConnections(), this.deployProject, this.projects, this.sourcePath, this.podPath, this.useInferredPodPath, getResource(), getResourceItems(), newRoute, this.selectDefaultRoute, this.routesByProject, this.ocBinaryStatus); } protected List<IRoute> getAllRoutes(IRoute route) { IProject project = getOpenShiftProject(route); if (project == null) { return Collections.emptyList(); } return getAllRoutes(project); } protected Map<IProject, List<IRoute>> getAllRoutes() { return routesByProject; } protected List<IRoute> getAllRoutes(IResource resource) { IProject project = resource.getProject(); if (project == null) { return Collections.emptyList(); } return getAllRoutes(project); } protected List<IRoute> getAllRoutes(IProject project) { return routesByProject.get(project); } protected IProject getOpenShiftProject(IRoute route) { return route.getProject(); } public IServer saveServer(IProgressMonitor monitor) throws CoreException { return server.save(true, monitor); } protected String getConnectionUrl(IConnection connection) { ConnectionURL connectionUrl; try { connectionUrl = ConnectionURL.forConnection(connection); return connectionUrl.toString(); } catch (UnsupportedEncodingException | MalformedURLException e) { throw new OpenShiftException(e, "Could not get url for connection {0}", connection.getHost()); } } private void updateOCBinaryStatus(IStatus ocBinaryStatus) { firePropertyChange(PROPERTY_OC_BINARY_STATUS, this.ocBinaryStatus, this.ocBinaryStatus = ocBinaryStatus); } public IStatus getOCBinaryStatus() { return ocBinaryStatus; } public void setOCBinaryStatus(IStatus ocBinaryStatus) { update(getConnection(), getConnections(), this.deployProject, this.projects, this.sourcePath, this.podPath, this.useInferredPodPath, getResource(), getResourceItems(), route, this.selectDefaultRoute, this.routesByProject, ocBinaryStatus); } protected IServerWorkingCopy getServer() { return server; } @Override public void resourceChanged(IResourceChangeEvent event) { boolean[] needReload = new boolean[1]; try { event.getDelta().accept((delta) -> { if ((delta.getResource().getType() == org.eclipse.core.resources.IResource.PROJECT) && ((delta.getKind() == IResourceDelta.ADDED) || (delta.getKind() == IResourceDelta.REMOVED))) { needReload[0] = true; } return true; }, true); if (needReload[0]) { setProjects(loadProjects()); } } catch (CoreException e) { OpenShiftUIActivator.log(Status.ERROR, e.getLocalizedMessage(), e); } } /* (non-Javadoc) * @see org.jboss.tools.common.databinding.ObservablePojo#dispose() */ @Override public void dispose() { super.dispose(); ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); } }