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

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

There is a newer version: 7.2024.1.Alpha1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2009-2013 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.
 */
// Portions Copyright [2018-2021] Payara Foundation and/or affiliates

package org.glassfish.admin.rest.adapter;

import com.sun.enterprise.config.serverbeans.Config;
import com.sun.enterprise.util.LocalStringManagerImpl;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Provider;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;

import org.glassfish.admin.rest.Constants;
import org.glassfish.admin.rest.RestLogging;
import org.glassfish.admin.rest.RestService;
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.admin.restconnector.ProxiedRestAdapter;
import org.glassfish.api.ActionReport;
import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.jersey.inject.hk2.Hk2ReferencingFactory;
import org.glassfish.api.container.EndpointRegistrationException;
import org.glassfish.common.util.admin.RestSessionManager;
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.hk2.api.*;
import org.glassfish.hk2.utilities.Binder;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.internal.api.AdminAccessController;
import org.glassfish.internal.api.RemoteAdminAccessException;
import org.glassfish.internal.api.ServerContext;
import org.glassfish.jersey.internal.util.collection.Ref;
import org.glassfish.jersey.internal.util.collection.Refs;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.ContainerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.jvnet.hk2.annotations.Optional;

/**
 * Adapter for REST interface
 * @author Rajeshwar Patil, Ludovic Champenois
 * @author [email protected]
 */
public abstract class RestAdapter extends HttpHandler implements ProxiedRestAdapter, PostConstruct {
    protected static final String COOKIE_REST_TOKEN = "gfresttoken";
    protected static final String COOKIE_GF_REST_UID = "gfrestuid";
    protected static final String HEADER_ACCEPT = "Accept";
    protected static final String HEADER_USER_AGENT = "User-Agent";
    protected static final String HEADER_X_AUTH_TOKEN = "X-Auth-Token";
    protected static final String HEADER_AUTHENTICATE = "WWW-Authenticate";

    protected static final LocalStringManagerImpl localStrings = new LocalStringManagerImpl(RestService.class);

    private RestResourceProvider restResourceProvider;

    @Inject
    protected ServiceLocator habitat;

    @Inject
    @Named(ServerEnvironment.DEFAULT_INSTANCE_NAME)
    private Config config;

    private CountDownLatch latch = new CountDownLatch(1);

    @Inject
    protected ServerContext sc;

    @Inject
    protected ServerEnvironment serverEnvironment;

    @Inject
    private RestSessionManager sessionManager;

    @Inject @Optional
    protected AdminAccessController adminAuthenticator;

    private volatile JerseyContainer adapter = null;

    protected RestAdapter() {
        setAllowEncodedSlash(true);
    }

    @Override
    public void postConstruct() {
        latch.countDown();
    }

    protected String getContextRoot() {
        return getRestResourceProvider().getContextRoot();
    }

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

    @Override
    public void service(Request req, Response res) {
        RestLogging.restLogger.log(Level.FINER, "Received resource request: {0}", req.getRequestURI());
        
        try {
            res.setCharacterEncoding(Constants.ENCODING);
            if (latch.await(20L, TimeUnit.SECONDS)) {
                if(serverEnvironment.isInstance()) {
                    if(!Method.GET.equals(req.getMethod()) && !getRestResourceProvider().enableModifAccessToInstances()) {
                        reportError(req, res, HttpURLConnection.HTTP_FORBIDDEN,
                                localStrings.getLocalString("rest.resource.only.GET.on.instance",
                                "Only GET requests are allowed on an instance that is not DAS."));
                        return;
                    }
                }

                if (adminAuthenticator != null) {
                    final Subject subject = adminAuthenticator.loginAsAdmin(req);
                    req.setAttribute(Constants.REQ_ATTR_SUBJECT, subject);
                }

                String context = getContextRoot();
                if ((context != null) && (!"".equals(context)) && (adapter == null)) {
                    RestLogging.restLogger.log(Level.FINE, "Exposing rest resource context root: {0}", context);
                    adapter = exposeContext();
                    RestLogging.restLogger.log(Level.INFO, RestLogging.REST_INTERFACE_INITIALIZED, context);
                }

                ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader();
                boolean monitoring = context.equals("/monitoring");
                try {
                    // Temporarily switch classloader for monitoring so that the HK2 injection manager can be found
                    if (monitoring) {
                        ClassLoader apiClassLoader = sc.getCommonClassLoader();
                        Thread.currentThread().setContextClassLoader(apiClassLoader);
                    }

                    // Delegate to adapter managed by Jersey.
                    adapter.service(req, res);
                } finally {
                    // Switch back the classloader
                    if (monitoring) {
                        Thread.currentThread().setContextClassLoader(originalContextClassLoader);
                    }
                }

            } else { // !latch.await(...)
                reportError(req, res, HttpURLConnection.HTTP_UNAVAILABLE,
                        localStrings.getLocalString("rest.adapter.server.wait",
                        "Server cannot process this command at this time, please wait"));
            }
        } catch (InterruptedException e) {
            reportError(req, res, HttpURLConnection.HTTP_UNAVAILABLE,
                    localStrings.getLocalString("rest.adapter.server.wait",
                    "Server cannot process this command at this time, please wait")); //service unavailable
        } catch (IOException e) {
            reportError(req, res, HttpURLConnection.HTTP_UNAVAILABLE,
                    localStrings.getLocalString("rest.adapter.server.ioexception",
                    "REST: IO Exception " + e.getLocalizedMessage())); //service unavailable
        } catch (RemoteAdminAccessException e) {
            reportError(req, res, HttpURLConnection.HTTP_FORBIDDEN,
                    localStrings.getLocalString("rest.adapter.auth.forbidden",
                    "Remote access not allowed. If you desire remote access, please turn on secure admin"));
        } catch (LoginException e) {
            int status = HttpURLConnection.HTTP_UNAUTHORIZED;
            String msg = localStrings.getLocalString("rest.adapter.auth.userpassword",
                                                     "Invalid user name or password");
            res.setHeader(HEADER_AUTHENTICATE, "BASIC");
            reportError(req, res, status, msg);
        } catch (Exception e) {
            // TODO: This string is duplicated.  Can we pull this text out of the logging bundle?
            String msg = localStrings.getLocalString("rest.adapter.server.exception",
                    "An error occurred while processing the request. Please see the server logs for details.");
            RestLogging.restLogger.log(Level.INFO, RestLogging.SERVER_ERROR, e);
            reportError(req, res, HttpURLConnection.HTTP_UNAVAILABLE, msg); //service unavailable
        }
    }

    private String getAcceptedMimeType(Request req) {
        String type = null;
        String requestURI = req.getRequestURI();
        Set acceptableTypes = new HashSet(3);
        acceptableTypes.add("html");
        acceptableTypes.add("xml");
        acceptableTypes.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(HEADER_USER_AGENT);
            if (userAgent != null) {
                String accept = req.getHeader(HEADER_ACCEPT);
                if (accept != null) {
                    if (accept.contains("html")) {//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;
    }

    protected RestResourceProvider getRestResourceProvider() {
        return restResourceProvider;
    }

    protected void setRestResourceProvider(RestResourceProvider rrp) {
        this.restResourceProvider = rrp;
    }

    public static class SubjectReferenceFactory implements Factory> {

        Ref requestReference;

        @Inject
        public SubjectReferenceFactory(Provider> requestReference) {
            this.requestReference = requestReference.get();
        }

        @Override
        public Ref provide() {
            Subject subject = (Subject) requestReference.get().getAttribute(Constants.REQ_ATTR_SUBJECT);
            return Refs.of(subject);
        }

        @Override
        public void dispose(Ref t) {
        }
    }

    protected Set getAdditionalBinders(){
        return Collections.singleton(new AbstractBinder() {
            @Override
            protected void configure() {
                bindFactory(SubjectReferenceFactory.class).to(new TypeLiteral>() {
                }).in(PerLookup.class);
                
                bindFactory(Hk2ReferencingFactory.referenceFactory()).to(new TypeLiteral>() {
                }).in(RequestScoped.class);
            }
        });
    }

    /**
     * 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 100 millis at GlassFish startup time
     * @return 
     * @throws org.glassfish.api.container.EndpointRegistrationException
     */
    protected JerseyContainer exposeContext() throws EndpointRegistrationException {
        Set> classes = getRestResourceProvider().getResourceClasses(habitat);
        // Use common classloader. Jersey artifacts are not visible through
        // module classloader. Actually there is a more important reason to use CommonClassLoader.
        // jax-rs API called RuntimeDelegate makes stupid class loading assumption and throws LinkageError
        // when it finds an implementation of RuntimeDelegate that's part of WLS system class loader.
        // So, we force it to restrict its search space using common class loader.
        ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            ClassLoader apiClassLoader = sc.getCommonClassLoader();
            Thread.currentThread().setContextClassLoader(apiClassLoader);
            ResourceConfig rc = getRestResourceProvider().getResourceConfig(classes, sc, habitat, getAdditionalBinders());
            return getJerseyContainer(rc);
        } finally {
            Thread.currentThread().setContextClassLoader(originalContextClassLoader);
        }
    }

    protected JerseyContainer getJerseyContainer(ResourceConfig rc) {
        final HttpHandler httpHandler = ContainerFactory.createContainer(HttpHandler.class, rc);
        return new JerseyContainer() {
            @Override
            public void service(Request request, Response response) throws Exception {
                httpHandler.service(request, response);
            }
        };
    }

    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 (null == type) {
                res.setContentType("text/html");
                provider = new ActionReportResultHtmlProvider();
            } else switch (type) {
                case "xml":
                    res.setContentType("application/xml");
                    provider = new ActionReportResultXmlProvider();
                    break;
                case "json":
                    res.setContentType("application/json");
                    provider = new ActionReportResultJsonProvider();
                    break;
                default:
                    res.setContentType("text/html");
                    provider = new ActionReportResultHtmlProvider();
                    break;
            }
            res.setStatus(statusCode);
            res.getOutputStream().write(provider.getContent(new ActionReportResult(report)).getBytes());
            res.getOutputStream().flush();
            res.finish();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy