Package org.structr.core

Source Code of org.structr.core.Services

/**
* 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.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.tooling.GlobalGraphOperations;
import org.structr.common.SecurityContext;
import org.structr.common.StructrConf;
import org.structr.core.app.App;
import org.structr.core.app.StructrApp;
import org.structr.core.graph.NodeFactory;
import org.structr.core.graph.NodeInterface;
import org.structr.core.graph.NodeService;
import org.structr.core.graph.RelationshipFactory;
import org.structr.core.graph.RelationshipInterface;
import org.structr.core.graph.Tx;
import org.structr.core.property.StringProperty;
import org.structr.module.JarConfigurationProvider;
import org.structr.schema.ConfigurationProvider;

//~--- classes ----------------------------------------------------------------

/**
* Provides access to the service layer in structr.
*
* Use the command method to obtain an instance of the desired command.
*
* @author Christian Morgner
*/
public class Services {

  private static final Logger logger                       = Logger.getLogger(StructrApp.class.getName());
  private static StructrConf baseConf                         = null;

  // Configuration constants
  public static final String INITIAL_SEED_FILE                = "seed.zip";
  public static final String BASE_PATH                        = "base.path";
  public static final String CONFIGURED_SERVICES              = "configured.services";
  public static final String CONFIG_FILE_PATH                 = "configfile.path";
  public static final String DATABASE_PATH                    = "database.path";
  public static final String FILES_PATH                       = "files.path";
  public static final String DATA_EXCHANGE_PATH               = "data.exchange.path";
  public static final String LOG_DATABASE_PATH                = "log.database.path";
  public static final String FOREIGN_TYPE                     = "foreign.type.key";
  public static final String NEO4J_SHELL_ENABLED              = "neo4j.shell.enabled";
  public static final String NEO4J_SHELL_PORT                 = "neo4j.shell.port";
  public static final String LOG_SERVICE_INTERVAL             = "structr.logging.interval";
  public static final String LOG_SERVICE_THRESHOLD            = "structr.logging.threshold";
  public static final String SERVER_IP                        = "server.ip";
  public static final String SMTP_HOST                        = "smtp.host";
  public static final String SMTP_PORT                        = "smtp.port";
  public static final String SMTP_USER                        = "smtp.user";
  public static final String SMTP_PASSWORD                    = "smtp.password";
  public static final String SUPERUSER_USERNAME               = "superuser.username";
  public static final String SUPERUSER_PASSWORD               = "superuser.password";
  public static final String TCP_PORT                         = "tcp.port";
  public static final String TMP_PATH                         = "tmp.path";
  public static final String UDP_PORT                         = "udp.port";
  public static final String JSON_INDENTATION                 = "json.indentation";
  public static final String JSON_REDUNDANCY_REDUCTION        = "json.redundancyReduction";
  public static final String GEOCODING_PROVIDER               = "geocoding.provider";
  public static final String GEOCODING_LANGUAGE               = "geocoding.language";
  public static final String GEOCODING_APIKEY                 = "geocoding.apikey";
  public static final String CONFIGURATION                    = "configuration.provider";
  public static final String TESTING                          = "testing";
  public static final String MIGRATION_KEY                    = "NodeService.migration";
  public static final String ACCESS_CONTROL_MAX_AGE           = "access.control.max.age";
  public static final String ACCESS_CONTROL_ALLOW_METHODS     = "access.control.allow.methods";
  public static final String ACCESS_CONTROL_ALLOW_HEADERS     = "access.control.allow.headers";
  public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "access.control.allow.credentials";
  public static final String ACCESS_CONTROL_EXPOSE_HEADERS    = "access.control.expose.headers";

  // singleton instance
  private static Services singletonInstance = null;

  // non-static members
  private final Map<String, Object> attributes       = new ConcurrentHashMap<>(10, 0.9f, 8);
  private final Map<Class, Service> serviceCache     = new ConcurrentHashMap<>(10, 0.9f, 8);
  private final Set<Class> registeredServiceClasses  = new LinkedHashSet<>();
  private final Set<String> configuredServiceClasses = new LinkedHashSet<>();
  private StructrConf structrConf                    = new StructrConf();
  private ConfigurationProvider configuration        = null;
  private boolean initializationDone                 = false;
  private String configuredServiceNames              = null;
  private String configurationClass                  = null;

  private Services() { }

  public static Services getInstance() {

    if (singletonInstance == null) {

      singletonInstance = new Services();
      singletonInstance.initialize();
    }

    return singletonInstance;
  }

  public static Services getInstance(final StructrConf properties) {

    if (singletonInstance == null) {

      singletonInstance = new Services();
      singletonInstance.initialize(properties);
    }

    return singletonInstance;
  }

  /**
   * Creates and returns a command of the given <code>type</code>. If a command is
   * found, the corresponding service will be discovered and activated.
   *
   * @param <T>
   * @param securityContext
   * @param commandType the runtime type of the desired command
   * @return the command
   */
  public <T extends Command> T command(SecurityContext securityContext, Class<T> commandType) {

    Class serviceClass = null;
    T command          = null;

    try {

      command = commandType.newInstance();
      command.setSecurityContext(securityContext);

      serviceClass = command.getServiceClass();

      if ((serviceClass != null) && configuredServiceClasses.contains(serviceClass.getSimpleName())) {

        // search for already running service..
        Service service = serviceCache.get(serviceClass);

        if (service == null) {

          // service not cached
          service = createService(serviceClass);

        } else {

          // check RunnableService for isRunning()..
          if (service instanceof RunnableService) {

            RunnableService runnableService = (RunnableService) service;

            if (!runnableService.isRunning()) {

              runnableService.stopService();
              runnableService.shutdown();
              service = createService(serviceClass);
            }
          }
        }

        logger.log(Level.FINEST, "Initializing command ", commandType.getName());
        service.injectArguments(command);
      }

    } catch (Throwable t) {

      t.printStackTrace();

      logger.log(Level.SEVERE, "Exception while creating command " + commandType.getName(), t);
    }

    return (command);
  }

  private void initialize() {

    final StructrConf config = getBaseConfiguration();

    // read structr.conf
    final String configTemplateFileName = "structr.conf_templ";
    final String configFileName         = "structr.conf";
    final File configTemplateFile       = new File(configTemplateFileName);
    final File configFile               = new File(configFileName);

    if (!configFile.exists() && !configTemplateFile.exists()) {

      logger.log(Level.SEVERE, "Unable to create config file, {0} and {1} do not exist, aborting. Please create a {0} configuration file and try again.", new Object[] { configFileName, configTemplateFileName } );

      // exit immediately, since we can not proceed without configuration file
      System.exit(1);
    }

    if (!configFile.exists() && configTemplateFile.exists()) {

      logger.log(Level.WARNING, "Configuration file {0} not found, copying from template {1}. Please adapt newly created {0} to your needs.", new Object[] { configFileName, configTemplateFileName } );

      try {
        Files.copy(configTemplateFile.toPath(), configFile.toPath());

      } catch (IOException ioex) {

        logger.log(Level.SEVERE, "Unable to create config file, copying of template failed.", ioex);

        System.exit(1);
      }
    }

    logger.log(Level.INFO, "Reading {0}..", configFileName);

    try {
      final FileInputStream fis = new FileInputStream(configFileName);
      structrConf.load(fis);
      fis.close();

    } catch (IOException ioex) {

      logger.log(Level.WARNING, "Unable to read configuration file {0}: {1}", new Object[] { configFileName, ioex.getMessage() } );

      System.exit(1);
    }

    mergeConfiguration(config, structrConf);

    initialize(config);

  }

