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

org.glassfish.admin.rest.adapter.RestAdapter Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2009-2011 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.glassfish.admin.rest.adapter;

import com.sun.enterprise.config.serverbeans.AdminService;
import com.sun.enterprise.config.serverbeans.Config;
import com.sun.enterprise.config.serverbeans.SecureAdmin;
import com.sun.enterprise.module.common_impl.LogHelper;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.v3.admin.AdminAdapter;
import com.sun.enterprise.v3.admin.adapter.AdminEndpointDecider;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import javax.security.auth.login.LoginException;

import org.glassfish.admin.rest.LazyJerseyInterface;
import org.glassfish.admin.rest.RestService;
import org.glassfish.admin.rest.SessionManager;
import org.glassfish.api.ActionReport;
import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.api.container.Adapter;
import org.glassfish.api.container.EndpointRegistrationException;
import org.glassfish.api.event.EventListener;
import org.glassfish.api.event.EventTypes;
import org.glassfish.api.event.Events;
import org.glassfish.api.event.RestrictTo;
import org.jvnet.hk2.annotations.Inject;
import org.jvnet.hk2.component.Habitat;
import org.jvnet.hk2.component.PostConstruct;

import java.net.HttpURLConnection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import org.glassfish.admin.rest.Constants;
import org.glassfish.admin.rest.provider.ActionReportResultHtmlProvider;
import org.glassfish.admin.rest.provider.ActionReportResultJsonProvider;
import org.glassfish.admin.rest.provider.ActionReportResultXmlProvider;
import org.glassfish.admin.rest.provider.BaseProvider;
import org.glassfish.admin.rest.results.ActionReportResult;
import org.glassfish.admin.rest.utils.xml.RestActionReporter;
import org.glassfish.grizzly.http.Cookie;
import org.glassfish.grizzly.http.Method;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.http.server.Response;
import org.glassfish.internal.api.AdminAccessController;
import org.glassfish.internal.api.ServerContext;
import java.util.logging.Level;

/**
 * Adapter for REST interface
 * @author Rajeshwar Patil, Ludovic Champenois
 */
public abstract class RestAdapter extends HttpHandler implements Adapter, PostConstruct, EventListener {

    public final static LocalStringManagerImpl localStrings = new LocalStringManagerImpl(RestAdapter.class);

    @Inject
    volatile AdminService as = null;

    @Inject
    Events events;
    
    @Inject
    Habitat habitat;

    @Inject(name=ServerEnvironment.DEFAULT_INSTANCE_NAME)
    Config config;

    CountDownLatch latch = new CountDownLatch(1);

    @Inject
    ServerContext sc;

    @Inject
    ServerEnvironment serverEnvironment;
    
    private volatile LazyJerseyInterface lazyJerseyInterface =null;

    @Inject
    private Logger logger;

    private Map httpStatus = new HashMap() {{
        put(404, "Resource not found");
        put(500, "A server error occurred. Please check the server logs.");
    }};

    protected RestAdapter() {
        setAllowEncodedSlash(true);
    }


    @Override
    public void postConstruct() {
        epd = new AdminEndpointDecider(config, logger);
        events.register(this);
    }

    @Override
    public HttpHandler getHttpService() {
        return this;
    }

    @Override
    public void service(Request req, Response res) {
        LogHelper.getDefaultLogger().finer("Rest adapter !");
        LogHelper.getDefaultLogger().finer("Received resource request: " + req.getRequestURI());

        try {
            res.setCharacterEncoding(Constants.ENCODING);
            if (!latch.await(20L, TimeUnit.SECONDS)) {
                String msg = localStrings.getLocalString("rest.adapter.server.wait",
                        "Server cannot process this command at this time, please wait");
                reportError(req, res, HttpURLConnection.HTTP_UNAVAILABLE, msg);
                return;
            } else {

                if(serverEnvironment.isInstance()) {
                    if(!Method.GET.equals(req.getMethod())) {
                        String msg = localStrings.getLocalString("rest.resource.only.GET.on.instance", "Only GET requests are allowed on an instance that is not DAS.");
                        reportError(req, res, HttpURLConnection.HTTP_FORBIDDEN, msg);
                        return;
                    }
                }

                if (!authenticate(req)) {
                    //Could not authenticate throw error
                    String msg = localStrings.getLocalString("rest.adapter.auth.userpassword", "Invalid user name or password");
                    res.setHeader("WWW-Authenticate", "BASIC");
                    reportError(req, res, HttpURLConnection.HTTP_UNAUTHORIZED, msg);
                    return;
                }

                //Use double checked locking to lazily initialize adapter
                if (adapter == null) {
                    synchronized(HttpHandler.class) {
                        if(adapter == null) {
                            exposeContext();  //Initializes adapter
                        }
                    }

                }

                //delegate to adapter managed by Jersey.
                ((HttpHandler)adapter).service(req, res);
                int status = res.getStatus();
                if (status < 200 || status > 299) {
                    String message = httpStatus.get(status);
                    if (message == null) {
                        // i18n
                        message = "Request returned " + status;
                    }

//                    reportError(req, res, status, message);
                }
            }
        } catch(InterruptedException e) {
                String msg = localStrings.getLocalString("rest.adapter.server.wait",
                        "Server cannot process this command at this time, please wait");
                reportError(req, res, HttpURLConnection.HTTP_UNAVAILABLE, msg); //service unavailable
                return;
        } catch(IOException e) {
                String msg = localStrings.getLocalString("rest.adapter.server.ioexception",
                        "REST: IO Exception "+e.getLocalizedMessage());
                reportError(req, res, HttpURLConnection.HTTP_UNAVAILABLE, msg); //service unavailable
                return;
        } catch(LoginException e) {
            String msg = localStrings.getLocalString("rest.adapter.auth.error", "Error authenticating");
            reportError(req, res, HttpURLConnection.HTTP_UNAUTHORIZED, msg); //authentication error
            return;
        } catch (Exception e) {
            StringWriter result = new StringWriter();
            PrintWriter printWriter = new PrintWriter(result);
            e.printStackTrace(printWriter);
            String msg = localStrings.getLocalString("rest.adapter.server.exception",
                    "REST:  Exception " + result.toString());
            reportError(req, res, HttpURLConnection.HTTP_UNAVAILABLE, msg); //service unavailable
            return;
        }
    }

    private boolean authenticate(Request req) throws LoginException, IOException {
        boolean authenticated = authenticateViaAnonymousUser(req);

        if (!authenticated) {
	    authenticated = authenticateViaLocalPassword(req);
	    if (!authenticated) {
		authenticated = authenticateViaRestToken(req);
		if (!authenticated) {
		    authenticated = authenticateViaAdminRealm(req);
		}
	    }
	}

        return authenticated;
    }

