Package org.structr.rest.service

Source Code of org.structr.rest.service.HttpService

/**
* Copyright (C) 2010-2014 Morgner UG (haftungsbeschränkt)
*
* This file is part of Structr <http://structr.org>.
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Structr.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.rest.service;

import ch.qos.logback.access.jetty.RequestLogImpl;
import ch.qos.logback.access.servlet.TeeFilter;
import java.io.File;
import java.io.IOException;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.DispatcherType;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.AsyncGzipFilter;
import org.eclipse.jetty.servlets.GzipFilter;
import org.eclipse.jetty.util.resource.JarResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.structr.common.PropertyView;
import org.structr.common.StructrConf;
import org.structr.core.Command;
import org.structr.core.RunnableService;
import org.structr.core.Services;
import org.structr.core.auth.SuperUserAuthenticator;
import org.structr.rest.DefaultResourceProvider;
import org.structr.rest.ResourceProvider;
import org.structr.rest.servlet.JsonRestServlet;
import org.tuckey.web.filters.urlrewrite.UrlRewriteFilter;

/**
*
* @author Christian Morgner
*/
public class HttpService implements RunnableService {

  private static final Logger logger = Logger.getLogger(HttpService.class.getName());
  public static final String SERVLETS = "HttpService.servlets";
  public static final String RESOURCE_HANDLERS = "HttpService.resourceHandlers";
  public static final String LIFECYCLE_LISTENERS = "HttpService.lifecycle.listeners";
  public static final String MAIN_CLASS = "HttpService.mainClass";
  public static final String ASYNC = "HttpService.async";

  public static final String APPLICATION_TITLE = "application.title";
  public static final String APPLICATION_HOST = "application.host";
  public static final String APPLICATION_HTTP_PORT = "application.http.port";
  public static final String APPLICATION_HTTPS_PORT = "application.https.port";
  public static final String APPLICATION_HTTPS_ENABLED = "application.https.enabled";
  public static final String APPLICATION_KEYSTORE_PATH = "application.keystore.path";
  public static final String APPLICATION_KEYSTORE_PASSWORD = "application.keystore.password";



  // set of resource providers for this service
  private Set<ResourceProvider> resourceProviders = new LinkedHashSet<>();

  private enum LifecycleEvent {

    Started, Stopped
  }

  private Server server = null;
  private String basePath = null;
  private String applicationName = null;
  private String host = null;
  private boolean async = true;
  private int httpPort = 8082;
  private int maxIdleTime = 30000;
  private int requestHeaderSize = 8192;

  private HttpConfiguration httpConfig;
  private HttpConfiguration httpsConfig;

  @Override
  public void startService() {

    logger.log(Level.INFO, "Starting {0} (host={1}:{2}, maxIdleTime={3}, requestHeaderSize={4})", new Object[]{applicationName, host, String.valueOf(httpPort), String.valueOf(maxIdleTime), String.valueOf(requestHeaderSize)});
    logger.log(Level.INFO, "Base path {0}", basePath);
    logger.log(Level.INFO, "{0} started at http://{1}:{2}", new Object[]{applicationName, String.valueOf(host), String.valueOf(httpPort)});

    try {
      server.start();

    } catch (Exception ex) {

      logger.log(Level.SEVERE, "Unable to start HTTP service: {0}", ex);
    }

    // The jsp directory is created by the container, but we don't need it
    removeDir(basePath, "jsp");

    // send lifecycle event that the server has been started
    sendLifecycleEvent(LifecycleEvent.Started);
  }

  @Override
  public void stopService() {
  }

  @Override
  public boolean runOnStartup() {
    return true;
  }

  @Override
  public boolean isRunning() {
    return server != null && server.isRunning();
  }

  @Override
  public void injectArguments(Command command) {
  }

  @Override
  public void initialize(final StructrConf additionalConfig) {

    final StructrConf finalConfig = new StructrConf();

    // Default configuration
    finalConfig.setProperty(APPLICATION_TITLE, "structr server");
    finalConfig.setProperty(APPLICATION_HOST, "0.0.0.0");
    finalConfig.setProperty(APPLICATION_HTTP_PORT, "8082");
    finalConfig.setProperty(APPLICATION_HTTPS_ENABLED, "false");
    finalConfig.setProperty(APPLICATION_HTTPS_PORT, "8083");
    finalConfig.setProperty(ASYNC, "false");
    finalConfig.setProperty(SERVLETS, "JsonRestServlet");

    finalConfig.setProperty("JsonRestServlet.class", JsonRestServlet.class.getName());
    finalConfig.setProperty("JsonRestServlet.path", "/structr/rest/*");
    finalConfig.setProperty("JsonRestServlet.resourceprovider", DefaultResourceProvider.class.getName());
    finalConfig.setProperty("JsonRestServlet.authenticator", SuperUserAuthenticator.class.getName());
    finalConfig.setProperty("JsonRestServlet.user.class", "org.structr.dynamic.User");
    finalConfig.setProperty("JsonRestServlet.user.autocreate", "false");
    finalConfig.setProperty("JsonRestServlet.defaultview", PropertyView.Public);
    finalConfig.setProperty("JsonRestServlet.outputdepth", "3");

    Services.mergeConfiguration(finalConfig, additionalConfig);

    String mainClassName = (String) finalConfig.get(MAIN_CLASS);

    Class mainClass = null;
    if (mainClassName != null) {

      logger.log(Level.INFO, "Running main class {0}", new Object[]{mainClassName});

      try {
        mainClass = Class.forName(mainClassName);
      } catch (ClassNotFoundException ex) {
        logger.log(Level.WARNING, "Did not find class for main class from config " + mainClassName, ex);
      }

    }

    String sourceJarName = (mainClass != null ? mainClass : getClass()).getProtectionDomain().getCodeSource().getLocation().toString();
    final boolean isTest = Boolean.parseBoolean(finalConfig.getProperty(Services.TESTING, "false"));

    if (!isTest && StringUtils.stripEnd(sourceJarName, System.getProperty("file.separator")).endsWith("classes")) {

      String jarFile = System.getProperty("jarFile");
      if (StringUtils.isEmpty(jarFile)) {
        throw new IllegalArgumentException(getClass().getName() + " was started in an environment where the classloader cannot determine the JAR file containing the main class.\n"
          + "Please specify the path to the JAR file in the parameter -DjarFile.\n"
          + "Example: -DjarFile=${project.build.directory}/${project.artifactId}-${project.version}.jar");
      }
      sourceJarName = jarFile;
    }

    // load configuration from properties file
    applicationName   = finalConfig.getProperty(APPLICATION_TITLE);
    host              = finalConfig.getProperty(APPLICATION_HOST);
    basePath          = finalConfig.getProperty(Services.BASE_PATH);
    httpPort          = parseInt(finalConfig.getProperty(APPLICATION_HTTP_PORT), 8082);
    maxIdleTime       = parseInt(System.getProperty("maxIdleTime"), 30000);
    requestHeaderSize = parseInt(System.getProperty("requestHeaderSize"), 8192);
    async             = parseBoolean(finalConfig.getProperty(ASYNC), false);

    if (async) {
      logger.log(Level.INFO, "Running in asynchronous mode");
    }

    // other properties
    String keyStorePath = finalConfig.getProperty(APPLICATION_KEYSTORE_PATH);
    String keyStorePassword = finalConfig.getProperty(APPLICATION_KEYSTORE_PASSWORD);
    String contextPath = System.getProperty("contextPath", "/");
    String logPrefix = "structr";
    boolean enableRewriteFilter = true; // configurationFile.getProperty(Services.
    boolean enableHttps = parseBoolean(finalConfig.getProperty(APPLICATION_HTTPS_ENABLED), false);
    boolean enableGzipCompression = true; //
    boolean logRequests = false; //
    int httpsPort = parseInt(finalConfig.getProperty(APPLICATION_HTTPS_PORT), 8083);

    // get current base path
    basePath = System.getProperty("home", basePath);
    if (basePath.isEmpty()) {

      // use cwd and, if that fails, /tmp as a fallback
      basePath = System.getProperty("user.dir", "/tmp");
    }

    // create base directory if it does not exist
    File baseDir = new File(basePath);
    if (!baseDir.exists()) {
      baseDir.mkdirs();
    }

    server = new Server(httpPort);
    ContextHandlerCollection contexts = new ContextHandlerCollection();

    contexts.addHandler(new DefaultHandler());

    final ServletContextHandler servletContext = new ServletContextHandler(server, contextPath, true, true);
    final List<Connector> connectors = new LinkedList<>();

    // create resource collection from base path & source JAR
    try {
      servletContext.setBaseResource(new ResourceCollection(Resource.newResource(basePath), JarResource.newJarResource(Resource.newResource(sourceJarName))));

    } catch (Throwable t) {

      logger.log(Level.WARNING, "Base resource {0} not usable: {1}", new Object[]{basePath, t.getMessage()});
    }

    // this is needed for the filters to work on the root context "/"
    servletContext.addServlet("org.eclipse.jetty.servlet.DefaultServlet", "/");
    servletContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");

    if (enableRewriteFilter) {

      FilterHolder rewriteFilter = new FilterHolder(UrlRewriteFilter.class);
      rewriteFilter.setInitParameter("confPath", "urlrewrite.xml");
      servletContext.addFilter(rewriteFilter, "/*", EnumSet.of(DispatcherType.REQUEST, async ? DispatcherType.ASYNC : DispatcherType.FORWARD));
    }

    if (enableGzipCompression) {

      FilterHolder gzipFilter = async ? new FilterHolder(AsyncGzipFilter.class) : new FilterHolder(GzipFilter.class);
      gzipFilter.setInitParameter("mimeTypes", "text/html,text/plain,text/css,text/javascript,application/json");
      gzipFilter.setInitParameter("bufferSize", "32768");
      gzipFilter.setInitParameter("minGzipSize", "256");
      gzipFilter.setInitParameter("deflateCompressionLevel", "9");
      gzipFilter.setInitParameter("methods", "GET,POST");
      servletContext.addFilter(gzipFilter, "/*", EnumSet.of(DispatcherType.REQUEST, async ? DispatcherType.ASYNC : DispatcherType.FORWARD));

    }

    contexts.addHandler(servletContext);

    // enable request logging
    if (logRequests || "true".equals(finalConfig.getProperty("log.requests", "false"))) {

      String etcPath = basePath + "/etc";
      File etcDir = new File(etcPath);

      if (!etcDir.exists()) {

        etcDir.mkdir();
      }

      String logbackConfFilePath = basePath + "/etc/logback-access.xml";
      File logbackConfFile = new File(logbackConfFilePath);

      if (!logbackConfFile.exists()) {

        // synthesize a logback accees log config file
        List<String> config = new LinkedList<>();

        config.add("<configuration>");
        config.add("  <appender name=\"FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">");
        config.add("    <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">");
        config.add("      <fileNamePattern>logs/" + logPrefix + "-%d{yyyy_MM_dd}.request.log.zip</fileNamePattern>");
        config.add("    </rollingPolicy>");
        config.add("    <encoder>");
        config.add("      <charset>UTF-8</charset>");
        config.add("      <pattern>%h %l %u %t \"%r\" %s %b %n%fullRequest%n%n%fullResponse</pattern>");
        config.add("    </encoder>");
        config.add("  </appender>");
        config.add("  <appender-ref ref=\"FILE\" />");
        config.add("</configuration>");

        try {
          logbackConfFile.createNewFile();
          FileUtils.writeLines(logbackConfFile, "UTF-8", config);

        } catch (IOException ioex) {

          logger.log(Level.WARNING, "Unable to write logback configuration.", ioex);
        }
      }

      FilterHolder loggingFilter = new FilterHolder(TeeFilter.class);
      servletContext.addFilter(loggingFilter, "/*", EnumSet.of(DispatcherType.REQUEST, async ? DispatcherType.ASYNC : DispatcherType.FORWARD));
      loggingFilter.setInitParameter("includes", "");

      RequestLogHandler requestLogHandler = new RequestLogHandler();
      String logPath = basePath + "/logs";
      File logDir = new File(logPath);

      // Create logs directory if not existing
      if (!logDir.exists()) {

        logDir.mkdir();

      }

      final RequestLogImpl requestLog = new RequestLogImpl();
      requestLogHandler.setRequestLog(requestLog);

      final HandlerCollection handlers = new HandlerCollection();

      handlers.setHandlers(new Handler[]{contexts, new DefaultHandler(), requestLogHandler});

      server.setHandler(handlers);

    } else {

      server.setHandler(contexts);

    }

    final List<ContextHandler> resourceHandler = collectResourceHandlers(finalConfig);
    for (ContextHandler contextHandler : resourceHandler) {
      contexts.addHandler(contextHandler);
    }

    final Map<String, ServletHolder> servlets = collectServlets(finalConfig);

    // add servlet elements
    int position = 1;
    for (Map.Entry<String, ServletHolder> servlet : servlets.entrySet()) {

      ServletHolder servletHolder = servlet.getValue();
      String path = servlet.getKey();

      servletHolder.setInitOrder(position++);

      logger.log(Level.INFO, "Adding servlet {0} for {1}", new Object[]{servletHolder, path});

      servletContext.addServlet(servletHolder, path);
    }

    contexts.addHandler(servletContext);

    if (host != null && !host.isEmpty() && httpPort > -1) {

      httpConfig = new HttpConfiguration();
      httpConfig.setSecureScheme("https");
      httpConfig.setSecurePort(httpsPort);
      //httpConfig.setOutputBufferSize(8192);
      httpConfig.setRequestHeaderSize(requestHeaderSize);

      ServerConnector httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));

      httpConnector.setHost(host);
      httpConnector.setPort(httpPort);

      connectors.add(httpConnector);

    } else {

      logger.log(Level.WARNING, "Unable to configure HTTP server port, please make sure that {0} and {1} are set correctly in structr.conf.", new Object[]{APPLICATION_HOST, APPLICATION_HTTP_PORT});
    }

    if (enableHttps) {

      if (httpsPort > -1 && keyStorePath != null && !keyStorePath.isEmpty() && keyStorePassword != null) {

        httpsConfig = new HttpConfiguration(httpConfig);
        httpsConfig.addCustomizer(new SecureRequestCustomizer());

        SslContextFactory sslContextFactory = new SslContextFactory();
        sslContextFactory.setKeyStorePath(keyStorePath);
        sslContextFactory.setKeyStorePassword(keyStorePassword);

        ServerConnector https = new ServerConnector(server,
          new SslConnectionFactory(sslContextFactory, "http/1.1"),
          new HttpConnectionFactory(httpsConfig));

        https.setPort(httpsPort);
        https.setIdleTimeout(500000);

        https.setHost(host);
        https.setPort(httpsPort);

        connectors.add(https);

      } else {

        logger.log(Level.WARNING, "Unable to configure SSL, please make sure that {0}, {1} and {2} are set correctly in structr.conf.", new Object[]{
          APPLICATION_HTTPS_PORT,
          APPLICATION_KEYSTORE_PATH,
          APPLICATION_KEYSTORE_PASSWORD
        });
      }
    }

    if (!connectors.isEmpty()) {

      server.setConnectors(connectors.toArray(new Connector[0]));

    } else {

      logger.log(Level.SEVERE, "No connectors configured, aborting.");
      System.exit(0);
    }

    server.setStopTimeout(1000);
    server.setStopAtShutdown(true);
  }

  @Override
  public void initialized() {}

  @Override
  public void shutdown() {

    if (server != null) {

      try {
        server.stop();

      } catch (Exception ex) {

        logger.log(Level.WARNING, "Error while stopping Jetty server: {0}", ex.getMessage());
      }
    }

    // send lifecycle event that the server has been stopped
    sendLifecycleEvent(LifecycleEvent.Stopped);
  }

  @Override
  public String getName() {
    return HttpService.class.getName();
  }

  @Override
  public boolean isVital() {
    return true;
  }

  public Set<ResourceProvider> getResourceProviders() {
    return resourceProviders;
  }

  /**
   * Tries to parse the given String to an int value, returning
   * defaultValue on error.
   *
   * @param value the source String to parse
   * @param defaultValue the default value that will be returned when parsing fails
   * @return the parsed value or the given default value when parsing fails
   */
  public static int parseInt(String value, int defaultValue) {

    if (value == null) {

      return defaultValue;

    }

    try {
      return Integer.parseInt(value);
    } catch (NumberFormatException ignore) {}

    return defaultValue;
  }

  public static boolean parseBoolean(Object source, boolean defaultValue) {

    try { return Boolean.parseBoolean(source.toString()); } catch(Throwable ignore) {}

    return defaultValue;
  }

  // ----- private methods -----
  private List<ContextHandler> collectResourceHandlers(final StructrConf properties) {

    final List<ContextHandler> resourceHandlers = new LinkedList<>();

    final String resourceHandlerList = properties.getProperty(RESOURCE_HANDLERS, "");

    if (resourceHandlerList != null) {

      for (String resourceHandlerName : resourceHandlerList.split("[ \\t]+")) {

        try {

          final String contextPath = properties.getProperty(resourceHandlerName.concat(".contextPath"));
          if (contextPath != null) {

            final String resourceBase = properties.getProperty(resourceHandlerName.concat(".resourceBase"));
            if (resourceBase != null) {

              final String directoriesListed = properties.getProperty(resourceHandlerName.concat(".directoriesListed"));
              if (directoriesListed != null) {

                final String welcomeFiles = properties.getProperty(resourceHandlerName.concat(".welcomeFiles"));
                if (welcomeFiles != null) {

                  ResourceHandler resourceHandler = new ResourceHandler();
                  resourceHandler.setDirectoriesListed(Boolean.parseBoolean(directoriesListed));
                  resourceHandler.setWelcomeFiles(StringUtils.split(welcomeFiles));
                  resourceHandler.setResourceBase(resourceBase);
                  ContextHandler staticResourceHandler = new ContextHandler();
                  staticResourceHandler.setContextPath(contextPath);
                  staticResourceHandler.setHandler(resourceHandler);

                  resourceHandlers.add(staticResourceHandler);

                } else {

                  logger.log(Level.WARNING, "Unable to register resource handler {0}, missing {0}.welcomeFiles", resourceHandlerName);

                }

              } else {

                logger.log(Level.WARNING, "Unable to register resource handler {0}, missing {0}.resourceBase", resourceHandlerName);

              }

            } else {

              logger.log(Level.WARNING, "Unable to register resource handler {0}, missing {0}.resourceBase", resourceHandlerName);
            }

          } else {

            logger.log(Level.WARNING, "Unable to register resource handler {0}, missing {0}.contextPath", resourceHandlerName);
          }

        } catch (Throwable t) {

          logger.log(Level.WARNING, "Unable to initialize resource handler {0}: {1}", new Object[]{resourceHandlerName, t.getMessage()});
        }
      }

    } else {

      logger.log(Level.WARNING, "No resource handlers configured for HttpService.");
    }

    // TODO: read context handlers from configuration file
//    public Structr addResourceHandler(String contextPath, String resourceBase, boolean directoriesListed, String[] welcomeFiles) {
//
//    ResourceHandler resourceHandler = new ResourceHandler();
//    resourceHandler.setDirectoriesListed(directoriesListed);
//    resourceHandler.setWelcomeFiles(welcomeFiles);
//    resourceHandler.setResourceBase(resourceBase);
//    ContextHandler staticResourceHandler = new ContextHandler();
//    staticResourceHandler.setContextPath(contextPath);
//    staticResourceHandler.setHandler(resourceHandler);
//
//    this.resourceHandler.add(staticResourceHandler);
    return resourceHandlers;
  }