  private void initialize(final StructrConf properties) {

    this.structrConf = properties;

    configurationClass     = properties.getProperty(Services.CONFIGURATION);
    configuredServiceNames = properties.getProperty(Services.CONFIGURED_SERVICES);

    // create set of configured services
    configuredServiceClasses.addAll(Arrays.asList(configuredServiceNames.split("[ ,]+")));

    // if configuration is not yet established, instantiate it
    // this is the place where the service classes get the
    // opportunity to modifyConfiguration the default configuration
    getConfigurationProvider();

    logger.log(Level.INFO, "Starting services");

    // initialize other services
    for (final String serviceClassName : configuredServiceClasses) {

        Class serviceClass = getServiceClassForName(serviceClassName);
        if (serviceClass != null) {

          try {

            final Service service = createService(serviceClass);
            if (service != null) {

              service.initialized();
            }

          } catch (Throwable t) {

            logger.log(Level.WARNING, "Exception while registering service {0}: {1}", new Object[] { serviceClassName, t });
            t.printStackTrace();
          }
        }
    }

    logger.log(Level.INFO, "{0} service(s) processed", serviceCache.size());
    registeredServiceClasses.clear();

    // do migration of an existing database
    if (getService(NodeService.class) != null) {

      if ("true".equals(properties.getProperty(Services.MIGRATION_KEY))) {
        migrateDatabase();
      }
    }

    logger.log(Level.INFO, "Registering shutdown hook.");

    // register shutdown hook
    Runtime.getRuntime().addShutdownHook(new Thread() {

      @Override
      public void run() {

        shutdown();
      }
    });

    logger.log(Level.INFO, "Initialization complete");

    initializationDone = true;
  }

  public boolean isInitialized() {
    return initializationDone;
  }

  public void shutdown() {

    initializationDone = false;

    System.out.println("INFO: Shutting down...");
    for (Service service : serviceCache.values()) {

      try {

        if (service instanceof RunnableService) {

          RunnableService runnableService = (RunnableService) service;

          if (runnableService.isRunning()) {
            runnableService.stopService();
          }
        }

        service.shutdown();

      } catch (Throwable t) {

        System.out.println("WARNING: Failed to shut down " + service.getName() + ": " + t.getMessage());
      }
    }

    serviceCache.clear();

    // shut down configuration provider
    configuration.shutdown();

    // clear singleton instance
    singletonInstance = null;

    System.out.println("INFO: Shutdown complete");

  }

  /**
   * Registers a service, enabling the service layer to automatically start
   * autorun servies.
   *
   * @param serviceClass the service class to register
   */
  public void registerServiceClass(Class serviceClass) {

    registeredServiceClasses.add(serviceClass);

    // let service instance visit default configuration
    try {

      Service service = (Service)serviceClass.newInstance();
      //service.modifyConfiguration(getBaseConfiguration());

    } catch (Throwable t) {
      t.printStackTrace();
    }
  }

  public String getConfigurationValue(String key) {
    return getConfigurationValue(key, null);
  }

  public String getConfigurationValue(String key, String defaultValue) {
    return getCurrentConfig().getProperty(key, defaultValue);
  }

  public Class getServiceClassForName(final String serviceClassName) {

    for (Class serviceClass : registeredServiceClasses) {

      if (serviceClass.getSimpleName().equals(serviceClassName)) {
        return serviceClass;
      }

    }

    return null;
  }

  public ConfigurationProvider getConfigurationProvider() {

    // instantiate configuration provider
    if (configuration == null) {

      // when executing tests, the configuration class may already exist,
      // so we don't instantiate it again since all the entities are already
      // known to the ClassLoader and we would miss the code in all the static
      // initializers.
      try {

        configuration = (ConfigurationProvider)Class.forName(configurationClass).newInstance();
        configuration.initialize();

      } catch (Throwable t) {

        t.printStackTrace();

        logger.log(Level.SEVERE, "Unable to instantiate schema provider of type {0}: {1}", new Object[] { configurationClass, t.getMessage() });
      }
    }

    return configuration;
  }

  /**
   * Store an attribute value in the service config
   *
   * @param name
   * @param value
   */
  public void setAttribute(final String name, final Object value) {
    synchronized (attributes) {
      attributes.put(name, value);
    }
  }

  /**
   * Retrieve attribute value from service config
   *
   * @param name
   * @return attribute
   */
  public Object getAttribute(final String name) {
    return attributes.get(name);
  }

  /**
   * Remove attribute value from service config
   *
   * @param name
   */
  public void removeAttribute(final String name) {
    attributes.remove(name);
  }

