All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vmware.xenon.common.LoaderService Maven / Gradle / Ivy

There is a newer version: 1.6.18
Show newest version
/*
 * Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, without warranties or
 * conditions of any kind, EITHER EXPRESS OR IMPLIED.  See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.vmware.xenon.common;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

public class LoaderService extends StatefulService {
    public static final long MAINTENANCE_INTERVAL_MICROS = 30000000L;
    public static final String FILESYSTEM_DEFAULT_GROUP = "default";
    public static final String FILESYSTEM_DEFAULT_PATH = "services";

    public enum LoaderType {
        FILESYSTEM
    }

    public static class LoaderServiceInfo {
        public String name;
        public Long fileUpdateTimeMillis;
        public Map serviceClasses = new HashMap();
    }

    public static class LoaderServiceState extends ServiceDocument {
        public LoaderType loaderType;
        public String path;
        public Map servicePackages;
    }

    public LoaderService() {
        super(LoaderServiceState.class);
        super.toggleOption(ServiceOption.PERIODIC_MAINTENANCE, true);
    }

    @Override
    public void handleStart(Operation op) {
        if (op.hasBody()) {
            LoaderServiceState s = op.getBody(LoaderServiceState.class);
            if (s.loaderType == null) {
                s.loaderType = LoaderType.FILESYSTEM;
            }

            logFine("Initial path is %s", s.path);
        }

        super.setMaintenanceIntervalMicros(MAINTENANCE_INTERVAL_MICROS);

        op.complete();
    }

    @Override
    public void handlePatch(Operation patch) {
        // Get the current state.
        final LoaderServiceState currentState = getState(patch);
        final LoaderServiceState patchBody = getBody(patch);

        if (patchBody.path != null) {
            currentState.path = patchBody.path;
        }

        if (patchBody.loaderType != null) {
            currentState.loaderType = patchBody.loaderType;
        } else {
            currentState.loaderType = LoaderType.FILESYSTEM;
        }

        if (patchBody.servicePackages != null) {
            currentState.servicePackages = patchBody.servicePackages;
        }

        // Update the state.
        patch.setBody(currentState).complete();
    }

    @Override
    public void handlePut(Operation put) {
        final LoaderServiceState newState = put.getBody(LoaderServiceState.class);
        if (newState.loaderType == null) {
            newState.loaderType = LoaderType.FILESYSTEM;
        }
        setState(put, newState);
        put.setBody(newState).complete();
    }

    @Override
    public void handlePost(Operation op) {
        logFine("Post called");

        if (!op.hasBody()) {
            op.fail(new IllegalArgumentException("body is required"));
            return;
        }

        LoaderServiceState localState = getState(op);
        if (localState == null || localState.loaderType == null
                || localState.path == null) {
            op.fail(new IllegalStateException("Service state is null or invalid"));
            return;
        }

        // Acknowledge the request and complete the operation before actual loading
        op.setStatusCode(Operation.STATUS_CODE_ACCEPTED).complete();

        loadServices(localState);
    }

    @Override
    public void handleMaintenance(Operation op) {
        logFine("Maintenance called");

        sendRequest(Operation.createGet(getUri()).setCompletion(
                (o, e) -> performMaintenance(op, o, e)));
    }

    private void performMaintenance(Operation maint, Operation get,
            Throwable getEx) {
        if (getEx != null) {
            logWarning("Failure getting state: %s", getEx.toString());
            maint.complete();
            return;
        }

        if (!get.hasBody()) {
            maint.complete();
            return;
        }

        if (getHost().isStopping()) {
            maint.complete();
            return;
        }

        LoaderServiceState localState = get.getBody(LoaderServiceState.class);
        if (localState == null || localState.loaderType == null
                || localState.path == null) {
            maint.complete();
            return;
        }

        loadServices(localState);

        maint.complete();
    }

    private void loadServices(LoaderServiceState localState) {
        Map discoveredPackages = null;
        switch (localState.loaderType) {
        case FILESYSTEM:
            try {
                discoveredPackages = loadFromFileSystem(localState);
            } catch (Exception e) {
                logWarning("Failed to load packages from path %s, %s.",
                        localState.path, Utils.toString(e));
            }
            break;
        default:
            logWarning("Unknown loader type: %s", localState.loaderType);
        }

        if (discoveredPackages != null) {
            LoaderServiceState newState = new LoaderServiceState();
            newState.servicePackages = discoveredPackages;
            final Operation patch = Operation.createPatch(getUri()).setBody(newState);
            sendRequest(patch);
        }
    }

    private Map loadFromFileSystem(LoaderServiceState state)
            throws ClassNotFoundException, InstantiationException,
            IllegalAccessException {
        File libDir = new File(state.path);
        if (!libDir.isAbsolute()) {
            libDir = new File(new File(getHost().getStorageSandbox()), state.path);
        }

        if (!libDir.exists()) {
            if (!libDir.mkdirs()) {
                logFine("Failed to pre-create the Loader path directory %s", libDir);
                return null;
            }
        } else if (!libDir.isDirectory()) {
            logFine("Loader path %s is not a directory.", libDir.getAbsolutePath());
            return null;
        }

        Map services = discoverServices(libDir, state.servicePackages);

        if (services != null) {
            startDiscoveredServices(services, state);
        }

        return services;
    }

    private void startDiscoveredServices(Map services,
            LoaderServiceState state)
                    throws ClassNotFoundException, InstantiationException,
                    IllegalAccessException {
        logFine("Updating the class loader with new libraries");

        URL[] urls = new URL[services.size()];
        int i = 0;
        for (String servicePackage : services.keySet()) {
            try {
                urls[i++] = new URI(servicePackage).toURL();
            } catch (MalformedURLException | URISyntaxException e) {
                logWarning("Failed to convert path to URL", Utils.toString(e));
            }
        }

        URLClassLoader cl = null;
        try {
            cl = new URLClassLoader(urls);

            for (LoaderServiceInfo packageInfo : services.values()) {
                logFine("Processing package %s", packageInfo.name);
                for (Iterator iterator = packageInfo.serviceClasses.keySet().iterator();
                        iterator.hasNext(); ) {
                    String serviceClass = iterator.next();
                    Class clazz = cl.loadClass(serviceClass);

                    if (isValidDynamicService(clazz)) {
                        Service service = startDynamicService(clazz);
                        packageInfo.serviceClasses.put(serviceClass, service.getSelfLink());
                    } else {
                        iterator.remove();
                    }
                }
            }
        } finally {
            if (cl != null) {
                try {
                    cl.close();
                } catch (IOException e) {
                }
            }
        }
    }

    private Service startDynamicService(Class clazz)
            throws IllegalArgumentException, IllegalAccessException, InstantiationException {
        Service service = (Service) clazz.newInstance();
        URI link = null;
        // If it's a service
        if (getSelfOrFactoryLink(clazz).getName().equals(UriUtils.FIELD_NAME_SELF_LINK)) {
            link = UriUtils.buildUri(getHost(), service.getClass());
            getHost().startService(
                    Operation.createPost(link),
                    service);
        } else {
            // If it's a factory
            link = UriUtils.buildFactoryUri(getHost(), service.getClass());
            getHost().startFactory(service);
        }

        logInfo("Started service " + link);
        return service;
    }

    private boolean isValidDynamicService(Class clazz) throws IllegalArgumentException,
            IllegalAccessException {
        return (Service.class.isAssignableFrom(clazz) && getSelfOrFactoryLink(clazz) != null);
    }

    private Field getSelfOrFactoryLink(Class clazz)
            throws IllegalArgumentException, IllegalAccessException {
        try {
            Field link = clazz.getField(UriUtils.FIELD_NAME_SELF_LINK);
            logFine("Class %s self link %s", clazz, link.get(null));
            return link;
        } catch (NoSuchFieldException e) {
            try {
                Field link = clazz.getField(UriUtils.FIELD_NAME_FACTORY_LINK);
                logFine("Class %s factory link %s", clazz, link.get(null));
                return link;
            } catch (NoSuchFieldException e2) {
                logFine("Self link fields wasn't found in %s", clazz);
            }
        }
        return null;
    }

    private Map discoverServices(File libDir,
            Map existingPackages) {
        logFine("Checking for updates in " + libDir.toURI());
        Map discoveredPackages = new HashMap<>();

        boolean updated = false;
        File[] files = libDir.listFiles();
        if (files == null) {
            return null;
        }
        for (File file : files) {
            if (!file.getName().endsWith(".jar")) {
                continue;
            }
            logFine("Found jar file %s", file.toURI());

            LoaderServiceInfo packageInfo = existingPackages.get(file.toURI().toString());
            if (packageInfo != null) {
                long lastModified = file.lastModified();
                if (lastModified == packageInfo.fileUpdateTimeMillis) {
                    // Jar file has been previously loaded.
                    // Add to the list of discovered packages and skip.
                    discoveredPackages.put(file.toURI().toString(), packageInfo);
                    continue;
                }
            }

            try (JarInputStream jar = new JarInputStream(new FileInputStream(file))) {
                while (true) {
                    JarEntry e = jar.getNextJarEntry();
                    if (e == null) {
                        break;
                    }

                    String name = e.getName();

                    // Assuming specific naming convention for
                    // Service classes
                    if (isValidServiceClassName(name)) {
                        logFine("Found service class %s", name);
                        String className = name.replaceAll("\\.class$", "").replaceAll("/", ".");

                        if (packageInfo == null) {
                            packageInfo = new LoaderServiceInfo();
                        }

                        packageInfo.name = file.getName();
                        packageInfo.fileUpdateTimeMillis = file.lastModified();
                        updated |= (null == packageInfo.serviceClasses.put(className, null));

                        discoveredPackages.put(file.toURI().toString(), packageInfo);
                    }
                }
            } catch (IOException e) {
                logWarning("Problem loading package %s, Exception %s",
                        file.getName(), Utils.toString(e));
            }
        }

        if (updated) {
            return discoveredPackages;
        }

        return null;
    }

    /**
     * Initial filter for class files to avoid loading all classes into the class loader. The method
     * checks for class name ending with "Factory" or "Service", such as "ExampleFactory.class"
     *
     * @param name
     *            class file name to validate
     * @return
     */
    private boolean isValidServiceClassName(String name) {
        return name.endsWith("Factory.class") ||
                name.endsWith("Service.class");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy