@Test
  public void testEndpointMBeanExporterInParentChild()
      throws IntrospectionException, InstanceNotFoundException, MalformedObjectNameException,
          ReflectionException {
    this.context = new AnnotationConfigApplicationContext();
    this.context.register(
        JmxAutoConfiguration.class,
        EndpointAutoConfiguration.class,
        EndpointMBeanExportAutoConfiguration.class);

    AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
    parent.register(
        JmxAutoConfiguration.class,
        EndpointAutoConfiguration.class,
        EndpointMBeanExportAutoConfiguration.class);
    this.context.setParent(parent);

    parent.refresh();
    this.context.refresh();

    parent.close();

    System.out.println("parent " + ObjectUtils.getIdentityHexString(parent));
    System.out.println("child " + ObjectUtils.getIdentityHexString(this.context));
  }
  private void appendCache(
      Map<String, Object> output, Set<String> seen, Map<String, Object> input, String path) {

    synchronized (this.cache) {
      seen.add(ObjectUtils.getIdentityHexString(input));

      for (Entry<String, Object> entry : input.entrySet()) {
        String key = entry.getKey();
        if (StringUtils.hasText(path)) {
          if (key.startsWith("[")) {
            key = path + key;
          } else {
            key = path + "." + key;
          }
        }
        Object value = entry.getValue();
        if (value instanceof String) {
          output.put(key, value);
        } else if (value instanceof Map) {
          // Need a compound key
          @SuppressWarnings("unchecked")
          Map<String, Object> map = (Map<String, Object>) value;
          output.put(key, map);
          if (!seen.contains(ObjectUtils.getIdentityHexString(map))) {
            appendCache(output, seen, map, key);
          }
        } else if (value instanceof Collection) {
          // Need a compound key
          @SuppressWarnings("unchecked")
          Collection<Object> collection = (Collection<Object>) value;
          output.put(key, collection);
          int count = 0;
          for (Object object : collection) {
            String index = "[" + (count++) + "]";
            if (!seen.contains(ObjectUtils.getIdentityHexString(object))) {
              appendCache(output, seen, Collections.singletonMap(index, object), key);
            } else {
              output.put(key + index, object);
            }
          }
        } else {
          output.put(key, value);
        }
      }
    }
  }
 /** Returns an instance of {@code ObjectName} based on the identity of the managed resource. */
 public ObjectName getObjectName(Object managedBean, String beanKey)
     throws MalformedObjectNameException {
   String domain = ClassUtils.getPackageName(managedBean.getClass());
   Hashtable keys = new Hashtable();
   keys.put(TYPE_KEY, ClassUtils.getShortName(managedBean.getClass()));
   keys.put(HASH_CODE_KEY, ObjectUtils.getIdentityHexString(managedBean));
   return ObjectNameManager.getInstance(domain, keys);
 }
