org.cloudfoundry.ide.eclipse.server.standalone.internal.application.JavaCloudFoundryArchiver.java Source code

Java tutorial

Introduction

Here is the source code for org.cloudfoundry.ide.eclipse.server.standalone.internal.application.JavaCloudFoundryArchiver.java

Source

/*******************************************************************************
 * Copyright (c) 2013, 2015 Pivotal Software, Inc. 
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of 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.
 *  
 *  Contributors:
 *     Pivotal Software, Inc. - initial API and implementation
 ********************************************************************************/
package org.cloudfoundry.ide.eclipse.server.standalone.internal.application;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.zip.ZipFile;

import org.cloudfoundry.client.lib.archive.ApplicationArchive;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudErrorUtil;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryProjectUtil;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryServer;
import org.cloudfoundry.ide.eclipse.server.core.internal.application.CloudZipApplicationArchive;
import org.cloudfoundry.ide.eclipse.server.core.internal.application.JavaWebApplicationDelegate;
import org.cloudfoundry.ide.eclipse.server.core.internal.client.CloudFoundryApplicationModule;
import org.cloudfoundry.ide.eclipse.server.standalone.internal.Messages;
import org.cloudfoundry.ide.eclipse.server.ui.internal.CloudUiUtil;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.internal.ui.jarpackagerfat.FatJarRsrcUrlBuilder;
import org.eclipse.jdt.ui.jarpackager.IJarBuilder;
import org.eclipse.jdt.ui.jarpackager.IJarExportRunnable;
import org.eclipse.jdt.ui.jarpackager.JarPackageData;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.wst.server.core.IModule;
import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.Library;
import org.springframework.boot.loader.tools.LibraryCallback;
import org.springframework.boot.loader.tools.LibraryScope;
import org.springframework.boot.loader.tools.Repackager;

/**
 * Generates a Cloud Foundry client archive represent the Java application that
 * should be pushed to a Cloud Foundry server.
 * <p/>
 * Handles Spring boot application repackaging via Spring Boot loader tools
 * <p/>
 * Also supports packaged apps pointed to by the "path" property in an
 * application's manifest.yml
 * 
 */
public class JavaCloudFoundryArchiver {

    private final CloudFoundryApplicationModule appModule;

    private final CloudFoundryServer cloudServer;

    private static final String META_FOLDER_NAME = "META-INF"; //$NON-NLS-1$

    private static final String MANIFEST_FILE = "MANIFEST.MF"; //$NON-NLS-1$

    public JavaCloudFoundryArchiver(CloudFoundryApplicationModule appModule, CloudFoundryServer cloudServer) {
        this.appModule = appModule;
        this.cloudServer = cloudServer;
    }

