/******************************************************************************* * Copyright (c) May 24, 2011 Zend Technologies Ltd. * All rights reserved. This program and the accompanying materials * are 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 *******************************************************************************/ package org.zend.sdklib.application; //import java.io.File; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.regex.Pattern; import org.zend.sdklib.internal.application.ZendConnection; import org.zend.sdklib.mapping.IMappingLoader; import org.zend.sdklib.mapping.IVariableResolver; import org.zend.sdklib.repository.site.Application; import org.zend.sdklib.target.ITargetLoader; import org.zend.sdklib.target.IZendTarget; import org.zend.webapi.core.WebApiClient; import org.zend.webapi.core.WebApiException; import org.zend.webapi.core.connection.data.ApplicationInfo; import org.zend.webapi.core.connection.data.ApplicationsList; import org.zend.webapi.core.connection.data.VhostInfo; import org.zend.webapi.core.connection.data.VhostsList; import org.zend.webapi.core.connection.data.values.ZendServerVersion; import org.zend.webapi.core.connection.request.NamedInputStream; import org.zend.webapi.core.progress.BasicStatus; import org.zend.webapi.core.progress.StatusCode; /** * Utility class which provides methods to perform operations on application. * * @author Wojciech Galanciak, 2011 * */ public class ZendApplication extends ZendConnection { public static final String TEMP_PREFIX = "ZendStudioDeployment"; private final static ZendServerVersion v6_2_0 = ZendServerVersion .byName("6.2.0"); private IVariableResolver variableResolver; public ZendApplication() { super(); } public ZendApplication(IMappingLoader mappingLoader) { super(mappingLoader); } public ZendApplication(ITargetLoader loader) { super(loader); } public ZendApplication(ITargetLoader loader, IMappingLoader mappingLoader) { super(loader, mappingLoader); } public void setVariableResolver(IVariableResolver variableResolver) { this.variableResolver = variableResolver; } /** * Provides information about status of specified application(s) in selected * target. * * @param targetId * @param applicationIds * - array of application id(s) for which status should be * checked * @return instance of {@link ApplicationsList} or <code>null</code> if * there where problems with connections or target with specified id * does not exist */ public ApplicationsList getStatus(String targetId, String... applicationIds) { try { WebApiClient client = getClient(targetId); applicationIds = applicationIds == null ? new String[0] : applicationIds; notifier.statusChanged(new BasicStatus( StatusCode.STARTING, "Application Status", "Retrieving application status(es) from selected server...", -1)); ApplicationsList result = client .applicationGetStatus(applicationIds); notifier.statusChanged(new BasicStatus(StatusCode.STOPPING, "Application Status", "Application status(es) retrievied successfully. ")); return result; } catch (MalformedURLException e) { notifier.statusChanged(new BasicStatus(StatusCode.ERROR, "Application Status", "Error during retrieving application status from '" + targetId + "'", e)); log.error(e); } catch (WebApiException e) { notifier.statusChanged(new BasicStatus(StatusCode.ERROR, "Application Status", "Error during retrieving application status from '" + targetId + "'", e)); log.error("Error during retrieving application status from '" + targetId + "'."); log.error("\tpossible error: " + e.getMessage()); } return null; } /** * Provides information about available virtual hosts on the specified * target. * * @param targetId * @param vhosts * - array of vhost id(s) for which status should be checked * @return instance of {@link VhostsList} or <code>null</code> if there * where problems with connections or target with specified id does * not exist */ public VhostsList getVhosts(String targetId, String... vhosts) { try { WebApiClient client = getClient(targetId); notifier.statusChanged(new BasicStatus(StatusCode.STARTING, "Virtual Host Status", "Retrieving virtual hosts of selected server...", -1)); VhostsList result = client.vhostGetStatus(vhosts); notifier.statusChanged(new BasicStatus(StatusCode.STOPPING, "Virtual Host Status", "Virtual hosts retrievied successfully. ")); return result; } catch (MalformedURLException e) { notifier.statusChanged(new BasicStatus(StatusCode.ERROR, "Virtual Host Status", "Error during retrieving virtual hosts status from '" + targetId + "'", e)); log.error(e); } catch (WebApiException e) { notifier.statusChanged(new BasicStatus(StatusCode.ERROR, "Virtual Host Status", "Error during retrieving virtual hosts status from '" + targetId + "'", e)); log.error("Error during retrieving virtual hosts status from '" + targetId + "'."); log.error("\tpossible error: " + e.getMessage()); } return null; } /** * Deploys a new application to the specified target. * * @param path * - path to project location or application package * @param basePath * - base path to deploy the application to. relative to the * host/vhost * @param targetId * - target id * @param propertiesFile * - path to properties file which consists user deployment * parameters * @param appName * - application name * @param ignoreFailures * - ignore failures during staging if only some servers reported * failures * @param vhostURL * - virtual host's URL, if such a virtual host wasn't already * created by Zend Server it will be created * @param defaultServer * - deploy the application on the default server; the base URL * host provided will be ignored and replaced with * <default-server>. * @return instance of {@link ApplicationInfo} or <code>null</code> if there * where problems with connections or target with specified id does * not exist or there is no package/project in specified path */ public ApplicationInfo deploy(String path, String basePath, String targetId, String propertiesFile, String appName, Boolean ignoreFailures, URL vhostURL, Boolean defaultServer) { Map<String, String> userParams = null; if (propertiesFile != null) { notifier.statusChanged(new BasicStatus(StatusCode.STARTING, "Deploying", "Reading user parameters properites file...", -1)); File propsFile = new File(propertiesFile); if (propsFile.exists()) { userParams = getUserParameters(propsFile); } else { userParams = getUserParameters(propertiesFile); } } notifier.statusChanged(new BasicStatus(StatusCode.STOPPING, "Deploying", "Reading user parametes is completed.")); return deploy(path, basePath, targetId, userParams, appName, ignoreFailures, vhostURL, defaultServer); } /** * Deploys a new application to the specified target. * * @param inputStream * - input stream for application package * @param application * - application description from repository site * @param targetId * - target id * @param propertiesFile * - path to properties file which consists user deployment * parameters * @param appName * - application name * @param ignoreFailures * - ignore failures during staging if only some servers reported * failures * @param vhostURL * - the virtual host's URL to use, if such a virtual host wasn't * already created by Zend Server it will be created * @param defaultServer * - deploy the application on the default server; the base URL * host provided will be ignored and replaced with * <default-server>. * @return instance of {@link ApplicationInfo} or <code>null</code> if there * where problems with connections or target with specified id does * not exist or there is no package/project in specified path */ public ApplicationInfo deploy(InputStream inputStream, Application application, String basePath, String targetId, String propertiesFile, String appName, Boolean ignoreFailures, URL vhostURL, Boolean defaultServer) { if (inputStream != null && application != null) { String path = getPackagePath(inputStream, application); if (path != null) { return deploy(path, basePath, targetId, propertiesFile, appName, ignoreFailures, vhostURL, defaultServer); } } return null; } /** * Deploys a new application to the specified target. * * @param path * - path to project location or application package * @param basePath * - base path to deploy the application to. relative to * host/vhost * @param targetId * - target id * @param userParams * - map with user parameters (key and value) * * @param appName * - application name * @param ignoreFailures * - ignore failures during staging if only some servers reported * failures * @param vhostURL * - The virtual host to use, if such a virtual host wasn't * already created by Zend Server - it is created. * @param defaultServer * - deploy the application on the default server; the base URL * host provided will be ignored and replaced with * <default-server>. * @return instance of {@link ApplicationInfo} or <code>null</code> if there * where problems with connections or target with specified id does * not exist or there is no package/project in specified path */ public ApplicationInfo deploy(String path, String basePath, String targetId, Map<String, String> userParams, String appName, Boolean ignoreFailures, URL vhostURL, Boolean defaultServer) { deleteFile(getTempFile(path)); File zendPackage = createPackage(path); try { WebApiClient client = getClient(targetId); String baseUrl = resolveBaseUrl(new File(path), basePath, defaultServer, vhostURL); if (appName == null) { String[] segments = baseUrl.split("/"); appName = segments.length > 0 ? segments[segments.length - 1] : null; } notifier.statusChanged( new BasicStatus(StatusCode.STARTING, "Deploying", "Deploying application to server...", -1)); boolean vhost = vhostURL != null; // Update parameters for Zend Server version >= 6.2.0 base on // virtual hosts IZendTarget target = getTargetById(targetId); if (target != null) { ZendServerVersion version = ZendServerVersion.byName(target .getProperty(IZendTarget.SERVER_VERSION)); if (version.compareTo(v6_2_0) >= 0) { VhostInfo virtualHost = getVirtualHost(targetId, baseUrl); if (virtualHost != null) { defaultServer = virtualHost.isDefaultVhost(); vhost = false; } else { vhost = true; defaultServer = false; } } } if (vhost && baseUrl.startsWith("https://")) { notifier.statusChanged(new BasicStatus( StatusCode.ERROR, "Deployment Error", "Specified virtual host does not exist. HTTPS or SSL secure virtual hosts may only be created using Zend Server GUI.")); return null; } if (zendPackage != null) { ApplicationInfo result = client.applicationDeploy( new NamedInputStream(zendPackage), baseUrl, ignoreFailures, userParams, appName, vhost, defaultServer); notifier.statusChanged(new BasicStatus(StatusCode.STOPPING, "Deploying", "Application deployed successfully")); deleteFile(getTempFile(path)); return result; } } catch (MalformedURLException e) { notifier.statusChanged(new BasicStatus(StatusCode.ERROR, "Deploying", "Error during deploying application to '" + targetId + "'", e)); log.error(e); deleteFile(getTempFile(path)); } catch (WebApiException e) { notifier.statusChanged(new BasicStatus(StatusCode.ERROR, "Deploying", "Error during deploying application to '" + targetId + "'", e)); log.error("Error during deploying application to '" + targetId + "':"); log.error("\tpossible error: " + e.getMessage()); } return null; } /** * Deploys a new application to the specified target. * * @param inputStream * - input stream for application package * @param application * - application description from repository site * @param basePath * - base path to deploy the application to. relative to * host/vhost * @param targetId * - target id * @param userParams * - map with user parameters (key and value) * @param appName * - application name * @param ignoreFailures * - ignore failures during staging if only some servers reported * failures * @param vhostURL * - The virtual host to use, if such a virtual host wasn't * already created by Zend Server - it is created. * @param defaultServer * - deploy the application on the default server; the base URL * host provided will be ignored and replaced with * <default-server>. * @return instance of {@link ApplicationInfo} or <code>null</code> if there * where problems with connections or target with specified id does * not exist or there is no package/project in specified path */ public ApplicationInfo deploy(InputStream inputStream, Application application, String basePath, String targetId, Map<String, String> userParams, String appName, Boolean ignoreFailures, URL vhostURL, Boolean defaultServer) { if (inputStream != null && application != null) { String path = getPackagePath(inputStream, application); if (path != null) { return deploy(path, basePath, targetId, userParams, appName, ignoreFailures, vhostURL, defaultServer); } } return null; } /** * Redeploys an existing application, whether in order to fix a problem or * to reset an installation. * * @param targetId * - target id * @param appId * - application id * @param servers * - array of server id(s) on which application should be * redeployed * @param ignoreFailures * - ignore failures during staging if only some servers reported * failures * @return instance of {@link ApplicationInfo} or <code>null</code> if there * where problems with connections or target with specified id does * not exist or there is no application with specified id in the * target */ public ApplicationInfo redeploy(String targetId, String appId, String[] servers, boolean ignoreFailures) { try { WebApiClient client = getClient(targetId); int appIdint = Integer.parseInt(appId); return client.applicationSynchronize(appIdint, ignoreFailures, servers); } catch (MalformedURLException e) { log.error(e); } catch (NumberFormatException e) { log.error(e.getMessage()); } catch (WebApiException e) { log.error("Error during redeploying application to '" + targetId + "':"); log.error("\tpossible error: " + e.getMessage()); } return null; } /** * Removes/undeploys an existing application. * * @param targetId * - target id * @param appId * - application id * @return instance of {@link ApplicationInfo} or <code>null</code> if there * where problems with connections or target with specified id does * not exist or there is no application with specified id in the * target */ public ApplicationInfo remove(String targetId, String appId) { try { WebApiClient client = getClient(targetId); int appIdint = Integer.parseInt(appId); return client.applicationRemove(appIdint); } catch (MalformedURLException e) { log.error(e); } catch (NumberFormatException e) { log.error(e.getMessage()); } catch (WebApiException e) { log.error("Error during removing application from '" + targetId + "':"); log.error("\tpossible error: " + e.getMessage()); } return null; } /** * Updates/redeploys an existing application. * * @param path * - path to project location or application package * @param targetId * - target id * @param appId * - application id * @param propertiesFile * - path to properties file which consists user deployment * parameters * @param ignoreFailures * - ignore failures during staging if only some servers reported * failures * @return instance of {@link ApplicationInfo} or <code>null</code> if there * where problems with connections or target with specified id does * not exist or there is no package/project in specified path */ public ApplicationInfo update(String path, String targetId, String appId, String propertiesFile, Boolean ignoreFailures) { Map<String, String> userParams = null; if (propertiesFile != null) { notifier.statusChanged(new BasicStatus(StatusCode.STARTING, "Updating", "Reading user parameters properites file...", -1)); File propsFile = new File(propertiesFile); if (propsFile.exists()) { userParams = getUserParameters(propsFile); } } notifier.statusChanged(new BasicStatus(StatusCode.STOPPING, "Updating", "Reading user parametes is completed.")); return update(path, targetId, appId, userParams, ignoreFailures); } /** * Updates/redeploys an existing application. * * @param inputStream * - input stream for application package * @param application * - application description from repository site * @param targetId * - target id * @param appId * - application id * @param propertiesFile * - path to properties file which consists user deployment * parameters * @param ignoreFailures * - ignore failures during staging if only some servers reported * failures * @return instance of {@link ApplicationInfo} or <code>null</code> if there * where problems with connections or target with specified id does * not exist or there is no package/project in specified path */ public ApplicationInfo update(InputStream inputStream, Application application, String targetId, String appId, String propertiesFile, Boolean ignoreFailures) { if (inputStream != null && application != null) { String path = getPackagePath(inputStream, application); if (path != null) { return update(path, targetId, appId, propertiesFile, ignoreFailures); } } return null; } /** * Updates/redeploys an existing application. * * @param path * - path to project location or application package * @param targetId * - target id * @param appId * - application id * @param userParams * - map with user parameters (key and value) * @param ignoreFailures * - ignore failures during staging if only some servers reported * failures * @return instance of {@link ApplicationInfo} or <code>null</code> if there * where problems with connections or target with specified id does * not exist or there is no package/project in specified path */ public ApplicationInfo update(String path, String targetId, String appId, Map<String, String> userParams, Boolean ignoreFailures) { File zendPackage = createPackage(path); try { if (zendPackage != null) { int appIdint = Integer.parseInt(appId); WebApiClient client = getClient(targetId); notifier.statusChanged( new BasicStatus(StatusCode.STARTING, "Updating", "Updating application on server...", -1)); ApplicationInfo result = client.applicationUpdate(appIdint, new NamedInputStream(zendPackage), ignoreFailures, userParams); notifier.statusChanged(new BasicStatus(StatusCode.STOPPING, "Updating", "Application updated successfully")); return result; } } catch (MalformedURLException e) { notifier.statusChanged(new BasicStatus(StatusCode.ERROR, "Updating", "Error during updating application on '" + targetId + "'", e)); log.error(e); } catch (WebApiException e) { notifier.statusChanged(new BasicStatus(StatusCode.ERROR, "Updating", "Error during updating application on '" + targetId + "'", e)); log.error("Error during updating application on '" + targetId + "':"); log.error("\tpossible error: " + e.getMessage()); } finally { deleteFile(getTempFile(path)); } return null; } /** * Updates/redeploys an existing application. * * @param inputStream * - input stream for application package * @param application * - application description from repository site * @param targetId * - target id * @param appId * - application id * @param userParams * - map with user parameters (key and value) * @param ignoreFailures * - ignore failures during staging if only some servers reported * failures * @return instance of {@link ApplicationInfo} or <code>null</code> if there * where problems with connections or target with specified id does * not exist or there is no package/project in specified path */ public ApplicationInfo update(InputStream inputStream, Application application, String targetId, String appId, Map<String, String> userParams, Boolean ignoreFailures) { if (inputStream != null && application != null) { String path = getPackagePath(inputStream, application); if (path != null) { return update(path, targetId, appId, userParams, ignoreFailures); } } return null; } private Map<String, String> getUserParameters(File propsFile) { Map<String, String> result = null; Properties p = new Properties(); try { p.load(new FileInputStream(propsFile)); Enumeration<?> e = p.propertyNames(); if (e.hasMoreElements()) { result = new HashMap<String, String>(); } while (e.hasMoreElements()) { String key = (String) e.nextElement(); result.put(key, p.getProperty(key)); } } catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error(e); } return result; } public Map<String, String> getUserParameters(String propertiesString) { Map<String, String> result = new LinkedHashMap<String, String>(3); if (Pattern .matches("[[^,=]=[^,=]*][,[^,=]*=[^,=]*]*", propertiesString)) { final String[] split = propertiesString.split(","); for (String token : split) { final String[] val = token.split("="); if (val.length == 2) { result.put(val[0], val[1]); } else { log.error("Error parsing property string , skipping token " + token); } } } else { log.error("Error parsing property string " + propertiesString); } return result; } private File createPackage(String path) { File file = new File(path); if (!file.exists()) { log.error("Path does not exist: " + file); return null; } if (file.isDirectory()) { File tempFile = getTempFile(path); if (tempFile.isDirectory()) { File[] children = tempFile.listFiles(); if (children.length == 1) { return children[0]; } } return getPackageBuilder(path, variableResolver) .createDeploymentPackage(tempFile); } else { return file; } } private File getTempFile(String path) { String tempDir = System.getProperty("java.io.tmpdir"); path = path.replace("\\", "/"); String suffix = path.substring(path.lastIndexOf("/") + 1); File tempFile = new File(tempDir + File.separator + TEMP_PREFIX + suffix); if (!tempFile.exists()) { tempFile.mkdir(); } return tempFile; } private boolean deleteFile(File file) { if (file == null || !file.exists()) { return true; } if (file.isDirectory()) { String[] children = file.list(); for (int i = 0; i < children.length; i++) { boolean result = deleteFile(new File(file, children[i])); if (!result) { return false; } } } return file.delete(); } private String getPackagePath(InputStream in, Application app) { String packageName = getPackageName(app.getUrl()); File tempFile = getTempFile(packageName); tempFile = new File(tempFile.getAbsolutePath() + new Random().nextInt()); if (!tempFile.exists()) { tempFile.mkdir(); } File packageFile = new File(tempFile, packageName); OutputStream out = null; notifier.statusChanged(new BasicStatus(StatusCode.STARTING, "Package Downloading", "Downloading package from repository...", -1)); try { if (!packageFile.exists()) { packageFile.createNewFile(); } out = new FileOutputStream(packageFile); byte[] buffer = new byte[2048]; int len = in.read(buffer); while (len != -1) { out.write(buffer, 0, len); len = in.read(buffer); } } catch (IOException e) { log.error(e); return null; } finally { closeStream(in); closeStream(out); } notifier.statusChanged(new BasicStatus(StatusCode.STOPPING, "Package Downloading", "Package is downloaded from repository successfully.", -1)); return packageFile.getAbsolutePath(); } private String getPackageName(String url) { url = url.replace("\\", "/"); return url.substring(url.lastIndexOf("/") + 1); } private VhostInfo getVirtualHost(String targetId, String baseUrl) throws MalformedURLException { VhostsList hostsList = getVhosts(targetId); URL applicationUrl = new URL(baseUrl); if (hostsList != null) { List<VhostInfo> infos = hostsList.getVhosts(); if (infos != null) { VhostInfo defaultVhost = null; for (VhostInfo vhostInfo : infos) { if (vhostInfo.isDefaultVhost()) { defaultVhost = vhostInfo; } String protocol = vhostInfo.isSSL() ? "https" : "http"; String host = vhostInfo.getName(); if ("*".equals(host)) { host = getTargetById(targetId).getHost().getHost(); } int port = vhostInfo.getPort(); URL vhostUrl = new URL(protocol, host, port, applicationUrl.getFile()); if (baseUrl.equals(vhostUrl.toString())) { return vhostInfo; } // If it is https with a default port then try without a // port if (vhostInfo.isSSL() && port == 443) { vhostUrl = new URL(protocol, host, -1, applicationUrl.getFile()); if (baseUrl.equals(vhostUrl.toString())) { return vhostInfo; } } // If it is http with a default port then try without a port if (!vhostInfo.isSSL() && port == 80) { vhostUrl = new URL(protocol, host, -1, applicationUrl.getFile()); if (baseUrl.equals(vhostUrl.toString())) { return vhostInfo; } } } // Use default vhost if there is no perfect match return defaultVhost; } } return null; } private String resolveBaseUrl(File path, String basePath, Boolean defaultServer, URL vhostURL) throws MalformedURLException { URL resolvedURL = null; if (basePath == null) { basePath = path.getName(); int index = basePath.indexOf('-'); basePath = "/" + basePath.substring(0, index); } if (vhostURL != null) { resolvedURL = new URL(vhostURL.getProtocol(), vhostURL.getHost(), vhostURL.getPort(), basePath); } else { resolvedURL = new URL("http", "default-server", -1, basePath); } return resolvedURL.toString(); } }