  private Service createService(Class serviceClass) throws InstantiationException, IllegalAccessException {

    logger.log(Level.FINE, "Creating service ", serviceClass.getName());

    Service service = (Service) serviceClass.newInstance();
    try {

      service.initialize(getCurrentConfig());

      if (service instanceof RunnableService) {

        RunnableService runnableService = (RunnableService) service;

        if (runnableService.runOnStartup()) {

          logger.log(Level.FINER, "Starting RunnableService instance ", serviceClass.getName());

          // start RunnableService and cache it
          runnableService.startService();
          serviceCache.put(serviceClass, service);
        }

      } else if (service instanceof SingletonService) {

        // cache SingletonService
        serviceCache.put(serviceClass, service);
      }

    } catch (Throwable t) {

      t.printStackTrace();

      if (service.isVital()) {

        logger.log(Level.SEVERE, "Vital service {0} failed to start with {1}, aborting.", new Object[] { service.getClass().getSimpleName(), t.getMessage() } );

        // hard(est) exit
        System.err.println("Vital service " + service.getClass().getSimpleName() + " failed to start with " + t.getMessage() + ", aborting.");
        System.exit(1);
      }
    }

    return service;
  }

  /**
   * Return all registered services
   *
   * @return list of services
   */
  public List<Service> getServices() {

    List<Service> services = new LinkedList<>();
    for (Service service : serviceCache.values()) {
      services.add(service);
    }

    return services;
  }

  public <T extends Service> T getService(final Class<T> type) {
    return (T) serviceCache.get(type);
  }

  public String getConfigValue(final Map<String, String> config, final String key, final String defaultValue) {

    String value = StringUtils.strip(config.get(key));

    if (value != null) {
      return value;
    }

    return defaultValue;
  }

  /**
   * Return true if the given service is ready to be used,
         * means initialized and running.
   *
   * @param serviceClass
   * @return isReady
   */
  public boolean isReady(final Class serviceClass) {
                Service service = serviceCache.get(serviceClass);
                return (service != null && service.isRunning());
  }

  public StructrConf getCurrentConfig() {
    return structrConf;
  }

  public Set<String> getResources() {

    final Set<String> resources = new LinkedHashSet<>();

    // scan through structr.conf and try to identify module-specific classes
    for (final Object configurationValue : structrConf.values()) {

      for (final String value : configurationValue.toString().split("[\\s ,;]+")) {

        try {

          // try to load class and find source code origin
          final Class candidate = Class.forName(value.toString());
          if (!candidate.getName().startsWith("org.structr")) {

            final String codeLocation = candidate.getProtectionDomain().getCodeSource().getLocation().toString();
            if (codeLocation.startsWith("file:") && codeLocation.endsWith(".jar") || codeLocation.endsWith(".war")) {

              resources.add(codeLocation.substring(5));
            }
          }

        } catch (Throwable ignore) { }
      }
    }

    logger.log(Level.INFO, "Found {0} possible resources: {1}", new Object[] { resources.size(), resources } );

    return resources;
  }

  public static StructrConf getBaseConfiguration() {

    if (baseConf == null) {

      baseConf = new StructrConf();

      baseConf.setProperty(CONFIGURATION,             JarConfigurationProvider.class.getName());
      baseConf.setProperty(CONFIGURED_SERVICES,       "NodeService AgentService CronService SchemaService");
      baseConf.setProperty(NEO4J_SHELL_ENABLED,       "true");
      baseConf.setProperty(NEO4J_SHELL_PORT,          "1337");
      baseConf.setProperty(JSON_INDENTATION,          "true");

      baseConf.setProperty(SUPERUSER_USERNAME,        "superadmin");
      baseConf.setProperty(SUPERUSER_PASSWORD,        RandomStringUtils.randomAlphanumeric(12));

      baseConf.setProperty(BASE_PATH,                 "");
      baseConf.setProperty(TMP_PATH,                  "/tmp");
      baseConf.setProperty(DATABASE_PATH,             System.getProperty("user.dir").concat("/db"));
      baseConf.setProperty(FILES_PATH,                System.getProperty("user.dir").concat("/files"));
      baseConf.setProperty(LOG_DATABASE_PATH,         System.getProperty("user.dir").concat("/logDb.dat"));

      baseConf.setProperty(SMTP_HOST,                 "localhost");
      baseConf.setProperty(SMTP_PORT,                 "25");
      baseConf.setProperty(TCP_PORT,                  "54555");
      baseConf.setProperty(UDP_PORT,                  "57555");
    }

    return baseConf;
  }