Exemple #4
0
 /**
  * Returns a <code>StringBuffer</code> containing:
  *
  * <ol>
  *   <li>the class name of the given object
  *   <li>the character '@'
  *   <li>the hex string for the object's identity hash code
  * </ol>
  *
  * <p>This method will return an empty <code>StringBuffer</code> if the given object is <code>null
  * </code>.
  *
  * @param obj the given object.
  * @return a <code>StringBuffer</code> containing identity information of the given object.
  */
 public static StringBuffer identityToString(Object obj) {
   StringBuffer buffer = new StringBuffer();
   if (obj != null) {
     buffer.append(obj.getClass().getName());
     buffer.append("@");
     buffer.append(ObjectUtils.getIdentityHexString(obj));
   }
   return buffer;
 }
 private ObjectName getObjectName(
     String domain, String beanKey, ApplicationContext applicationContext)
     throws MalformedObjectNameException {
   if (applicationContext.getParent() != null) {
     return ObjectNameManager.getInstance(
         String.format(
             "%s:type=Endpoint,name=%s,context=%s,identity=%s",
             domain,
             beanKey,
             ObjectUtils.getIdentityHexString(applicationContext),
             ObjectUtils.getIdentityHexString(applicationContext.getBean(beanKey))));
   } else {
     return ObjectNameManager.getInstance(
         String.format(
             "%s:type=Endpoint,name=%s,identity=%s",
             domain,
             beanKey,
             ObjectUtils.getIdentityHexString(applicationContext.getBean(beanKey))));
   }
 }
 @Test
 public void testContainerBeanNameWhenNoGatewayBeanName() {
   JmsOutboundGateway gateway = new JmsOutboundGateway();
   gateway.setConnectionFactory(mock(ConnectionFactory.class));
   gateway.setRequestDestinationName("foo");
   gateway.setUseReplyContainer(true);
   gateway.setReplyContainerProperties(new ReplyContainerProperties());
   gateway.afterPropertiesSet();
   assertEquals(
       "JMS_OutboundGateway@" + ObjectUtils.getIdentityHexString(gateway) + ".replyListener",
       TestUtils.getPropertyValue(gateway, "replyContainer.beanName"));
 }
  private TaskExecutor createDefaultTaskExecutor() {
    // create thread-pool for starting contexts
    ThreadGroup threadGroup =
        new ThreadGroup(
            "eclipse-gemini-blueprint-extender["
                + ObjectUtils.getIdentityHexString(this)
                + "]-threads");
    threadGroup.setDaemon(false);

    SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
    taskExecutor.setThreadGroup(threadGroup);
    taskExecutor.setThreadNamePrefix("EclipseGeminiBlueprintExtenderThread-");

    isTaskExecutorManagedInternally = true;

    return taskExecutor;
  }
  /**
   * Generate a bean name for the given bean definition, unique within the given bean factory.
   *
   * @param beanDefinition the bean definition to generate a bean name for
   * @param beanFactory the bean factory that the definition is going to be registered with (to
   *     check for existing bean names)
   * @param isInnerBean whether the given bean definition will be registered as inner bean or as
   *     top-level bean (allowing for special name generation for inner beans vs. top-level beans)
   * @return the bean name to use
   * @throws BeanDefinitionStoreException if no unique name can be generated for the given bean
   *     definition
   */
  public static String generateBeanName(
      AbstractBeanDefinition beanDefinition,
      BeanDefinitionRegistry beanFactory,
      boolean isInnerBean)
      throws BeanDefinitionStoreException {

    String generatedId = beanDefinition.getBeanClassName();
    if (generatedId == null) {
      if (beanDefinition instanceof ChildBeanDefinition) {
        generatedId = ((ChildBeanDefinition) beanDefinition).getParentName() + "$child";
      } else if (beanDefinition.getFactoryBeanName() != null) {
        generatedId = beanDefinition.getFactoryBeanName() + "$created";
      }
    }
    if (!StringUtils.hasText(generatedId)) {
      throw new BeanDefinitionStoreException(
          beanDefinition.getResourceDescription(),
          "",
          "Unnamed bean definition specifies neither 'class' nor 'parent' nor 'factory-bean'"
              + " - can't generate bean name");
    }

    String id = generatedId;
    if (isInnerBean) {
      // Inner bean: generate identity hashcode suffix.
      id =
          generatedId
              + GENERATED_BEAN_NAME_SEPARATOR
              + ObjectUtils.getIdentityHexString(beanDefinition);
    } else {
      // Top-level bean: use plain class name. If not already unique,
      // add counter - increasing the counter until the name is unique.
      int counter = 0;
      while (beanFactory.containsBeanDefinition(id)) {
        counter++;
        id = generatedId + GENERATED_BEAN_NAME_SEPARATOR + counter;
      }
    }
    return id;
  }
  /**
   * Wrap the method invocation in a stateful retry with the policy and other helpers provided. If
   * there is a failure the exception will generally be re-thrown. The only time it is not re-thrown
   * is when retry is exhausted and the recovery path is taken (though the {@link
   * MethodInvocationRecoverer} provided if there is one). In that case the value returned from the
   * method invocation will be the value returned by the recoverer (so the return type for that
   * should be the same as the intercepted method).
   *
   * @see
   *     org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
   * @see MethodInvocationRecoverer#recover(Object[], Throwable)
   */
  @Override
  public Object invoke(final MethodInvocation invocation) throws Throwable {

    if (this.logger.isDebugEnabled()) {
      this.logger.debug(
          "Executing proxied method in stateful retry: "
              + invocation.getStaticPart()
              + "("
              + ObjectUtils.getIdentityHexString(invocation)
              + ")");
    }

    Object[] args = invocation.getArguments();
    Object defaultKey = Arrays.asList(args);
    if (args.length == 1) {
      defaultKey = args[0];
    }

    Object key = createKey(invocation, defaultKey);
    RetryState retryState =
        new DefaultRetryState(
            key,
            this.newMethodArgumentsIdentifier != null
                && this.newMethodArgumentsIdentifier.isNew(args),
            this.rollbackClassifier);

    Object result =
        this.retryOperations.execute(
            new MethodInvocationRetryCallback(invocation, label),
            this.recoverer != null ? new ItemRecovererCallback(args, this.recoverer) : null,
            retryState);

    if (this.logger.isDebugEnabled()) {
      this.logger.debug("Exiting proxied method in stateful retry with result: (" + result + ")");
    }

    return result;
  }
  @Override
  public void initializeNativeSession(Session session) {
    super.initializeNativeSession(session);

    this.id = ObjectUtils.getIdentityHexString(getNativeSession());
    this.uri = session.getUpgradeRequest().getRequestURI();

    this.headers = new HttpHeaders();
    this.headers.putAll(getNativeSession().getUpgradeRequest().getHeaders());
    this.headers = HttpHeaders.readOnlyHttpHeaders(headers);

    this.acceptedProtocol = session.getUpgradeResponse().getAcceptedSubProtocol();

    List<ExtensionConfig> source = getNativeSession().getUpgradeResponse().getExtensions();
    this.extensions = new ArrayList<WebSocketExtension>(source.size());
    for (ExtensionConfig ec : source) {
      this.extensions.add(new WebSocketExtension(ec.getName(), ec.getParameters()));
    }

    if (this.user == null) {
      this.user = session.getUpgradeRequest().getUserPrincipal();
    }
  }