  private Map<String, ServletHolder> collectServlets(final StructrConf properties) {

    final Map<String, ServletHolder> servlets = new LinkedHashMap<>();
    final String servletNameList = properties.getProperty(SERVLETS, "");

    if (servletNameList != null) {

      for (String servletName : servletNameList.split("[ \\t]+")) {

        try {

          final String servletClassName = properties.getProperty(servletName.concat(".class"));
          if (servletClassName != null) {

            final String servletPath = properties.getProperty(servletName.concat(".path"));
            if (servletPath != null) {

              final HttpServiceServlet servlet = (HttpServiceServlet) Class.forName(servletClassName).newInstance();
              servlet.getConfig().initializeFromProperties(properties, servletName, resourceProviders);

              if (servletPath.endsWith("*")) {

                servlets.put(servletPath, new ServletHolder(servlet));

              } else {

                servlets.put(servletPath + "/*", new ServletHolder(servlet));
              }

            } else {

              logger.log(Level.WARNING, "Unable to register servlet {0}, missing {0}.path", servletName);
            }

          } else {

            logger.log(Level.WARNING, "Unable to register servlet {0}, missing {0}.class", servletName);
          }

        } catch (Throwable t) {

          logger.log(Level.WARNING, "Unable to initialize servlet {0}: {1}", new Object[]{servletName, t.getMessage()});
        }
      }

    } else {

      logger.log(Level.WARNING, "No servlets configured for HttpService.");
    }

    return servlets;
  }

  private void removeDir(final String basePath, final String directoryName) {

    String strippedBasePath = StringUtils.stripEnd(basePath, "/");

    File file = new File(strippedBasePath + "/" + directoryName);

    if (file.isDirectory()) {

      try {

        FileUtils.deleteDirectory(file);

      } catch (IOException ex) {

        logger.log(Level.SEVERE, "Unable to delete directory {0}: {1}", new Object[]{directoryName, ex.getMessage()});
      }

    } else {

      file.delete();
    }
  }

  // ----- private methods -----
  private void sendLifecycleEvent(final LifecycleEvent event) {

    // instantiate and call lifecycle callbacks from configuration file
    final String listeners = Services.getInstance().getCurrentConfig().getProperty(LIFECYCLE_LISTENERS);
    if (listeners != null) {

      final String[] listenerClasses = listeners.split("[\\s ,;]+");
      for (String listenerClass : listenerClasses) {

        try {

          final HttpServiceLifecycleListener listener = (HttpServiceLifecycleListener) Class.forName(listenerClass).newInstance();
          switch (event) {

            case Started:
              listener.serverStarted();
              break;

            case Stopped:
              listener.serverStopped();
              break;
          }

        } catch (Throwable t) {
          logger.log(Level.WARNING, "Unable to call HttpServiceLifecycleListener {0}: {1}", new Object[]{listenerClass, t.getMessage()});
        }
      }
    }
  }
}
TOP

Related Classes of org.structr.rest.service.HttpService

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.