  public static void mergeConfiguration(final StructrConf baseConfig, final StructrConf additionalConfig) {
    baseConfig.putAll(additionalConfig);
    trim(baseConfig);
  }


  private void migrateDatabase() {

    final GraphDatabaseService graphDb     = getService(NodeService.class).getGraphDb();
    final SecurityContext superUserContext = SecurityContext.getSuperUserInstance();
    final NodeFactory nodeFactory          = new NodeFactory(superUserContext);
    final RelationshipFactory relFactory   = new RelationshipFactory(superUserContext);
    final App app                          = StructrApp.getInstance();
    final StringProperty uuidProperty      = new StringProperty("uuid");
    final int txLimit                      = 2000;

    boolean hasChanges                     = true;
    int actualNodeCount                    = 0;
    int actualRelCount                     = 0;

    logger.log(Level.INFO, "Migration of ID properties from uuid to id requested.");

    while (hasChanges) {

      hasChanges = false;

      try (final Tx tx = app.tx(false, false)) {

        // iterate over all nodes,
        final Iterator<Node> allNodes = GlobalGraphOperations.at(graphDb).getAllNodes().iterator();
        while (allNodes.hasNext()) {

          final Node node = allNodes.next();

          // do migration of our own ID properties (and only our own!)
          if (node.hasProperty("uuid") && node.getProperty("uuid") instanceof String && !node.hasProperty("id")) {

            try {
              final NodeInterface nodeInterface = nodeFactory.instantiate(node);
              final String uuid = nodeInterface.getProperty(uuidProperty);

              if (uuid != null) {

                nodeInterface.setProperty(GraphObject.id, uuid);
                nodeInterface.removeProperty(uuidProperty);
                actualNodeCount++;
                hasChanges = true;
              }

            } catch (Throwable t) {
              t.printStackTrace();
            }
          }

          // break out of loop to allow transaction to commit
          if (hasChanges && (actualNodeCount % txLimit) == 0) {
            break;
          }
        }

        tx.success();

      } catch (Throwable t) {

        t.printStackTrace();
      }

      logger.log(Level.INFO, "Migrated {0} nodes so far.", actualNodeCount);
    }

    logger.log(Level.INFO, "Migrated {0} nodes to new ID property.", actualNodeCount);

    // iterate over all relationships
    hasChanges = true;
    while (hasChanges) {

      hasChanges = false;

      try (final Tx tx = app.tx(false, false)) {

        final Iterator<Relationship> allRels = GlobalGraphOperations.at(graphDb).getAllRelationships().iterator();
        while (allRels.hasNext()) {

          final Relationship rel = allRels.next();

          // do migration of our own ID properties (and only our own!)
          if (rel.hasProperty("uuid") && rel.getProperty("uuid") instanceof String && !rel.hasProperty("id")) {

            try {
              final RelationshipInterface relInterface = relFactory.instantiate(rel);
              final String uuid = relInterface.getProperty(uuidProperty);

              if (uuid != null) {
                relInterface.setProperty(GraphObject.id, uuid);
                relInterface.removeProperty(uuidProperty);
                actualRelCount++;
                hasChanges = true;
              }

            } catch (Throwable t) {
              t.printStackTrace();
            }
          }

          // break out of loop to allow transaction to commit
          if (hasChanges && (actualRelCount % txLimit) == 0) {
            break;
          }
        }

        tx.success();

      } catch (Throwable t) {

        t.printStackTrace();
      }

      logger.log(Level.INFO, "Migrated {0} relationships so far.", actualRelCount);
    }

    logger.log(Level.INFO, "Migrated {0} relationships to new ID property.", actualRelCount);
  }

  public static String trim(final String value) {
    return StringUtils.trim(value);
  }

  public static void trim(StructrConf properties) {
    for (Object k : properties.keySet()) {
      properties.put(k, trim((String) properties.get(k)));
    }
  }


}
TOP

Related Classes of org.structr.core.Services

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.