com.microsoft.azuretools.utils.WebAppUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.azuretools.utils.WebAppUtils.java

Source

/*
 * Copyright (c) Microsoft Corporation
 *   <p/>
 *  All rights reserved.
 *   <p/>
 *  MIT License
 *   <p/>
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 *  documentation files (the "Software"), to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
 *  to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *  <p/>
 *  The above copyright notice and this permission notice shall be included in all copies or substantial portions of
 *  the Software.
 *   <p/>
 *  THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 *  THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 *  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 */

package com.microsoft.azuretools.utils;

import com.microsoft.azure.management.Azure;
import com.microsoft.azure.management.appservice.*;
import com.microsoft.azure.management.resources.Location;
import com.microsoft.azure.management.resources.ResourceGroup;
import com.microsoft.azuretools.Constants;
import com.microsoft.azuretools.authmanage.AuthMethodManager;
import com.microsoft.azuretools.authmanage.models.SubscriptionDetail;
import com.microsoft.azuretools.azurecommons.helpers.AzureCmdException;
import com.microsoft.azuretools.sdkmanage.AzureManager;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

import java.io.*;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.concurrent.CancellationException;

/**
 * Created by vlashch on 1/19/17.
 */
public class WebAppUtils {

    private static final String ftpRootPath = "/site/wwwroot/";
    private static final String ftpWebAppsPath = ftpRootPath + "webapps/";
    private static String jdkFolderName = "jdk";
    private static final String ftpJdkPath = ftpRootPath + jdkFolderName;
    private static String aspScriptName = "getjdk.aspx";
    private static String webConfigFilename = "web.config";
    private static String reportFilename = "report.txt";
    private static String statusFilename = "status.txt";

    public static FTPClient getFtpConnection(PublishingProfile pp) throws IOException {

        FTPClient ftp = new FTPClient();

        System.out.println("\t\t" + pp.ftpUrl());
        System.out.println("\t\t" + pp.ftpUsername());
        System.out.println("\t\t" + pp.ftpPassword());

        URI uri = URI.create("ftp://" + pp.ftpUrl());
        ftp.connect(uri.getHost(), 21);
        final int replyCode = ftp.getReplyCode();
        if (!FTPReply.isPositiveCompletion(replyCode)) {
            ftp.disconnect();
            throw new ConnectException("Unable to connect to FTP server");
        }

        if (!ftp.login(pp.ftpUsername(), pp.ftpPassword())) {
            throw new ConnectException("Unable to login to FTP server");
        }

        ftp.setControlKeepAliveTimeout(Constants.connection_read_timeout_ms);
        ftp.setFileType(FTP.BINARY_FILE_TYPE);
        ftp.enterLocalPassiveMode();//Switch to passive mode

        return ftp;
    }

    public static void deployArtifact(String artifactName, String artifactPath, PublishingProfile pp,
            boolean toRoot, IProgressIndicator indicator) throws IOException {
        FTPClient ftp = null;
        InputStream input = null;
        try {
            if (indicator != null)
                indicator.setText("Connecting to FTP server...");

            ftp = getFtpConnection(pp);

            if (indicator != null)
                indicator.setText("Uploading the application...");
            input = new FileInputStream(artifactPath);
            if (toRoot) {
                WebAppUtils.removeFtpDirectory(ftp, ftpWebAppsPath + "ROOT", indicator);
                ftp.deleteFile(ftpWebAppsPath + "ROOT.war");
                ftp.storeFile(ftpWebAppsPath + "ROOT.war", input);
            } else {
                WebAppUtils.removeFtpDirectory(ftp, ftpWebAppsPath + artifactName, indicator);
                ftp.deleteFile(artifactName + ".war");
                boolean success = ftp.storeFile(ftpWebAppsPath + artifactName + ".war", input);
                if (!success) {
                    int rc = ftp.getReplyCode();
                    throw new IOException("FTP client can't store the artifact, reply code: " + rc);
                }
            }
            if (indicator != null)
                indicator.setText("Logging out of FTP server...");
            ftp.logout();
        } finally {
            if (input != null)
                input.close();
            if (ftp != null && ftp.isConnected()) {
                ftp.disconnect();
            }
        }
    }

    public static void removeFtpDirectory(FTPClient ftpClient, String path, IProgressIndicator pi)
            throws IOException {
        String prefix = "Removing from FTP server: ";
        FTPFile[] subFiles = ftpClient.listFiles(path);
        if (subFiles.length > 0) {
            for (FTPFile ftpFile : subFiles) {
                if (pi != null && pi.isCanceled())
                    break;
                String currentFileName = ftpFile.getName();
                if (currentFileName.equals(".") || currentFileName.equals("..")) {
                    continue; // skip
                }

                String path1 = path + "/" + currentFileName;
                if (ftpFile.isDirectory()) {
                    // remove the sub directory
                    removeFtpDirectory(ftpClient, path1, pi);
                } else {
                    // delete the file
                    if (pi != null)
                        pi.setText2(prefix + path1);
                    ftpClient.deleteFile(path1);
                }
            }
        }

        if (pi != null)
            pi.setText2(prefix + path);
        ftpClient.removeDirectory(path);
        if (pi != null)
            pi.setText2("");
    }

    private static void uploadJdkDownloadScript(FTPClient ftp, String jdkDownloadUrl) throws IOException {

        String aspxPageName = aspScriptName;

        byte[] aspxPageData = generateAspxScriptForCustomJdkDownload(jdkDownloadUrl);
        ftp.storeFile(ftpRootPath + aspxPageName, new ByteArrayInputStream(aspxPageData));

        byte[] webConfigData = generateWebConfigForCustomJdkDownload(aspxPageName, null);
        ftp.storeFile(ftpRootPath + webConfigFilename, new ByteArrayInputStream(webConfigData));
    }

    public static boolean doesRemoteFileExist(FTPClient ftp, String path, String fileName) throws IOException {
        FTPFile[] files = ftp.listFiles(path);
        for (FTPFile file : files) {
            if (file.isFile() && file.getName().equalsIgnoreCase(fileName)) {
                return true;
            }
        }
        return false;
    }

    public static boolean doesRemoteFolderExist(FTPClient ftp, String path, String folderName) throws IOException {
        FTPFile[] files = ftp.listFiles(path);
        for (FTPFile file : files) {
            if (file.isDirectory() && file.getName().equalsIgnoreCase(folderName)) {
                return true;
            }
        }
        return false;
    }

    public static int sendGet(String sitePath) throws IOException {
        URL url = new URL(sitePath);
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        con.setRequestMethod("GET");
        con.setReadTimeout(Constants.connection_read_timeout_ms);
        return con.getResponseCode();
        //con.setRequestProperty("User-Agent", "AzureTools for Intellij");
    }

    public static boolean isUrlAccessible(String url) throws IOException {
        HttpURLConnection.setFollowRedirects(false);
        HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
        con.setRequestMethod("HEAD");
        con.setReadTimeout(Constants.connection_read_timeout_ms);
        try {
            if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
                return false;
            }
        } catch (IOException ex) {
            return false;
        }
        return true;
    }

    private static void cleanupWorkerData(FTPClient ftp) throws IOException {
        ftp.deleteFile(ftpRootPath + aspScriptName);
        ftp.deleteFile(ftpRootPath + "jdk.zip");
    }

    public static void removeCustomJdkArtifacts(FTPClient ftp, IProgressIndicator pi) throws IOException {
        if (doesRemoteFolderExist(ftp, ftpRootPath, jdkFolderName)) {
            removeFtpDirectory(ftp, ftpJdkPath, pi);
        }
        ftp.deleteFile(ftpRootPath + webConfigFilename);
        ftp.deleteFile(ftpRootPath + reportFilename);
        ftp.deleteFile(ftpRootPath + statusFilename);
    }

    public static class WebAppException extends Exception {
        /**
         * 
         */
        private static final long serialVersionUID = 1352713295336034845L;

        WebAppException(String message) {
            super(message);
        }
    }

    public static void deployCustomJdk(WebApp webApp, String jdkDownloadUrl, WebContainer webContainer,
            IProgressIndicator indicator) throws IOException, InterruptedException, WebAppException {
        FTPClient ftp = null;
        String customJdkFolderName = null;
        try {

            PublishingProfile pp = webApp.getPublishingProfile();
            ftp = getFtpConnection(pp);

            // stop and restart web app
            //            if (indicator != null) indicator.setText("Stopping the service...");
            //            webApp.stop();

            if (indicator != null)
                indicator.setText("Deleting custom jdk artifacts, if any (takes a while)...");
            removeCustomJdkArtifacts(ftp, indicator);

            if (indicator != null)
                indicator.setText("Uploading scripts...");
            uploadJdkDownloadScript(ftp, jdkDownloadUrl);

            //            if (indicator != null) indicator.setText("Starting the service...");
            //            webApp.start();

            final String siteUrl = "https://" + webApp.defaultHostName();

            // send get to activate the script
            sendGet(siteUrl);

            // Polling report.txt...
            if (indicator != null)
                indicator.setText("Checking the JDK gets downloaded and unpacked...");
            //int step = 0;
            while (!doesRemoteFileExist(ftp, ftpRootPath, reportFilename)) {
                if (indicator != null && indicator.isCanceled())
                    throw new CancellationException("Canceled by user.");
                //if (step++ > 3) checkFreeSpaceAvailability(ftp);
                Thread.sleep(5000);
                sendGet(siteUrl);
            }

            if (indicator != null)
                indicator.setText("Checking status...");
            OutputStream reportFileStream = new ByteArrayOutputStream();
            ftp.retrieveFile("report.txt", reportFileStream);
            String reportFileString = reportFileStream.toString();
            if (reportFileString.startsWith("FAIL")) {
                String err = reportFileString.substring(reportFileString.indexOf(":" + 1));
                throw new WebAppException(err);
            }

            // get top level jdk folder name (under jdk folder)
            FTPFile[] ftpDirs = ftp.listDirectories(ftpJdkPath);
            if (ftpDirs.length != 1) {
                String err = "Bad JDK archive. Please make sure the JDK archive contains a single JDK folder. For example, 'my-jdk1.7.0_79.zip' archive should contain 'jdk1.7.0_79' folder only";
                throw new WebAppException(err);
            }

            customJdkFolderName = ftpDirs[0].getName();

            uploadWebConfigForCustomJdk(ftp, webApp, customJdkFolderName, webContainer, indicator);
        } catch (IOException | WebAppException | InterruptedException ex) {
            if (doesRemoteFolderExist(ftp, ftpRootPath, jdkFolderName)) {
                indicator.setText("Error happened. Cleaning up...");
                removeFtpDirectory(ftp, ftpJdkPath, indicator);
            }
            throw ex;
        } finally {
            indicator.setText("Removing working data from server...");
            cleanupWorkerData(ftp);
            if (ftp != null && ftp.isConnected()) {
                ftp.disconnect();
            }
        }
    }

    private static void uploadWebConfigForCustomJdk(FTPClient ftp, WebApp webApp, String jdkFolderName,
            WebContainer webContainer, IProgressIndicator indicator) throws IOException {
        if (jdkFolderName == null || jdkFolderName.isEmpty()) {
            throw new IllegalArgumentException("jdkFolderName is null or empty");
        }

        if (indicator != null)
            indicator.setText("Stopping the service...");
        webApp.stop();

        if (indicator != null)
            indicator.setText("Deleting " + webConfigFilename + "...");
        ftp.deleteFile(ftpRootPath + webConfigFilename);

        if (indicator != null)
            indicator.setText("Turning the App Service into java based...");
        webApp.update().withJavaVersion(JavaVersion.JAVA_8_NEWEST).withWebContainer(webContainer).apply();

        if (indicator != null)
            indicator.setText("Generating " + webConfigFilename + "...");
        String jdkPath = "%HOME%\\site\\wwwroot\\jdk\\" + jdkFolderName;
        String webContainerPath = generateWebContainerPath(webContainer);
        byte[] webConfigData = generateWebConfigForCustomJDK(jdkPath, webContainerPath);

        if (indicator != null)
            indicator.setText("Uploading " + webConfigFilename + "...");
        ftp.storeFile(ftpRootPath + webConfigFilename, new ByteArrayInputStream(webConfigData));

        if (indicator != null)
            indicator.setText("Starting the service...");
        webApp.start();
    }

    public enum WebContainerMod {
        Newest_Tomcat_70("Newest Tomcat 7.0", "tomcat 7.0"), Newest_Tomcat_80("Newest Tomcat 8.0",
                "tomcat 8.0"), Newest_Tomcat_85("Newest Tomcat 8.5", "tomcat 8.5"), Newest_Jetty_91(
                        "Newest Jetty 9.1", "jetty 9.1"), Newest_Jetty_93("Newest Jetty 9.3", "jetty 9.3");

        private String displayName;
        private String value;

        WebContainerMod(String displayName, String value) {
            this.displayName = displayName;
            this.value = value;
        }

        public String getDisplayName() {
            return displayName;
        }

        public String getValue() {
            return value;
        }

        public WebContainer toWebContainer() {
            return new WebContainer(getValue());
        }

        @Override
        public String toString() {
            return getDisplayName();
        }

    }

    public static String generateWebContainerPath(WebContainer webContainer) throws IOException {
        if (webContainer.toString().equals(WebContainerMod.Newest_Tomcat_70.getValue())) {
            return "%AZURE_TOMCAT7_HOME%";
        } else if (webContainer.toString().equals(WebContainerMod.Newest_Tomcat_80.getValue())) {
            return "%AZURE_TOMCAT8_HOME%";
        } else if (webContainer.toString().equals(WebContainerMod.Newest_Tomcat_85.getValue())) {
            return "%AZURE_TOMCAT85_HOME%";
        } else if (webContainer.toString().equals(WebContainerMod.Newest_Jetty_91.getValue())) {
            return "%AZURE_JETTY9_HOME%";
        } else if (webContainer.toString().equals(WebContainerMod.Newest_Jetty_93.getValue())) {
            return "%AZURE_JETTY93_HOME%";
        }
        //        if (webContainer.equals(WebContainer.TOMCAT_7_0_NEWEST)) {
        //            return "%AZURE_TOMCAT7_HOME%";
        //        } else if (webContainer.equals(WebContainer.TOMCAT_8_0_NEWEST)) {
        //            return "%AZURE_TOMCAT8_HOME%";
        //        } else if (webContainer.equals(WebContainer.JETTY_9_1_NEWEST)) {
        //            return "%AZURE_JETTY9_HOME%";
        //        }
        //        String binPath = "%programfiles(x86)%\\";
        //        String wc = webContainer.toString();
        //        int verIdx = wc.indexOf(" ") + 1;
        //        String ver = wc.substring(verIdx);
        //        if (wc.startsWith("tomcat")) {
        //            return binPath + "apache-tomcat-" + ver;
        //        } else if (wc.startsWith("jetty")) {
        //            StringBuilder sbVer = new StringBuilder(ver);
        //            sbVer.insert(ver.lastIndexOf('.')+1, 'v');
        //            return binPath + "jetty-distribution-" + sbVer.toString();
        //        }

        throw new IOException("Unknown web container: " + webContainer.toString());
    }

    public static byte[] generateWebConfigForCustomJDK(String jdkPath, String webContainerPath) {
        String javaPath = jdkPath.isEmpty() ? "%JAVA_HOME%\\bin\\java.exe" : jdkPath + "\\bin\\java.exe";
        String debugOptions = "-Djava.net.preferIPv4Stack=true -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=127.0.0.1:%HTTP_PLATFORM_DEBUG_PORT% ";

        StringBuilder sb = new StringBuilder();

        sb.append("<?xml version='1.0' encoding='UTF-8'?>\n");
        sb.append("<configuration>\n");
        sb.append("    <system.webServer>\n");

        if (!webContainerPath.toUpperCase().contains("JETTY")) {
            sb.append("        <httpPlatform>\n");
            sb.append("            <environmentVariables>\n");
            sb.append("                <environmentVariable name='JRE_HOME' value='" + jdkPath + "'/>\n");
            sb.append("                <environmentVariable name='JAVA_OPTS' value='" + debugOptions + "'/>\n");
            sb.append("            </environmentVariables>\n");
            sb.append("        </httpPlatform>\n");
        } else {
            String arg = debugOptions + "-Djetty.port=%HTTP_PLATFORM_PORT% -Djetty.base=\"" + webContainerPath
                    + "\" -Djetty.webapps=\"d:\\home\\site\\wwwroot\\webapps\"  -jar \"" + webContainerPath
                    + "\\start.jar\" etc\\jetty-logging.xml";

            sb.append("        <httpPlatform processPath='" + javaPath
                    + "' startupTimeLimit='30' startupRetryCount='10' arguments='" + arg + "'/>\n");
        }
        sb.append("    </system.webServer>\n");
        sb.append("</configuration>\n");

        return sb.toString().getBytes();
    }

    public static byte[] generateAspxScriptForCustomJdkDownload(String jdkDownloadUrl) throws IOException {

        StringBuilder sb = new StringBuilder();

        sb.append("<%@ Page Language=\"C#\" %>\n");
        sb.append("<%@ Import namespace=\"System.IO\" %>\n");
        sb.append("<%@ Import namespace=\"System.Net\" %>\n");
        sb.append("<%@ Import namespace=\"System.IO.Compression\" %>\n");
        sb.append("<script runat=server>\n");

        sb.append("const string baseDir = @\"d:\\home\\site\\wwwroot\";\n");
        sb.append("const string keySuccess = \"SUCCESS\";\n");
        sb.append("const string keyFail = \"FAIL\";\n");
        sb.append("const string reportPattern = \"{0}:{1}\";\n");
        sb.append("readonly static string pathReport = Path.Combine(baseDir, \"report.txt\");\n");
        sb.append("readonly static string pathStatus = Path.Combine(baseDir, \"status.txt\");\n");

        sb.append("string getTime() {\n");
        sb.append("    getJdk();\n");
        sb.append("    return DateTime.Now.ToString(\"t\");\n");
        sb.append("}\n");

        sb.append("static void getJdk() {\n");
        sb.append("    try {\n");
        sb.append("         const string downloadSrc = @\"" + jdkDownloadUrl + "\";\n");
        sb.append("         string downloadDst = Path.Combine(baseDir, \"jdk.zip\");\n");
        sb.append("         statusAdd(\"Deleting zip file, if any\");\n");
        sb.append("         if (File.Exists(downloadDst)) { File.Delete(downloadDst); }\n");
        sb.append("         statusAdd(\"Checking zip size for download\");\n");
        sb.append("         var req = WebRequest.Create(downloadSrc);\n");
        sb.append("         req.Method = \"HEAD\";\n");
        sb.append("         long contentLength;\n");
        sb.append("         using (WebResponse resp = req.GetResponse()) {\n");
        sb.append("             if (!long.TryParse(resp.Headers.Get(\"Content-Length\"), out contentLength)) {\n");
        sb.append("                 throw new Exception(\"Can't get file size\");\n");
        sb.append("             }\n");
        sb.append("         }\n");
        sb.append(
                "         statusAdd(\"zip size is [\" + contentLength + \"] , disk size is [\" + getDiskFreeSpace() + \"]\" );\n");
        sb.append("         if (contentLength*2 > getDiskFreeSpace()) {\n");
        sb.append(
                "             throw new Exception(\"There is not enough disk space to complete the operation.\");\n");
        sb.append("         }\n");
        sb.append("         statusAdd(\"Downloading zip\");\n");
        sb.append("         using (var client = new WebClient()) {\n");
        sb.append("             client.DownloadFile(downloadSrc, downloadDst);\n");
        sb.append("         }\n");
        sb.append("         string unpackDst = Path.Combine(baseDir, \"jdk\");\n");
        sb.append("         statusAdd(\"Deleting jdk dir, if any\");\n");
        sb.append("         if (Directory.Exists(unpackDst)) { Directory.Delete(unpackDst, true); }\n");
        sb.append("         string unpackSrc = Path.Combine(baseDir, \"jdk.zip\");\n");
        sb.append("         statusAdd(\"Checking expected upacked size\");\n");
        sb.append("         long expectedUnpackedSize;\n");
        sb.append("         using (ZipArchive archive = ZipFile.OpenRead(unpackSrc)) {\n");
        sb.append("             expectedUnpackedSize = archive.Entries.Sum(entry => entry.Length);\n");
        sb.append("         }\n");
        sb.append(
                "         statusAdd(\"Expected upacked size is [\" + expectedUnpackedSize + \"] , disk size is [\" + getDiskFreeSpace() + \"]\");\n");
        sb.append("         if (expectedUnpackedSize*2 > getDiskFreeSpace()) {\n");
        sb.append(
                "             throw new Exception(\"There is not enough disk space to complete the operation.\");\n");
        sb.append("         }\n");
        sb.append("         statusAdd(\"Unpacking zip\");\n");
        sb.append("         ZipFile.ExtractToDirectory(unpackSrc, unpackDst);\n");
        sb.append("         statusAdd(\"Done\");\n");
        sb.append("         reportOneLine(string.Format(reportPattern, keySuccess, string.Empty));\n");
        sb.append("     } catch (Exception e) {\n");
        sb.append("         statusAdd(\"Exception: \" + e.Message);\n");
        sb.append("         reportOneLine(string.Format(reportPattern, keyFail, e.Message));\n");
        sb.append("     }\n");
        sb.append("}\n");

        sb.append("static long getDiskFreeSpace() {\n");
        sb.append("     DriveInfo driveInfo = new DriveInfo(@\"d:\");\n");
        sb.append("     return driveInfo.AvailableFreeSpace;\n");
        sb.append("}\n");

        sb.append("static void reportOneLine(string message) {\n");
        sb.append("     if (File.Exists(pathReport)) File.Delete(pathReport);\n");
        sb.append("     using (StreamWriter sw = File.CreateText(pathReport)) {\n");
        sb.append("         sw.WriteLine(message);\n");
        sb.append("     }\n");
        sb.append("}\n");

        sb.append("static void statusAdd(string message) {\n");
        sb.append("     if (!File.Exists(pathStatus)) {\n");
        sb.append("         using (StreamWriter sw = File.CreateText(pathStatus)) {\n");
        sb.append("             sw.WriteLine(message);\n");
        sb.append("         }\n");
        sb.append("     } else {\n");
        sb.append("         using (StreamWriter sw = File.AppendText(pathStatus)) {\n");
        sb.append("             sw.WriteLine(message);\n");
        sb.append("         }\n");
        sb.append("     }\n");
        sb.append("}\n");

        sb.append("</script>\n");
        sb.append("<html>\n");
        sb.append("<body>\n");
        sb.append("<form id=\"form1\" runat=\"server\">\n");
        sb.append("Current server time is <% =getTime()%>\n");
        sb.append("</form>\n");
        sb.append("</body>\n");
        sb.append("</html>\n");
        return sb.toString().getBytes();
    }

    public static byte[] generateWebConfigForCustomJdkDownload(String initializationPage, String[] assemblies)
            throws IOException {

        StringBuilder sb = new StringBuilder();

        sb.append("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
        sb.append("<configuration>\n");
        sb.append("    <system.webServer>\n");
        sb.append("        <applicationInitialization remapManagedRequestsTo='/hostingstart.html'>\n");
        if (initializationPage != null && !initializationPage.isEmpty())
            sb.append("        <add initializationPage='/" + initializationPage + "'/>\n");
        sb.append("    </applicationInitialization>\n");
        sb.append("    </system.webServer>\n");
        sb.append("    <system.web>\n");
        sb.append("        <customErrors mode='Off'/>\n");
        sb.append("        <compilation debug='true' targetFramework='4.5'>\n");
        sb.append("        <assemblies>\n");
        sb.append(
                "            <add assembly='System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089'/>\n");
        sb.append(
                "            <add assembly='System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'/>\n");

        if (assemblies != null) {
            for (String assembly : assemblies) {
                sb.append("            <add assembly='" + assembly + "'/>\n");
            }
        }

        sb.append("        </assemblies>\n");
        sb.append("        </compilation>\n");
        sb.append("        <httpRuntime targetFramework='4.5'/>\n");
        sb.append("    </system.web>\n");
        sb.append("</configuration>\n");
        return sb.toString().getBytes();
    }

    public static abstract class CreateAppServiceModel {
        public enum JdkTab {
            Default, ThirdParty, Own;
        }

        public String webAppName;
        public WebContainer webContainer;
        public SubscriptionDetail subscriptionDetail;

        public boolean isResourceGroupCreateNew;
        public ResourceGroup resourceGroup;
        public String resourceGroupNameCreateNew;

        public boolean isAppServicePlanCreateNew;
        public AppServicePlan appServicePlan;
        public String appServicePlanNameCreateNew;
        public Location appServicePlanLocationCreateNew;
        public PricingTier appServicePricingTierCreateNew;

        public String jdk3PartyUrl;
        public String jdkOwnUrl;
        public String storageAccountKey;
        public JdkTab jdkTab;
        public String jdkDownloadUrl;

        public abstract void collectData();
    }

    public static WebApp createAppService(IProgressIndicator progressIndicator, CreateAppServiceModel model)
            throws IOException, WebAppException, InterruptedException, AzureCmdException {

        AzureManager azureManager = AuthMethodManager.getInstance().getAzureManager();
        // not signed in
        if (azureManager == null) {
            return null;
        }

        Azure azure = azureManager.getAzure(model.subscriptionDetail.getSubscriptionId());

        AppServicePlan appServicePlan = null;
        if (model.isAppServicePlanCreateNew) {
            AppServicePlan.DefinitionStages.WithGroup ds1 = azure.appServices().appServicePlans()
                    .define(model.appServicePlanNameCreateNew)
                    .withRegion(model.appServicePlanLocationCreateNew.name());
            AppServicePlan.DefinitionStages.WithPricingTier ds2;
            if (model.isResourceGroupCreateNew) {
                ds2 = ds1.withNewResourceGroup(model.resourceGroupNameCreateNew);
            } else {
                ds2 = ds1.withExistingResourceGroup(model.resourceGroup);
            }
            appServicePlan = ds2.withPricingTier(model.appServicePricingTierCreateNew)
                    .withOperatingSystem(OperatingSystem.WINDOWS).create();
        } else {
            appServicePlan = model.appServicePlan;
        }

        WebApp.DefinitionStages.Blank definitionStages = azure.webApps().define(model.webAppName);
        WebAppBase.DefinitionStages.WithCreate<WebApp> withCreate;

        WebApp.DefinitionStages.ExistingWindowsPlanWithGroup ds1 = definitionStages
                .withExistingWindowsPlan(appServicePlan);
        if (model.isResourceGroupCreateNew) {
            withCreate = ds1.withNewResourceGroup(model.resourceGroupNameCreateNew);
        } else {
            withCreate = ds1.withExistingResourceGroup(model.resourceGroup);
        }

        if (model.jdkDownloadUrl == null) { // no custom jdk
            withCreate = withCreate.withJavaVersion(JavaVersion.JAVA_8_NEWEST).withWebContainer(model.webContainer);
        }

        WebApp myWebApp = withCreate.create();

        if (model.jdkDownloadUrl != null) {
            progressIndicator.setText("Deploying custom jdk...");
            WebAppUtils.deployCustomJdk(myWebApp, model.jdkDownloadUrl, model.webContainer, progressIndicator);
        }

        // update cache
        if (model.isResourceGroupCreateNew) {
            ResourceGroup rg = azure.resourceGroups().getByName(model.resourceGroupNameCreateNew);
            if (rg == null) {
                throw new AzureCmdException(String.format("azure.resourceGroups().getByName(%s) returned null"),
                        model.resourceGroupNameCreateNew);
            }

            AzureModelController.addNewResourceGroup(model.subscriptionDetail, rg);
            AzureModelController.addNewWebAppToJustCreatedResourceGroup(rg, myWebApp);
            if (model.isAppServicePlanCreateNew) {
                AzureModelController.addNewAppServicePlanToJustCreatedResourceGroup(rg, appServicePlan);
            } else {
                // add empty list
                AzureModelController.addNewAppServicePlanToJustCreatedResourceGroup(rg, null);
            }
        } else {
            ResourceGroup rg = model.resourceGroup;
            AzureModelController.addNewWebAppToExistingResourceGroup(rg, myWebApp);
            if (model.isAppServicePlanCreateNew) {
                //AppServicePlan asp = azure.appServices().appServicePlans().getById(myWebApp.appServicePlanId());
                AzureModelController.addNewAppServicePlanToExistingResourceGroup(rg, appServicePlan);
            }
        }

        return myWebApp;
    }

    public static void deleteAppService(WebAppDetails webAppDetails) throws IOException {
        AzureManager azureManager = AuthMethodManager.getInstance().getAzureManager();
        Azure azure = azureManager.getAzure(webAppDetails.subscriptionDetail.getSubscriptionId());
        azure.webApps().deleteById(webAppDetails.webApp.id());
        // check asp still exists
        AppServicePlan asp = azure.appServices().appServicePlans().getById(webAppDetails.appServicePlan.id());
        System.out.println("asp is " + (asp == null ? "null -> removing form cache" : asp.name()));
        // update cache
        AzureModelController.removeWebAppFromResourceGroup(webAppDetails.resourceGroup, webAppDetails.webApp);
        if (asp == null) {
            AzureModelController.removeAppServicePlanFromResourceGroup(webAppDetails.appServicePlanResourceGroup,
                    webAppDetails.appServicePlan);
        }
    }

    public static void uploadWebConfig(WebApp webApp, InputStream fileStream, IProgressIndicator indicator)
            throws IOException {
        FTPClient ftp = null;
        try {
            PublishingProfile pp = webApp.getPublishingProfile();
            ftp = getFtpConnection(pp);

            if (indicator != null)
                indicator.setText("Stopping the service...");
            webApp.stop();

            if (indicator != null)
                indicator.setText("Uploading " + webConfigFilename + "...");
            ftp.storeFile(ftpRootPath + webConfigFilename, fileStream);

            if (indicator != null)
                indicator.setText("Starting the service...");
            webApp.start();
        } finally {
            if (ftp != null && ftp.isConnected()) {
                ftp.disconnect();
            }
        }
    }

    public static class WebAppDetails {
        public SubscriptionDetail subscriptionDetail;
        public ResourceGroup resourceGroup;
        public AppServicePlan appServicePlan;
        public ResourceGroup appServicePlanResourceGroup;
        public WebApp webApp;

        public WebAppDetails() {
        }

        public WebAppDetails(ResourceGroup resourceGroup, WebApp webApp, AppServicePlan appServicePlan,
                ResourceGroup appServicePlanResourceGroup, SubscriptionDetail subscriptionDetail) {
            this.resourceGroup = resourceGroup;
            this.webApp = webApp;
            this.appServicePlan = appServicePlan;
            this.appServicePlanResourceGroup = appServicePlanResourceGroup;
            this.subscriptionDetail = subscriptionDetail;
        }
    }

    public static class AspDetails {
        private AppServicePlan asp;
        private ResourceGroup rg;

        public AspDetails(AppServicePlan asp, ResourceGroup rg) {
            this.asp = asp;
            this.rg = rg;
        }

        public AppServicePlan getAsp() {
            return asp;
        }

        public ResourceGroup getRg() {
            return rg;
        }
    }
}