org.glassfish.admin.rest.adapter.RestAdapter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* 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 extends Binder> 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);
}
}
}