/**
 * An abstract class for {@link SockJsService} implementations. Provides configuration support,
 * SockJS path resolution, and processing for static SockJS requests (e.g. "/info", "/iframe.html",
 * etc). Sub-classes are responsible for handling transport requests.
 *
 * <p>It is expected that this service is mapped correctly to one or more prefixes such as "/echo"
 * including all sub-URLs (e.g. "/echo/**"). A SockJS service itself is generally unaware of request
 * mapping details but nevertheless must be able to extract the SockJS path, which is the portion of
 * the request path following the prefix. In most cases, this class can auto-detect the SockJS path
 * but you can also explicitly configure the list of valid prefixes with {@link
 * #setValidSockJsPrefixes(String...)}.
 *
 * @author Rossen Stoyanchev
 * @since 4.0
 */
public abstract class AbstractSockJsService implements SockJsService, SockJsConfiguration {

  protected final Log logger = LogFactory.getLog(getClass());

  private static final int ONE_YEAR = 365 * 24 * 60 * 60;

  private String name = "SockJSService@" + ObjectUtils.getIdentityHexString(this);

  private String clientLibraryUrl = "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js";

  private int streamBytesLimit = 128 * 1024;

  private boolean jsessionIdCookieRequired = true;

  private long heartbeatTime = 25 * 1000;

  private long disconnectDelay = 5 * 1000;

  private boolean webSocketsEnabled = true;

  private final TaskScheduler taskScheduler;

  private final List<String> validSockJsPrefixes = new ArrayList<String>();

  private final Set<String> knownSockJsPrefixes = new CopyOnWriteArraySet<String>();

  public AbstractSockJsService(TaskScheduler scheduler) {
    Assert.notNull(scheduler, "scheduler is required");
    this.taskScheduler = scheduler;
  }

  /** A unique name for the service mainly for logging purposes. */
  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return this.name;
  }

  /**
   * Use this property to configure one or more prefixes that this SockJS service is allowed to
   * serve. The prefix (e.g. "/echo") is needed to extract the SockJS specific portion of the URL
   * (e.g. "${prefix}/info", "${prefix}/iframe.html", etc).
   *
   * <p>This property is not strictly required. In most cases, the SockJS path can be auto-detected
   * since the initial request from the SockJS client is of the form "{prefix}/info". Assuming the
   * SockJS service is mapped correctly (e.g. using Ant-style pattern "/echo/**") this should work
   * fine. This property can be used to configure explicitly the prefixes this service is allowed to
   * service.
   *
   * @param prefixes the prefixes to use; prefixes do not need to include the portions of the path
   *     that represent Servlet container context or Servlet path.
   */
  public void setValidSockJsPrefixes(String... prefixes) {

    this.validSockJsPrefixes.clear();
    for (String prefix : prefixes) {
      if (prefix.endsWith("/") && (prefix.length() > 1)) {
        prefix = prefix.substring(0, prefix.length() - 1);
      }
      this.validSockJsPrefixes.add(prefix);
    }

    // sort with longest prefix at the top
    Collections.sort(
        this.validSockJsPrefixes,
        Collections.reverseOrder(
            new Comparator<String>() {
              @Override
              public int compare(String o1, String o2) {
                return new Integer(o1.length()).compareTo(new Integer(o2.length()));
              }
            }));
  }

  /**
   * Transports which don't support cross-domain communication natively (e.g. "eventsource",
   * "htmlfile") rely on serving a simple page (using the "foreign" domain) from an invisible
   * iframe. Code run from this iframe doesn't need to worry about cross-domain issues since it is
   * running from a domain local to the SockJS server. The iframe does need to load the SockJS
   * javascript client library and this option allows configuring its url.
   *
   * <p>By default this is set to point to
   * "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js".
   */
  public AbstractSockJsService setSockJsClientLibraryUrl(String clientLibraryUrl) {
    this.clientLibraryUrl = clientLibraryUrl;
    return this;
  }

  /**
   * The URL to the SockJS JavaScript client library.
   *
   * @see #setSockJsClientLibraryUrl(String)
   */
  public String getSockJsClientLibraryUrl() {
    return this.clientLibraryUrl;
  }

  public AbstractSockJsService setStreamBytesLimit(int streamBytesLimit) {
    this.streamBytesLimit = streamBytesLimit;
    return this;
  }

  @Override
  public int getStreamBytesLimit() {
    return streamBytesLimit;
  }

  /**
   * Some load balancers do sticky sessions, but only if there is a JSESSIONID cookie. Even if it is
   * set to a dummy value, it doesn't matter since session information is added by the load
   * balancer.
   *
   * <p>Set this option to indicate if a JSESSIONID cookie should be created. The default value is
   * "true".
   */
  public AbstractSockJsService setJsessionIdCookieRequired(boolean jsessionIdCookieRequired) {
    this.jsessionIdCookieRequired = jsessionIdCookieRequired;
    return this;
  }

  /**
   * Whether setting JSESSIONID cookie is necessary.
   *
   * @see #setJsessionIdCookieRequired(boolean)
   */
  public boolean isJsessionIdCookieRequired() {
    return this.jsessionIdCookieRequired;
  }

  public AbstractSockJsService setHeartbeatTime(long heartbeatTime) {
    this.heartbeatTime = heartbeatTime;
    return this;
  }

  @Override
  public long getHeartbeatTime() {
    return this.heartbeatTime;
  }

  @Override
  public TaskScheduler getTaskScheduler() {
    return this.taskScheduler;
  }

  /**
   * The amount of time in milliseconds before a client is considered disconnected after not having
   * a receiving connection, i.e. an active connection over which the server can send data to the
   * client.
   *
   * <p>The default value is 5000.
   */
  public void setDisconnectDelay(long disconnectDelay) {
    this.disconnectDelay = disconnectDelay;
  }

  /** Return the amount of time in milliseconds before a client is considered disconnected. */
  public long getDisconnectDelay() {
    return this.disconnectDelay;
  }

  /**
   * Some load balancers don't support websockets. This option can be used to disable the WebSocket
   * transport on the server side.
   *
   * <p>The default value is "true".
   */
  public void setWebSocketsEnabled(boolean webSocketsEnabled) {
    this.webSocketsEnabled = webSocketsEnabled;
  }

  /**
   * Whether WebSocket transport is enabled.
   *
   * @see #setWebSocketsEnabled(boolean)
   */
  public boolean isWebSocketEnabled() {
    return this.webSocketsEnabled;
  }

  /**
   * TODO
   *
   * @param request
   * @param response
   * @param sockJsPath
   * @throws Exception
   */
  @Override
  public final void handleRequest(
      ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler)
      throws IOException, TransportErrorException {

    String sockJsPath = getSockJsPath(request);
    if (sockJsPath == null) {
      logger.warn(
          "Could not determine SockJS path for URL \""
              + request.getURI().getPath()
              + ". Consider setting validSockJsPrefixes.");
      response.setStatusCode(HttpStatus.NOT_FOUND);
      return;
    }

    logger.debug(request.getMethod() + " with SockJS path [" + sockJsPath + "]");

    try {
      request.getHeaders();
    } catch (IllegalArgumentException ex) {
      // Ignore invalid Content-Type (TODO)
    }

    try {
      if (sockJsPath.equals("") || sockJsPath.equals("/")) {
        response
            .getHeaders()
            .setContentType(new MediaType("text", "plain", Charset.forName("UTF-8")));
        response.getBody().write("Welcome to SockJS!\n".getBytes("UTF-8"));
        return;
      } else if (sockJsPath.equals("/info")) {
        this.infoHandler.handle(request, response);
        return;
      } else if (sockJsPath.matches("/iframe[0-9-.a-z_]*.html")) {
        this.iframeHandler.handle(request, response);
        return;
      } else if (sockJsPath.equals("/websocket")) {
        handleRawWebSocketRequest(request, response, handler);
        return;
      }

      String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/");
      if (pathSegments.length != 3) {
        logger.warn("Expected \"/{server}/{session}/{transport}\" but got \"" + sockJsPath + "\"");
        response.setStatusCode(HttpStatus.NOT_FOUND);
        return;
      }

      String serverId = pathSegments[0];
      String sessionId = pathSegments[1];
      String transport = pathSegments[2];

      if (!validateRequest(serverId, sessionId, transport)) {
        response.setStatusCode(HttpStatus.NOT_FOUND);
        return;
      }

      handleTransportRequest(
          request, response, sessionId, TransportType.fromValue(transport), handler);
    } finally {
      response.flush();
    }
  }

  /** Return the SockJS path or null if the path could not be determined. */
  private String getSockJsPath(ServerHttpRequest request) {

    String path = request.getURI().getPath();

    // SockJS prefix hints?
    if (!this.validSockJsPrefixes.isEmpty()) {
      for (String prefix : this.validSockJsPrefixes) {
        int index = path.indexOf(prefix);
        if (index != -1) {
          this.knownSockJsPrefixes.add(path.substring(0, index + prefix.length()));
          return path.substring(index + prefix.length());
        }
      }
      return null;
    }

    // SockJS info request?
    if (path.endsWith("/info")) {
      this.knownSockJsPrefixes.add(path.substring(0, path.length() - "/info".length()));
      return "/info";
    }

    // Have we seen this prefix before (following the initial /info request)?
    String match = null;
    for (String sockJsPath : this.knownSockJsPrefixes) {
      if (path.startsWith(sockJsPath)) {
        if ((match == null) || (match.length() < sockJsPath.length())) {
          match = sockJsPath;
        }
      }
    }
    if (match != null) {
      String result = path.substring(match.length());
      Assert.isTrue(
          result.charAt(0) == '/',
          "Invalid SockJS path extracted from incoming path \""
              + path
              + "\". The extracted SockJS path is \""
              + result
              + "\". It was extracted from these known SockJS prefixes "
              + this.knownSockJsPrefixes
              + ". Consider setting 'validSockJsPrefixes' on DefaultSockJsService.");
      return result;
    }

    // SockJS greeting?
    String pathNoSlash = path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
    String lastSegment = pathNoSlash.substring(pathNoSlash.lastIndexOf('/') + 1);

    if ((TransportType.fromValue(lastSegment) == null) && !lastSegment.startsWith("iframe")) {
      this.knownSockJsPrefixes.add(path);
      return "";
    }

    return null;
  }

  protected abstract void handleRawWebSocketRequest(
      ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler)
      throws IOException;

  protected abstract void handleTransportRequest(
      ServerHttpRequest request,
      ServerHttpResponse response,
      String sessionId,
      TransportType transportType,
      WebSocketHandler webSocketHandler)
      throws IOException, TransportErrorException;

  protected boolean validateRequest(String serverId, String sessionId, String transport) {

    if (!StringUtils.hasText(serverId)
        || !StringUtils.hasText(sessionId)
        || !StringUtils.hasText(transport)) {
      logger.warn("Empty server, session, or transport value");
      return false;
    }

    // Server and session id's must not contain "."
    if (serverId.contains(".") || sessionId.contains(".")) {
      logger.warn("Server or session contain a \".\"");
      return false;
    }

    if (!isWebSocketEnabled() && transport.equals(TransportType.WEBSOCKET.value())) {
      logger.warn("Websocket transport is disabled");
      return false;
    }

    return true;
  }

  protected void addCorsHeaders(
      ServerHttpRequest request, ServerHttpResponse response, HttpMethod... httpMethods) {

    String origin = request.getHeaders().getFirst("origin");
    origin = ((origin == null) || origin.equals("null")) ? "*" : origin;

    response.getHeaders().add("Access-Control-Allow-Origin", origin);
    response.getHeaders().add("Access-Control-Allow-Credentials", "true");

    List<String> accessControllerHeaders =
        request.getHeaders().get("Access-Control-Request-Headers");
    if (accessControllerHeaders != null) {
      for (String header : accessControllerHeaders) {
        response.getHeaders().add("Access-Control-Allow-Headers", header);
      }
    }

    if (!ObjectUtils.isEmpty(httpMethods)) {
      response
          .getHeaders()
          .add(
              "Access-Control-Allow-Methods",
              StringUtils.arrayToDelimitedString(httpMethods, ", "));
      response.getHeaders().add("Access-Control-Max-Age", String.valueOf(ONE_YEAR));
    }
  }

  protected void addCacheHeaders(ServerHttpResponse response) {
    response.getHeaders().setCacheControl("public, max-age=" + ONE_YEAR);
    response.getHeaders().setExpires(new Date().getTime() + ONE_YEAR * 1000);
  }

  protected void addNoCacheHeaders(ServerHttpResponse response) {
    response.getHeaders().setCacheControl("no-store, no-cache, must-revalidate, max-age=0");
  }

  protected void sendMethodNotAllowed(ServerHttpResponse response, List<HttpMethod> httpMethods)
      throws IOException {
    logger.debug("Sending Method Not Allowed (405)");
    response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
    response.getHeaders().setAllow(new HashSet<HttpMethod>(httpMethods));
  }

  private interface SockJsRequestHandler {

    void handle(ServerHttpRequest request, ServerHttpResponse response) throws IOException;
  }

  private static final Random random = new Random();

  private final SockJsRequestHandler infoHandler =
      new SockJsRequestHandler() {

        private static final String INFO_CONTENT =
            "{\"entropy\":%s,\"origins\":[\"*:*\"],\"cookie_needed\":%s,\"websocket\":%s}";

        @Override
        public void handle(ServerHttpRequest request, ServerHttpResponse response)
            throws IOException {

          if (HttpMethod.GET.equals(request.getMethod())) {

            response
                .getHeaders()
                .setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));

            addCorsHeaders(request, response);
            addNoCacheHeaders(response);

            String content =
                String.format(
                    INFO_CONTENT,
                    random.nextInt(),
                    isJsessionIdCookieRequired(),
                    isWebSocketEnabled());
            response.getBody().write(content.getBytes());
          } else if (HttpMethod.OPTIONS.equals(request.getMethod())) {

            response.setStatusCode(HttpStatus.NO_CONTENT);

            addCorsHeaders(request, response, HttpMethod.OPTIONS, HttpMethod.GET);
            addCacheHeaders(response);
          } else {
            sendMethodNotAllowed(response, Arrays.asList(HttpMethod.OPTIONS, HttpMethod.GET));
          }
        }
      };

  private final SockJsRequestHandler iframeHandler =
      new SockJsRequestHandler() {

        private static final String IFRAME_CONTENT =
            "<!DOCTYPE html>\n"
                + "<html>\n"
                + "<head>\n"
                + "  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n"
                + "  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
                + "  <script>\n"
                + "    document.domain = document.domain;\n"
                + "    _sockjs_onload = function(){SockJS.bootstrap_iframe();};\n"
                + "  </script>\n"
                + "  <script src=\"%s\"></script>\n"
                + "</head>\n"
                + "<body>\n"
                + "  <h2>Don't panic!</h2>\n"
                + "  <p>This is a SockJS hidden iframe. It's used for cross domain magic.</p>\n"
                + "</body>\n"
                + "</html>";

        @Override
        public void handle(ServerHttpRequest request, ServerHttpResponse response)
            throws IOException {

          if (!HttpMethod.GET.equals(request.getMethod())) {
            sendMethodNotAllowed(response, Arrays.asList(HttpMethod.GET));
            return;
          }

          String content = String.format(IFRAME_CONTENT, getSockJsClientLibraryUrl());
          byte[] contentBytes = content.getBytes(Charset.forName("UTF-8"));
          StringBuilder builder = new StringBuilder("\"0");
          DigestUtils.appendMd5DigestAsHex(contentBytes, builder);
          builder.append('"');
          String etagValue = builder.toString();

          List<String> ifNoneMatch = request.getHeaders().getIfNoneMatch();
          if (!CollectionUtils.isEmpty(ifNoneMatch) && ifNoneMatch.get(0).equals(etagValue)) {
            response.setStatusCode(HttpStatus.NOT_MODIFIED);
            return;
          }

          response
              .getHeaders()
              .setContentType(new MediaType("text", "html", Charset.forName("UTF-8")));
          response.getHeaders().setContentLength(contentBytes.length);

          addCacheHeaders(response);
          response.getHeaders().setETag(etagValue);
          response.getBody().write(contentBytes);
        }
      };
}
 /**
  * Given a PropertyValue, return a value, resolving any references to other beans in the factory
  * if necessary. The value could be:
  * <li>A BeanDefinition, which leads to the creation of a corresponding new bean instance.
  *     Singleton flags and names of such "inner beans" are always ignored: Inner beans are
  *     anonymous prototypes.
  * <li>A RuntimeBeanReference, which must be resolved.
  * <li>A ManagedList. This is a special collection that may contain RuntimeBeanReferences or
  *     Collections that will need to be resolved.
  * <li>A ManagedSet. May also contain RuntimeBeanReferences or Collections that will need to be
  *     resolved.
  * <li>A ManagedMap. In this case the value may be a RuntimeBeanReference or Collection that will
  *     need to be resolved.
  * <li>An ordinary object or {@code null}, in which case it's left alone.
  *
  * @param argName the name of the argument that the value is defined for
  * @param value the value object to resolve
  * @return the resolved object
  */
 public Object resolveValueIfNecessary(Object argName, Object value) {
   // We must check each value to see whether it requires a runtime reference
   // to another bean to be resolved.
   if (value instanceof RuntimeBeanReference) {
     RuntimeBeanReference ref = (RuntimeBeanReference) value;
     return resolveReference(argName, ref);
   } else if (value instanceof RuntimeBeanNameReference) {
     String refName = ((RuntimeBeanNameReference) value).getBeanName();
     refName = String.valueOf(doEvaluate(refName));
     if (!this.beanFactory.containsBean(refName)) {
       throw new BeanDefinitionStoreException(
           "Invalid bean name '" + refName + "' in bean reference for " + argName);
     }
     return refName;
   } else if (value instanceof BeanDefinitionHolder) {
     // Resolve BeanDefinitionHolder: contains BeanDefinition with name and aliases.
     BeanDefinitionHolder bdHolder = (BeanDefinitionHolder) value;
     return resolveInnerBean(argName, bdHolder.getBeanName(), bdHolder.getBeanDefinition());
   } else if (value instanceof BeanDefinition) {
     // Resolve plain BeanDefinition, without contained name: use dummy name.
     BeanDefinition bd = (BeanDefinition) value;
     String innerBeanName =
         "(inner bean)"
             + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR
             + ObjectUtils.getIdentityHexString(bd);
     return resolveInnerBean(argName, innerBeanName, bd);
   } else if (value instanceof ManagedArray) {
     // May need to resolve contained runtime references.
     ManagedArray array = (ManagedArray) value;
     Class<?> elementType = array.resolvedElementType;
     if (elementType == null) {
       String elementTypeName = array.getElementTypeName();
       if (StringUtils.hasText(elementTypeName)) {
         try {
           elementType =
               ClassUtils.forName(elementTypeName, this.beanFactory.getBeanClassLoader());
           array.resolvedElementType = elementType;
         } catch (Throwable ex) {
           // Improve the message by showing the context.
           throw new BeanCreationException(
               this.beanDefinition.getResourceDescription(),
               this.beanName,
               "Error resolving array type for " + argName,
               ex);
         }
       } else {
         elementType = Object.class;
       }
     }
     return resolveManagedArray(argName, (List<?>) value, elementType);
   } else if (value instanceof ManagedList) {
     // May need to resolve contained runtime references.
     return resolveManagedList(argName, (List<?>) value);
   } else if (value instanceof ManagedSet) {
     // May need to resolve contained runtime references.
     return resolveManagedSet(argName, (Set<?>) value);
   } else if (value instanceof ManagedMap) {
     // May need to resolve contained runtime references.
     return resolveManagedMap(argName, (Map<?, ?>) value);
   } else if (value instanceof ManagedProperties) {
     Properties original = (Properties) value;
     Properties copy = new Properties();
     for (Map.Entry<Object, Object> propEntry : original.entrySet()) {
       Object propKey = propEntry.getKey();
       Object propValue = propEntry.getValue();
       if (propKey instanceof TypedStringValue) {
         propKey = evaluate((TypedStringValue) propKey);
       }
       if (propValue instanceof TypedStringValue) {
         propValue = evaluate((TypedStringValue) propValue);
       }
       copy.put(propKey, propValue);
     }
     return copy;
   } else if (value instanceof TypedStringValue) {
     // Convert value to target type here.
     TypedStringValue typedStringValue = (TypedStringValue) value;
     Object valueObject = evaluate(typedStringValue);
     try {
       Class<?> resolvedTargetType = resolveTargetType(typedStringValue);
       if (resolvedTargetType != null) {
         return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType);
       } else {
         return valueObject;
       }
     } catch (Throwable ex) {
       // Improve the message by showing the context.
       throw new BeanCreationException(
           this.beanDefinition.getResourceDescription(),
           this.beanName,
           "Error converting typed String value for " + argName,
           ex);
     }
   } else {
     return evaluate(value);
   }
 }