PluginManager.java 9.6 KB
Newer Older
K
kohsuke 已提交
1 2
package hudson;

3
import hudson.model.*;
K
kohsuke 已提交
4 5
import hudson.util.Service;

6
import java.util.Enumeration;
K
kohsuke 已提交
7
import javax.servlet.ServletContext;
8
import javax.servlet.ServletException;
K
kohsuke 已提交
9 10 11 12 13 14
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
15
import java.util.Collections;
K
kohsuke 已提交
16 17 18
import java.util.HashSet;
import java.util.List;
import java.util.Set;
K
kohsuke 已提交
19
import java.util.logging.Level;
K
kohsuke 已提交
20 21
import java.util.logging.Logger;

22
import org.apache.commons.logging.LogFactory;
23 24 25
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.FileItem;
26 27
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
28
import org.kohsuke.stapler.QueryParameter;
29

K
kohsuke 已提交
30 31 32 33 34
/**
 * Manages {@link PluginWrapper}s.
 *
 * @author Kohsuke Kawaguchi
 */
35
public final class PluginManager extends AbstractModelObject {
K
kohsuke 已提交
36 37 38 39 40 41 42 43 44 45
    /**
     * All discovered plugins.
     */
    private final List<PluginWrapper> plugins = new ArrayList<PluginWrapper>();

    /**
     * All active plugins.
     */
    private final List<PluginWrapper> activePlugins = new ArrayList<PluginWrapper>();

46 47
    private final List<FailedPlugin> failedPlugins = new ArrayList<FailedPlugin>();

K
kohsuke 已提交
48 49 50 51 52 53 54 55 56 57 58 59
    /**
     * Plug-in root directory.
     */
    public final File rootDir;

    public final ServletContext context;

    /**
     * {@link ClassLoader} that can load all the publicly visible classes from plugins
     * (and including the classloader that loads Hudson itself.)
     *
     */
K
kohsuke 已提交
60
    // implementation is minimal --- just enough to run XStream
K
kohsuke 已提交
61 62 63
    // and load plugin-contributed classes.
    public final ClassLoader uberClassLoader = new UberClassLoader();

64 65 66 67 68 69 70
    /**
     * Once plugin is uploaded, this flag becomes true.
     * This is used to report a message that Hudson needs to be restarted
     * for new plugins to take effect.
     */
    public transient volatile boolean pluginUploaded =false;

K
kohsuke 已提交
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
    public PluginManager(ServletContext context) {
        this.context = context;
        rootDir = new File(Hudson.getInstance().getRootDir(),"plugins");
        if(!rootDir.exists())
            rootDir.mkdirs();

        File[] archives = rootDir.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(".hpi")        // plugin jar file
                    || name.endsWith(".hpl");       // linked plugin. for debugging.
            }
        });

        if(archives==null) {
            LOGGER.severe("Hudson is unable to create "+rootDir+"\nPerhaps its security privilege is insufficient");
            return;
        }
        for( File arc : archives ) {
            try {
K
kohsuke 已提交
90
                PluginWrapper p = new PluginWrapper(this,arc);
K
kohsuke 已提交
91 92 93 94
                plugins.add(p);
                if(p.isActive())
                    activePlugins.add(p);
            } catch (IOException e) {
95
                failedPlugins.add(new FailedPlugin(arc.getName(),e));
K
kohsuke 已提交
96 97 98
                LOGGER.log(Level.SEVERE, "Failed to load a plug-in " + arc, e);
            }
        }
99 100 101 102 103 104 105 106 107 108

        for (PluginWrapper p : activePlugins.toArray(new PluginWrapper[0]))
            try {
                p.load(this);
            } catch (IOException e) {
                failedPlugins.add(new FailedPlugin(p.getShortName(),e));
                LOGGER.log(Level.SEVERE, "Failed to load a plug-in " + p.getShortName(), e);
                activePlugins.remove(p);
                plugins.remove(p);
            }
K
kohsuke 已提交
109 110
    }

111 112 113 114 115 116 117
    /**
     * Retrurns true if any new plugin was added, which means a restart is required for the change to take effect.
     */
    public boolean isPluginUploaded() {
        return pluginUploaded;
    }
    
K
kohsuke 已提交
118 119 120 121
    public List<PluginWrapper> getPlugins() {
        return plugins;
    }

122 123 124 125
    public List<FailedPlugin> getFailedPlugins() {
        return failedPlugins;
    }

K
kohsuke 已提交
126 127 128 129 130 131 132 133
    public PluginWrapper getPlugin(String shortName) {
        for (PluginWrapper p : plugins) {
            if(p.getShortName().equals(shortName))
                return p;
        }
        return null;
    }

134 135 136 137 138 139 140 141
    public String getDisplayName() {
        return "Plugin Manager";
    }

    public String getSearchUrl() {
        return "pluginManager";
    }

K
kohsuke 已提交
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
    /**
     * Discover all the service provider implementations of the given class,
     * via <tt>META-INF/services</tt>.
     */
    public <T> Collection<Class<? extends T>> discover( Class<T> spi ) {
        Set<Class<? extends T>> result = new HashSet<Class<? extends T>>();

        for (PluginWrapper p : activePlugins) {
            Service.load(spi, p.classLoader, result);
        }

        return result;
    }

    /**
     * Orderly terminates all the plugins.
     */
    public void stop() {
        for (PluginWrapper p : activePlugins) {
            p.stop();
        }
163 164 165
        // Work around a bug in commons-logging.
        // See http://www.szegedi.org/articles/memleak.html
        LogFactory.release(uberClassLoader);
K
kohsuke 已提交
166 167
    }

168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    /**
     * Performs the installation of the plugins.
     */
    public void doInstall(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        Enumeration<String> en = req.getParameterNames();
        while (en.hasMoreElements()) {
            String n =  en.nextElement();
            if(n.startsWith("plugin.")) {
                n = n.substring(7);
                UpdateCenter.Plugin p = Hudson.getInstance().getUpdateCenter().getPlugin(n);
                if(p==null) {
                    sendError("No such plugin: "+n,req,rsp);
                    return;
                }
                p.install();
            }
        }
        rsp.sendRedirect("../updateCenter/");
    }

188
    public void doProxyConfigure(@QueryParameter("proxy.server") String server, @QueryParameter("proxy.port") String port, StaplerResponse rsp) throws IOException {
189 190
        setProp("http.proxyHost", Util.fixEmptyAndTrim(server));
        setProp("http.proxyPort",Util.fixEmptyAndTrim(port));
191 192
        setProp("https.proxyHost", Util.fixEmptyAndTrim(server));
        setProp("https.proxyPort",Util.fixEmptyAndTrim(port));
193 194 195
        rsp.sendRedirect("./advanced");
    }

196 197 198 199 200 201
    private void setProp(String key, String value) {
        // System.setProperty causes NPE if t he value is null.
        if(value==null) System.getProperties().remove(key);
        else        System.setProperty(key,value);
    }

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
    /**
     * Uploads a plugin.
     */
    public void doUploadPlugin( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        try {
            Hudson.getInstance().checkPermission(Hudson.ADMINISTER);

            ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());

            // Parse the request
            FileItem fileItem = (FileItem) upload.parseRequest(req).get(0);
            String fileName = Util.getFileName(fileItem.getName());
            if(!fileName.endsWith(".hpi")) {
                sendError(hudson.model.Messages.Hudson_NotAPlugin(fileName),req,rsp);
                return;
            }
            fileItem.write(new File(rootDir, fileName));
            fileItem.delete();

            pluginUploaded=true;

            rsp.sendRedirect2(".");
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {// grrr. fileItem.write throws this
            throw new ServletException(e);
        }
    }

K
kohsuke 已提交
231 232 233 234 235 236 237
    private final class UberClassLoader extends ClassLoader {
        public UberClassLoader() {
            super(PluginManager.class.getClassLoader());
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
238 239 240
            // first, use the context classloader so that plugins that are loading
            // can use its own classloader first.
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
241
            if(cl!=null && cl!=this)
242 243 244 245 246 247
                try {
                    return cl.loadClass(name);
                } catch(ClassNotFoundException e) {
                    // not found. try next
                }

K
kohsuke 已提交
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
            for (PluginWrapper p : activePlugins) {
                try {
                    return p.classLoader.loadClass(name);
                } catch (ClassNotFoundException e) {
                    //not found. try next
                }
            }
            // not found in any of the classloader. delegate.
            throw new ClassNotFoundException(name);
        }

        @Override
        protected URL findResource(String name) {
            for (PluginWrapper p : activePlugins) {
                URL url = p.classLoader.getResource(name);
                if(url!=null)
                    return url;
            }
            return null;
        }
268 269 270 271 272 273 274 275 276

        @Override
        protected Enumeration<URL> findResources(String name) throws IOException {
            List<URL> resources = new ArrayList<URL>();
            for (PluginWrapper p : activePlugins) {
                resources.addAll(Collections.list(p.classLoader.getResources(name)));
            }
            return Collections.enumeration(resources);
        }
K
kohsuke 已提交
277 278 279 280
    }

    private static final Logger LOGGER = Logger.getLogger(PluginManager.class.getName());

281 282 283 284 285 286 287 288 289 290 291 292 293
    /**
     * Remembers why a plugin failed to deploy.
     */
    public static final class FailedPlugin {
        public final String name;
        public final IOException cause;

        public FailedPlugin(String name, IOException cause) {
            this.name = name;
            this.cause = cause;
        }

        public String getExceptionString() {
294
            return Functions.printThrowable(cause);
295 296
        }
    }
K
kohsuke 已提交
297
}