    public ApplicationArchive getApplicationArchive(IProgressMonitor monitor) throws CoreException {

        ApplicationArchive archive = JavaWebApplicationDelegate.getArchiveFromManifest(appModule, cloudServer);

        if (archive == null) {

            File packagedFile = null;

            IJavaProject javaProject = CloudFoundryProjectUtil.getJavaProject(appModule);

            if (javaProject == null) {
                handleApplicationDeploymentFailure(Messages.JavaCloudFoundryArchiver_ERROR_NO_JAVA_PROJ_RESOLVED);
            }

            JavaPackageFragmentRootHandler rootResolver = getPackageFragmentRootHandler(javaProject, monitor);

            IType mainType = rootResolver.getMainType(monitor);

            final IPackageFragmentRoot[] roots = rootResolver.getPackageFragmentRoots(monitor);

            if (roots == null || roots.length == 0) {
                handleApplicationDeploymentFailure(Messages.JavaCloudFoundryArchiver_ERROR_NO_PACKAGE_FRAG_ROOTS);
            }

            JarPackageData jarPackageData = getJarPackageData(roots, mainType, monitor);

            boolean isBoot = CloudFoundryProjectUtil.isSpringBoot(appModule);

            // Search for existing MANIFEST.MF
            IFile metaFile = getManifest(roots, javaProject);

            // Only use existing manifest files for non-Spring boot, as Spring
            // boot repackager will
            // generate it own manifest file.
            if (!isBoot && metaFile != null) {
                // If it is not a boot project, use a standard library jar
                // builder
                jarPackageData.setJarBuilder(getDefaultLibJarBuilder());

                jarPackageData.setManifestLocation(metaFile.getFullPath());
                jarPackageData.setSaveManifest(false);
                jarPackageData.setGenerateManifest(false);
                // Check manifest accessibility through the jar package data
                // API
                // to verify the packaging won't fail
                if (!jarPackageData.isManifestAccessible()) {
                    handleApplicationDeploymentFailure(
                            NLS.bind(Messages.JavaCloudFoundryArchiver_ERROR_MANIFEST_NOT_ACCESSIBLE,
                                    metaFile.getLocation().toString()));
                }

                InputStream inputStream = null;
                try {

                    inputStream = new FileInputStream(metaFile.getLocation().toFile());
                    Manifest manifest = new Manifest(inputStream);
                    Attributes att = manifest.getMainAttributes();
                    if (att.getValue("Main-Class") == null) { //$NON-NLS-1$
                        handleApplicationDeploymentFailure(
                                Messages.JavaCloudFoundryArchiver_ERROR_NO_MAIN_CLASS_IN_MANIFEST);
                    }
                } catch (FileNotFoundException e) {
                    handleApplicationDeploymentFailure(NLS.bind(
                            Messages.JavaCloudFoundryArchiver_ERROR_FAILED_READ_MANIFEST, e.getLocalizedMessage()));

                } catch (IOException e) {
                    handleApplicationDeploymentFailure(NLS.bind(
                            Messages.JavaCloudFoundryArchiver_ERROR_FAILED_READ_MANIFEST, e.getLocalizedMessage()));

                } finally {

                    if (inputStream != null) {
                        try {
                            inputStream.close();

                        } catch (IOException io) {
                            // Ignore
                        }
                    }
                }

            } else {
                // Otherwise generate a manifest file. Note that manifest files
                // are only generated in the temporary jar meant only for
                // deployment.
                // The associated Java project is no modified.
                jarPackageData.setGenerateManifest(true);

                // This ensures that folders in output folders appear at root
                // level
                // Example: src/main/resources, which is in the project's
                // classpath, contains non-Java templates folder and
                // has output folder target/classes. If not exporting output
                // folder,
                // templates will be packaged in the jar using this path:
                // resources/templates
                // This may cause problems with the application's dependencies
                // if they are looking for just /templates at top level of the
                // jar
                // If exporting output folders, templates folder will be
                // packaged at top level in the jar.
                jarPackageData.setExportOutputFolders(true);
            }

            try {
                packagedFile = packageApplication(jarPackageData, monitor);
            } catch (CoreException e) {
                handleApplicationDeploymentFailure(
                        NLS.bind(Messages.JavaCloudFoundryArchiver_ERROR_JAVA_APP_PACKAGE, e.getMessage()));
            }

            if (packagedFile == null || !packagedFile.exists()) {
                handleApplicationDeploymentFailure(
                        Messages.JavaCloudFoundryArchiver_ERROR_NO_PACKAGED_FILE_CREATED);
            }

            if (isBoot) {
                bootRepackage(roots, packagedFile);
            }

            // At this stage a packaged file should have been created or found
            try {
                archive = new CloudZipApplicationArchive(new ZipFile(packagedFile));
            } catch (IOException ioe) {
                handleApplicationDeploymentFailure(
                        NLS.bind(Messages.JavaCloudFoundryArchiver_ERROR_CREATE_CF_ARCHIVE, ioe.getMessage()));
            }
        }

        return archive;
    }

    /**
     * 
     * @param resource
     *            that may contain a META-INF folder
     * @return META-INF folder, if found. Null otherwise
     * @throws CoreException
     */
    protected IFolder getMetaFolder(IResource resource) throws CoreException {
        if (!(resource instanceof IContainer)) {
            return null;
        }
        IContainer folder = (IContainer) resource;
        // Only look for META-INF folder at top-level in the given container.
        IResource[] members = folder.members();
        if (members != null) {
            for (IResource mem : members) {
                if (META_FOLDER_NAME.equals(mem.getName()) && mem instanceof IFolder) {
                    return (IFolder) mem;
                }
            }
        }
        return null;
    }

    protected IFile getManifest(IPackageFragmentRoot[] roots, IJavaProject javaProject) throws CoreException {

        IFolder metaFolder = null;
        for (IPackageFragmentRoot root : roots) {
            if (!root.isArchive() && !root.isExternal()) {
                IResource resource = root.getResource();
                metaFolder = getMetaFolder(resource);
                if (metaFolder != null) {
                    break;
                }
            }
        }

        // Otherwise look for manifest file in the java project:
        if (metaFolder == null) {
            metaFolder = getMetaFolder(javaProject.getProject());
        }

        if (metaFolder != null) {
            IResource[] members = metaFolder.members();
            if (members != null) {
                for (IResource mem : members) {
                    if (MANIFEST_FILE.equals(mem.getName().toUpperCase()) && mem instanceof IFile) {
                        return (IFile) mem;
                    }
                }
            }
        }

        return null;

    }

    protected IJarBuilder getDefaultLibJarBuilder() {
        return new FatJarRsrcUrlBuilder() {

            public void writeRsrcUrlClasses() throws IOException {
                // Do not unpack and repackage the Eclipse jar loader
            }
        };
    }

    protected JavaPackageFragmentRootHandler getPackageFragmentRootHandler(IJavaProject javaProject,
            IProgressMonitor monitor) throws CoreException {

        return new JavaPackageFragmentRootHandler(javaProject, cloudServer);
    }

    protected void bootRepackage(final IPackageFragmentRoot[] roots, File packagedFile) throws CoreException {
        Repackager bootRepackager = new Repackager(packagedFile);
        try {
            bootRepackager.repackage(new Libraries() {

                public void doWithLibraries(LibraryCallback callBack) throws IOException {
                    for (IPackageFragmentRoot root : roots) {

                        if (root.isArchive()) {

                            File rootFile = new File(root.getPath().toOSString());
                            if (rootFile.exists()) {
                                callBack.library(new Library(rootFile, LibraryScope.COMPILE));
                            }
                        }
                    }
                }
            });
        } catch (IOException e) {
            handleApplicationDeploymentFailure(
                    NLS.bind(Messages.JavaCloudFoundryArchiver_ERROR_REPACKAGE_SPRING, e.getMessage()));
        }
    }

    protected JarPackageData getJarPackageData(IPackageFragmentRoot[] roots, IType mainType,
            IProgressMonitor monitor) throws CoreException {

        String filePath = getTempJarPath(appModule.getLocalModule());

        if (filePath == null) {
            handleApplicationDeploymentFailure();
        }

        IPath location = new Path(filePath);

        // Note that if no jar builder is specified in the package data
        // then a default one is used internally by the data that does NOT
        // package any jar dependencies.
        JarPackageData packageData = new JarPackageData();

        packageData.setJarLocation(location);

        // Don't create a manifest. A repackager should determine if a generated
        // manifest is necessary
        // or use a user-defined manifest.
        packageData.setGenerateManifest(false);

        // Since user manifest is not used, do not save to manifest (save to
        // manifest saves to user defined manifest)
        packageData.setSaveManifest(false);

        packageData.setManifestMainClass(mainType);
        packageData.setElements(roots);
        return packageData;
    }

    protected File packageApplication(final JarPackageData packageData, IProgressMonitor monitor)
            throws CoreException {

        int progressWork = 10;
        final SubMonitor subProgress = SubMonitor.convert(monitor, progressWork);

        final File[] createdFile = new File[1];

        final CoreException[] error = new CoreException[1];
        Display.getDefault().syncExec(new Runnable() {

            @Override
            public void run() {
                try {

                    Shell shell = CloudUiUtil.getShell();

                    IJarExportRunnable runnable = packageData.createJarExportRunnable(shell);
                    try {
                        runnable.run(subProgress);

                        File file = new File(packageData.getJarLocation().toString());
                        if (!file.exists()) {
                            handleApplicationDeploymentFailure();
                        } else {
                            createdFile[0] = file;
                        }

                    } catch (InvocationTargetException e) {
                        throw CloudErrorUtil.toCoreException(e);
                    } catch (InterruptedException ie) {
                        throw CloudErrorUtil.toCoreException(ie);
                    } finally {
                        subProgress.done();
                    }
                } catch (CoreException e) {
                    error[0] = e;
                }
            }

        });
        if (error[0] != null) {
            throw error[0];
        }

        return createdFile[0];
    }

    protected void handleApplicationDeploymentFailure(String errorMessage) throws CoreException {
        if (errorMessage == null) {
            errorMessage = Messages.JavaCloudFoundryArchiver_ERROR_CREATE_PACKAGED_FILE;
        }
        throw CloudErrorUtil.toCoreException(errorMessage + " - " //$NON-NLS-1$
                + appModule.getDeployedApplicationName() + ". Unable to package application for deployment."); //$NON-NLS-1$
    }

    protected void handleApplicationDeploymentFailure() throws CoreException {
        handleApplicationDeploymentFailure(null);
    }

    public static String getTempJarPath(IModule module) throws CoreException {
        try {
            File tempFolder = File.createTempFile("tempFolderForJavaAppJar", //$NON-NLS-1$
                    null);
            tempFolder.delete();
            tempFolder.mkdirs();

            if (!tempFolder.exists()) {
                throw CloudErrorUtil.toCoreException(
                        NLS.bind(Messages.JavaCloudFoundryArchiver_ERROR_CREATE_TEMP_DIR, tempFolder.getPath()));
            }

            File targetFile = new File(tempFolder, module.getName() + ".jar"); //$NON-NLS-1$
            targetFile.deleteOnExit();

            String path = new Path(targetFile.getAbsolutePath()).toString();

            return path;

        } catch (IOException io) {
            CloudErrorUtil.toCoreException(io);
        }
        return null;
    }
}