Package ca.uhn.fhir.rest.server

Source Code of ca.uhn.fhir.rest.server.RestfulServer

package ca.uhn.fhir.rest.server;

import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.ConformanceMethodBinding;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.provider.ServerConformanceProvider;
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
import ca.uhn.fhir.util.VersionUtil;

public abstract class RestfulServer extends HttpServlet {


  private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);

  private static final long serialVersionUID = 1L;

  private FhirContext myFhirContext;
  private INarrativeGenerator myNarrativeGenerator;
  private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
  private Object myServerConformanceProvider;
  private Map<Class<? extends IResource>, IResourceProvider> myTypeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>();
  private boolean myUseBrowserFriendlyContentTypes;
  private ISecurityManager securityManager;
  private BaseMethodBinding myServerConformanceMethod;
  private String myServerName = "HAPI FHIR Server";
  private String myServerVersion = VersionUtil.getVersion();

  public RestfulServer() {
    myFhirContext = new FhirContext();
    myServerConformanceProvider=new ServerConformanceProvider(this);
  }

  public void addHapiHeader(HttpServletResponse theHttpResponse) {
    theHttpResponse.addHeader("X-CatchingFhir", "Powered by HAPI FHIR " + VersionUtil.getVersion());
  }

  @Override
  protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    handleRequest(SearchMethodBinding.RequestType.DELETE, request, response);
  }

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    handleRequest(SearchMethodBinding.RequestType.GET, request, response);
  }

  @Override
  protected void doOptions(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
    handleRequest(SearchMethodBinding.RequestType.OPTIONS, theReq, theResp);
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    handleRequest(SearchMethodBinding.RequestType.POST, request, response);
  }

  @Override
  protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    handleRequest(SearchMethodBinding.RequestType.PUT, request, response);
  }

  private void findResourceMethods(IResourceProvider theProvider) throws Exception {

    Class<? extends IResource> resourceType = theProvider.getResourceType();
    RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(resourceType);

    ResourceBinding r = new ResourceBinding();
    r.setResourceProvider(theProvider);
    r.setResourceName(definition.getName());
    myResourceNameToProvider.put(definition.getName(), r);

    ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass());

    Class<?> clazz = theProvider.getClass();
    for (Method m : clazz.getDeclaredMethods()) {
      if (Modifier.isPublic(m.getModifiers())) {
        ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName());

        BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(theProvider.getResourceType(), m, myFhirContext, theProvider);
        if (foundMethodBinding != null) {
          r.addMethod(foundMethodBinding);
          ourLog.info(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
        } else {
          ourLog.debug(" * Method: {}#{} is not a handler", theProvider.getClass(), m.getName());
        }
      }
    }
  }

  private void findSystemMethods(Object theSystemProvider) {
    Class<?> clazz = theSystemProvider.getClass();
    for (Method m : clazz.getDeclaredMethods()) {
      if (Modifier.isPublic(m.getModifiers())) {
        ourLog.debug("Scanning public method: {}#{}", theSystemProvider.getClass(), m.getName());

        BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindSystemMethod(m, myFhirContext);
        if (foundMethodBinding != null) {
          if (foundMethodBinding instanceof ConformanceMethodBinding) {
            myServerConformanceMethod = foundMethodBinding;
          }
          ourLog.info(" * Method: {}#{} is a handler", theSystemProvider.getClass(), m.getName());
        } else {
          ourLog.debug(" * Method: {}#{} is not a handler", theSystemProvider.getClass(), m.getName());
        }
      }
    }

  }

  public FhirContext getFhirContext() {
    return myFhirContext;
  }

  public INarrativeGenerator getNarrativeGenerator() {
    return myNarrativeGenerator;
  }

  public Collection<ResourceBinding> getResourceBindings() {
    return myResourceNameToProvider.values();
  }

  /**
   * This method must be overridden to provide one or more resource providers
   */
  public abstract Collection<IResourceProvider> getResourceProviders();


  /**
   * This method should be overridden to provide a security manager instance. By default, returns null.
   */
  public ISecurityManager getSecurityManager() {
    return null;
  }

  /**
   * Returns the server conformance provider, which is the provider that
   * is used to generate the server's conformance (metadata) statement.
   * <p>
   * By default, the {@link ServerConformanceProvider} is used, but
   * this can be changed, or set to <code>null</code> if you do not wish
   * to export a conformance statement.
   * </p>
   */
  public Object getServerConformanceProvider() {
    return myServerConformanceProvider;
  }

  public IResourceProvider getServerProfilesProvider() {
    return new ServerProfileProvider(getFhirContext());
  }

  protected void handleRequest(SearchMethodBinding.RequestType requestType, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try {

      if (null != securityManager) {
        securityManager.authenticate(request);
      }

      String resourceName = null;
      String requestFullPath = StringUtils.defaultString(request.getRequestURI());
      // String contextPath = StringUtils.defaultString(request.getContextPath());
      String servletPath = StringUtils.defaultString(request.getServletPath());
      StringBuffer requestUrl = request.getRequestURL();
      String servletContextPath = "";
      if (request.getServletContext() != null) {
        servletContextPath = StringUtils.defaultIfBlank(request.getServletContext().getContextPath(), servletPath);
      } else {
        servletContextPath = servletPath;
      }

      ourLog.info("Request FullPath: {}", requestFullPath);
      ourLog.info("Servlet Path: {}", servletPath);
      ourLog.info("Request Url: {}", requestUrl);
      ourLog.info("Context Path: {}", servletContextPath);

      servletPath = servletContextPath;

      IdDt id = null;
      IdDt versionId = null;
      String operation = null;

      String requestPath = requestFullPath.substring(servletPath.length());
      if (requestPath.length() > 0 && requestPath.charAt(0) == '/') {
        requestPath = requestPath.substring(1);
      }

      int contextIndex;
      if (servletPath.length() == 0) {
        contextIndex = requestUrl.indexOf(requestPath);
      } else {
        contextIndex = requestUrl.indexOf(servletPath);
      }

      String fhirServerBase = requestUrl.substring(0, contextIndex + servletPath.length());
      if (fhirServerBase.endsWith("/")) {
        fhirServerBase = fhirServerBase.substring(0, fhirServerBase.length() - 1);
      }

      String completeUrl = StringUtils.isNotBlank(request.getQueryString()) ? requestUrl + "?" + request.getQueryString() : requestUrl.toString();

      Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());

      StringTokenizer tok = new StringTokenizer(requestPath, "/");
      if (!tok.hasMoreTokens()) {
        throw new MethodNotFoundException("No resource name specified");
      }
      resourceName = tok.nextToken();

      Object provider=null;
      ResourceBinding resourceBinding=null;
      BaseMethodBinding resourceMethod=null;
      if ("metadata".equals(resourceName)) {
        provider = myServerConformanceProvider;
        if (provider==null) {
          throw new ResourceNotFoundException("This server does not support 'metadata' query");
        }
        resourceMethod = myServerConformanceMethod;
      } else {
        resourceBinding = myResourceNameToProvider.get(resourceName);
        if (resourceBinding == null) {
          throw new MethodNotFoundException("Unknown resource type '" + resourceName+"' - Server knows how to handle: "+myResourceNameToProvider.keySet());
        }
        provider = resourceBinding.getResourceProvider();
      }


      if (tok.hasMoreTokens()) {
        String nextString = tok.nextToken();
        if (nextString.startsWith("_")) {
          operation = nextString;
        } else {
          id = new IdDt(nextString);
        }
      }

      if (tok.hasMoreTokens()) {
        String nextString = tok.nextToken();
        if (nextString.startsWith("_")) {
          if (operation !=null) {
            throw new InvalidRequestException("URL Path contains two operations (part beginning with _): " + requestPath);
          }
          operation = nextString;
        }
      }

      if (tok.hasMoreTokens()) {
        String nextString = tok.nextToken();
        versionId = new IdDt(nextString);
      }

      // TODO: look for more tokens for version, compartments, etc...

      Request r = new Request();
      r.setResourceName(resourceName);
      r.setId(id);
      r.setVersion(versionId);
      r.setOperation(operation);
      r.setParameters(params);
      r.setRequestType(requestType);
      r.setResourceProvider(provider);
      r.setInputReader(request.getReader());
      r.setFhirServerBase(fhirServerBase);
      r.setCompleteUrl(completeUrl);
      r.setServletRequest(request);

      if (resourceMethod == null && resourceBinding != null) {
        resourceMethod = resourceBinding.getMethod(r);
      }
      if (null == resourceMethod) {
        throw new MethodNotFoundException("No resource method available for the supplied parameters " + params);
      }

      resourceMethod.invokeServer(this, r, response);

    } catch (AuthenticationException e) {
      response.setStatus(e.getStatusCode());
      addHapiHeader(response);
      response.setContentType("text/plain");
      response.setCharacterEncoding("UTF-8");
      response.getWriter().write(e.getMessage());
    } catch (BaseServerResponseException e) {

      if (e instanceof InternalErrorException) {
        ourLog.error("Failure during REST processing", e);
      } else {
        ourLog.warn("Failure during REST processing: {}", e.toString());
      }

      response.setStatus(e.getStatusCode());
      addHapiHeader(response);
      response.setContentType("text/plain");
      response.setCharacterEncoding("UTF-8");
      response.getWriter().append(e.getMessage());
      response.getWriter().close();

    } catch (Throwable t) {
      // TODO: handle this better
      ourLog.error("Failed to process invocation", t);
      throw new ServletException(t);
    }

  }

  @Override
  public final void init() throws ServletException {
    initialize();
    try {
      ourLog.info("Initializing HAPI FHIR restful server");

      securityManager = getSecurityManager();
      if (null == securityManager) {
        ourLog.warn("No security manager has been provided, requests will not be authenticated!");
      }

      Collection<IResourceProvider> resourceProvider = getResourceProviders();
      for (IResourceProvider nextProvider : resourceProvider) {
        Class<? extends IResource> resourceType = nextProvider.getResourceType();
        if (resourceType==null) {
          throw new NullPointerException("getResourceType() on class '" + nextProvider.getClass().getCanonicalName() + "' returned null");
        }
        if (myTypeToProvider.containsKey(resourceType)) {
          throw new ServletException("Multiple providers for type: " + resourceType.getCanonicalName());
        }
        myTypeToProvider.put(resourceType, nextProvider);
      }

      ourLog.info("Got {} resource providers", myTypeToProvider.size());

      myFhirContext.setNarrativeGenerator(myNarrativeGenerator);

      for (IResourceProvider provider : myTypeToProvider.values()) {
        findResourceMethods(provider);
      }

      findResourceMethods(getServerProfilesProvider());
      findSystemMethods(getServerConformanceProvider());

    } catch (Exception ex) {
      ourLog.error("An error occurred while loading request handlers!", ex);
      throw new ServletException("Failed to initialize FHIR Restful server", ex);
    }

    ourLog.info("A FHIR has been lit on this server");
  }

  /**
   * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
   */
  protected void initialize() {
    // nothing by default
  }

  public boolean isUseBrowserFriendlyContentTypes() {
    return myUseBrowserFriendlyContentTypes;
  }
 
  /**
   * Sets the {@link INarrativeGenerator Narrative Generator} to use when serializing responses from this server, or <code>null</code> (which is the default) to disable narrative generation.
   * Note that this method can only be called before the server is initialized.
   *
   * @throws IllegalStateException
   *             Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
   */
  public void setNarrativeGenerator(INarrativeGenerator theNarrativeGenerator) {
    if (myFhirContext != null) {
      throw new IllegalStateException("Server has already been initialized, can not change this property");
    }
    myNarrativeGenerator = theNarrativeGenerator;
  }

  /**
   * Returns the server conformance provider, which is the provider that
   * is used to generate the server's conformance (metadata) statement.
   * <p>
   * By default, the {@link ServerConformanceProvider} is used, but
   * this can be changed, or set to <code>null</code> if you do not wish
   * to export a conformance statement.
   * </p>
   * Note that this method can only be called before the server is initialized.
   *
   * @throws IllegalStateException
   *             Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
   */
  public void setServerConformanceProvider(Object theServerConformanceProvider) {
    if (myFhirContext!=null) {
      throw new IllegalStateException("Server is already started");
    }
    myServerConformanceProvider = theServerConformanceProvider;
  }

  /**
   * If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of standard FHIR ones) when it detects that the request is coming from a browser
   * instead of a FHIR
   */
  public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
    myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
  }

  public enum NarrativeModeEnum {
    NORMAL,
    ONLY,
    SUPPRESS;
   
    public static NarrativeModeEnum valueOfCaseInsensitive(String theCode) {
      return valueOf(NarrativeModeEnum.class, theCode.toUpperCase());
    }
  }

  /**
   * Gets the server's name, as exported in conformance profiles exported by the server. This
   * is informational only, but can be helpful to set with something appropriate.
   *
   * @see RestfulServer#setServerName(StringDt)
   */
  public String getServerName() {
    return myServerName;
  }

  /**
   * Gets the server's name, as exported in conformance profiles exported by the server. This
   * is informational only, but can be helpful to set with something appropriate.
   *
   * @see RestfulServer#setServerName(StringDt)
   */
  public void setServerName(String theServerName) {
    myServerName = theServerName;
  }

  /**
   * Gets the server's version, as exported in conformance profiles exported by the server. This
   * is informational only, but can be helpful to set with something appropriate.
   */
  public void setServerVersion(String theServerVersion) {
    myServerVersion = theServerVersion;
  }

  /**
   * Gets the server's version, as exported in conformance profiles exported by the server. This
   * is informational only, but can be helpful to set with something appropriate.
   */
  public String getServerVersion() {
    return myServerVersion;
  }

}
TOP

Related Classes of ca.uhn.fhir.rest.server.RestfulServer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.