    /**
     *	

This method should return true if there is an * anonymous user. It should also set an attribute called * "restUser" on the Request * containing the username of the anonymous user. If the anonymous * user is not valid, then this method should return * false.

* *

The anonymous user exists when there is only 1 admin user, * and that admin user's password is set to the empty string (""). In * this case, the user should not be prompted for a username & * password, but instead access should be automatically granted.

*/ private boolean authenticateViaAnonymousUser(Request req) { // FIXME: Implement according to JavaDoc above... /* if (anonymousUser) { String anonUser = req.setAttribute("restUser", anonUser); return true; } */ return false; } private boolean authenticateViaRestToken(Request req) { boolean authenticated = false; Cookie[] cookies = req.getCookies(); String restToken = null; if (cookies != null) { for (Cookie cookie : cookies) { if ("gfresttoken".equals(cookie.getName())) { restToken = cookie.getValue(); } } } if(restToken != null) { authenticated = SessionManager.getSessionManager().authenticate(restToken, req); } return authenticated; } private boolean authenticateViaLocalPassword(Request req) { Cookie[] cookies = req.getCookies(); boolean authenticated = false; String uid = RestService.getRestUID(); if (uid != null) { if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals("gfrestuid")) { if (cookie.getValue().equals(uid)) { authenticated = true; break; } } } } } return authenticated; } private boolean authenticateViaAdminRealm(Request req) throws LoginException, IOException { String[] up = AdminAdapter.getUserPassword(req); String user = up[0]; String password = up.length > 1 ? up[1] : ""; AdminAccessController authenticator = habitat.getByContract(AdminAccessController.class); if (authenticator != null) { return authenticator.loginAsAdmin(user, password, as.getAuthRealmName(), req.getRemoteHost(), getAuthRelatedHeaders(req), req.getUserPrincipal()) != AdminAccessController.Access.NONE; } return true; //if the authenticator is not available, allow all access - per Jerome } /** * Extract authentication related headers from Grizzly request. * This headers enables us to authenticate a request coming from DAS without a password. * The headers will be present if secured admin is not turned on and a request is sent from DAS to an instance. * @param req * @return Authentication related headers */ private Map getAuthRelatedHeaders(Request req) { Map authRelatedHeaders = Collections.EMPTY_MAP; String adminIndicatorHeader = req.getHeader(SecureAdmin.Util.ADMIN_INDICATOR_HEADER_NAME); if(adminIndicatorHeader != null) { authRelatedHeaders = new HashMap(1); authRelatedHeaders.put(SecureAdmin.Util.ADMIN_INDICATOR_HEADER_NAME, adminIndicatorHeader); } return authRelatedHeaders; } /** * Finish the response and recycle the request/response tokens. Base on * the connection header, the underlying socket transport will be closed */ // @Override // public void afterService(Request req, Response res) throws Exception { // // } /** * Notify all container event listeners that a particular event has * occurred for this Adapter. The default implementation performs * this notification synchronously using the calling thread. * * @param type Event type * @param data Event data */ public void fireAdapterEvent(String type, Object data) { } @Override public void event(@RestrictTo(EventTypes.SERVER_READY_NAME) Event event) { if (event.is(EventTypes.SERVER_READY)) { latch.countDown(); logger.fine("Ready to receive REST resource requests"); } //the count-down does not start if any other event is received } /** * Checks whether this adapter has been registered as a network endpoint. */ @Override public boolean isRegistered() { return isRegistered; } /** * Marks this adapter as having been registered or unregistered as a * network endpoint */ @Override public void setRegistered(boolean isRegistered) { this.isRegistered = isRegistered; } @Override public int getListenPort() { return epd.getListenPort(); } @Override public InetAddress getListenAddress() { return epd.getListenAddress(); } @Override public List getVirtualServers() { return epd.getAsadminHosts(); } protected abstract Set> getResourcesConfig(); private String getAcceptedMimeType(Request req) { String type = null; String requestURI = req.getRequestURI(); Set acceptableTypes = new HashSet() {{ add("html"); add("xml"); add("json"); }}; // first we look at the command extension (ie list-applications.[json | html | mf] if (requestURI.indexOf('.')!=-1) { type = requestURI.substring(requestURI.indexOf('.')+1); } else { String userAgent = req.getHeader("User-Agent"); if (userAgent != null) { String accept = req.getHeader("Accept"); if (accept != null) { if (accept.indexOf("html") != -1) {//html is possible so get it... return "html"; } StringTokenizer st = new StringTokenizer(accept, ","); while (st.hasMoreElements()) { String scheme=st.nextToken(); scheme = scheme.substring(scheme.indexOf('/')+1); if (acceptableTypes.contains(scheme)) { type = scheme; break; } } } } } return type; } // private ActionReport getClientActionReport(Request req) { // ActionReport report=null; // String requestURI = req.getRequestURI(); // String acceptedMimeType = getAcceptedMimeType(req); // report = habitat.getComponent(ActionReport.class, acceptedMimeType); // // if (report==null) { // // get the default one. // report = habitat.getComponent(ActionReport.class, "html"); // } // report.setActionDescription("REST"); // return report; // } /* * dynamically load the class that contains all references to Jersey APIs * so that Jersey is not loaded when the RestAdapter is loaded at boot time * gain a few 100millis at GlassFish startyp time + */ protected LazyJerseyInterface getLazyJersey() { if (lazyJerseyInterface != null) { return lazyJerseyInterface; } synchronized (HttpHandler.class) { if (lazyJerseyInterface == null) { try { Class lazyInitClass = Class.forName("org.glassfish.admin.rest.LazyJerseyInit"); lazyJerseyInterface = (LazyJerseyInterface) lazyInitClass.newInstance(); } catch (Exception ex) { logger.log(Level.SEVERE, "Error trying to call org.glassfish.admin.rest.LazyJerseyInit via instrospection: ", ex); } } } return lazyJerseyInterface; } private void exposeContext() throws EndpointRegistrationException { String context = getContextRoot(); logger.fine("Exposing rest resource context root: " + context); if ((context != null) || (!"".equals(context))) { Set> classes = getResourcesConfig(); adapter = lazyJerseyInterface.exposeContext(classes, sc, habitat); // ((HttpHandler) adapter).setResourcesContextPath(context); logger.info("Listening to REST requests at context: " + context + "/domain"); } } private void reportError(Request req, Response res, int statusCode, String msg) { try { // TODO: There's a lot of arm waving and flailing here. I'd like this to be cleaner, but I don't // have time at the moment. jdlee 8/11/10 RestActionReporter report = new RestActionReporter(); //getClientActionReport(req); report.setActionExitCode(ActionReport.ExitCode.FAILURE); report.setActionDescription("Error"); report.setMessage(msg); BaseProvider provider; String type = getAcceptedMimeType(req); if ("xml".equals(type)) { res.setContentType("application/xml"); provider = new ActionReportResultXmlProvider(); } else if ("json".equals(type)) { res.setContentType("application/json"); provider = new ActionReportResultJsonProvider(); } else { res.setContentType("text/html"); provider = new ActionReportResultHtmlProvider(); } res.setStatus(statusCode); res.getOutputStream().write(provider.getContent(new ActionReportResult(report)).getBytes()); res.getOutputStream().flush(); res.finish(); } catch (Exception e) { throw new RuntimeException(e); } } private volatile HttpHandler adapter = null; private boolean isRegistered = false; private AdminEndpointDecider epd = null; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy