org.fao.geonet.OgpAppHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.fao.geonet.OgpAppHandler.java

Source

//=============================================================================
//===   Copyright (C) 2001-2007 Food and Agriculture Organization of the
//===   United Nations (FAO-UN), United Nations World Food Programme (WFP)
//===   and United Nations Environment Programme (UNEP)
//===
//===   This program 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 2 of the License, or (at
//===   your option) any later version.
//===
//===   This program 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 this program; if not, write to the Free Software
//===   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
//===
//===   Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
//===   Rome - Italy. email: geonetwork@osgeo.org
//==============================================================================

package org.fao.geonet;

import com.vividsolutions.jts.geom.MultiPolygon;

import jeeves.config.springutil.ServerBeanPropertyUpdater;
import jeeves.interfaces.ApplicationHandler;
import jeeves.server.JeevesEngine;
import jeeves.server.JeevesProxyInfo;
import jeeves.server.ServiceConfig;
import jeeves.server.context.ServiceContext;
import jeeves.server.sources.http.ServletPathFinder;
import jeeves.xlink.Processor;

import org.apache.commons.lang.StringUtils;
import org.fao.geonet.constants.Geonet;
import org.fao.geonet.domain.Metadata;
import org.fao.geonet.domain.Pair;
import org.fao.geonet.domain.Profile;
import org.fao.geonet.domain.Setting;
import org.fao.geonet.domain.User;
import org.fao.geonet.entitylistener.AbstractEntityListenerManager;
import org.fao.geonet.exceptions.OperationAbortedEx;
import org.fao.geonet.inspireatom.InspireAtomType;
import org.fao.geonet.inspireatom.harvester.InspireAtomHarvesterScheduler;
import org.fao.geonet.kernel.DataManager;
import org.fao.geonet.kernel.GeonetworkDataDirectory;
import org.fao.geonet.kernel.SchemaManager;
import org.fao.geonet.kernel.SvnManager;
import org.fao.geonet.kernel.ThesaurusManager;
import org.fao.geonet.kernel.XmlSerializer;
import org.fao.geonet.kernel.XmlSerializerSvn;
import org.fao.geonet.kernel.csw.CswHarvesterResponseExecutionService;
import org.fao.geonet.kernel.harvest.HarvestManager;
import org.fao.geonet.kernel.metadata.StatusActions;
import org.fao.geonet.kernel.oaipmh.OaiPmhDispatcher;
import org.fao.geonet.kernel.search.LuceneConfig;
import org.fao.geonet.kernel.search.SearchManager;
import org.fao.geonet.kernel.search.spatial.SpatialIndexWriter;
import org.fao.geonet.kernel.setting.SettingInfo;
import org.fao.geonet.kernel.setting.SettingManager;
import org.fao.geonet.kernel.thumbnail.ThumbnailMaker;
import org.fao.geonet.languages.LanguageDetector;
import org.fao.geonet.lib.DbLib;
import org.fao.geonet.notifier.MetadataNotifierControl;
import org.fao.geonet.repository.MetadataRepository;
import org.fao.geonet.repository.SettingRepository;
import org.fao.geonet.resources.Resources;
import org.fao.geonet.services.config.LogUtils;
import org.fao.geonet.services.metadata.BatchOpsMetadataReindexer;
import org.fao.geonet.services.metadata.format.Format;
import org.fao.geonet.services.metadata.format.FormatType;
import org.fao.geonet.services.metadata.format.FormatterWidth;
import org.fao.geonet.services.util.z3950.Repositories;
import org.fao.geonet.services.util.z3950.Server;
import org.fao.geonet.util.ThreadUtils;
import org.fao.geonet.utils.IO;
import org.fao.geonet.utils.Log;
import org.fao.geonet.utils.ProxyInfo;
import org.fao.geonet.utils.XmlResolver;
import org.fao.geonet.wro4j.GeonetWro4jFilter;
import org.geotools.data.DataStore;
import org.geotools.data.shapefile.indexed.IndexType;
import org.geotools.data.shapefile.indexed.IndexedShapefileDataStore;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.jdom.Element;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.ServletWebRequest;

import java.io.File;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.servlet.ServletContext;
import javax.sql.DataSource;

/**
 * This is the main class, it handles http connections and inits the system.
 */
public class OgpAppHandler implements ApplicationHandler {
    private Logger logger;
    private Path appPath;
    private SearchManager searchMan;
    private MetadataNotifierControl metadataNotifierControl;
    private ConfigurableApplicationContext _applicationContext;

    //---------------------------------------------------------------------------
    //---
    //--- GetContextName
    //---
    //---------------------------------------------------------------------------

    public String getContextName() {
        return Geonet.CONTEXT_NAME;
    }

    //---------------------------------------------------------------------------
    //---
    //--- Start
    //---
    //---------------------------------------------------------------------------

    /**
     * Inits the engine, loading all needed data.
     */
    public Object start(Element config, ServiceContext context) throws Exception {
        context.setAsThreadLocal();
        this._applicationContext = context.getApplicationContext();
        ApplicationContextHolder.set(this._applicationContext);

        logger = context.getLogger();
        // If an error occur during logger configuration
        // Continue starting the application with
        // a logger initialized with the default log4j.xml.
        try {
            LogUtils.refreshLogConfiguration();
        } catch (OperationAbortedEx e) {
            logger.error("Error while setting log configuration. "
                    + "Check the setting in the database for logger configuration file.");
            logger.error(e.getMessage());
        }
        ConfigurableListableBeanFactory beanFactory = context.getApplicationContext().getBeanFactory();

        ServletPathFinder finder = new ServletPathFinder(this._applicationContext.getBean(ServletContext.class));
        appPath = finder.getAppPath();
        String baseURL = context.getBaseUrl();
        String webappName = baseURL.substring(1);
        // TODO : if webappName is "". ie no context

        final SystemInfo systemInfo = _applicationContext.getBean(SystemInfo.class);
        String version = systemInfo.getVersion();
        String subVersion = systemInfo.getSubVersion();

        logger.info("Initializing GeoNetwork " + version + "." + subVersion + " ...");

        // Get main service config handler
        @SuppressWarnings("unchecked")
        List<Element> serviceConfigElems = config.getChildren();
        ServiceConfig handlerConfig = new ServiceConfig(serviceConfigElems);

        // Init configuration directory
        final GeonetworkDataDirectory dataDirectory = _applicationContext.getBean(GeonetworkDataDirectory.class);
        dataDirectory.init(webappName, appPath, handlerConfig, context.getServlet());

        // Get config handler properties
        String systemDataDir = handlerConfig.getMandatoryValue(Geonet.Config.SYSTEM_DATA_DIR);
        String thesauriDir = handlerConfig.getMandatoryValue(Geonet.Config.CODELIST_DIR);
        String luceneDir = handlerConfig.getMandatoryValue(Geonet.Config.LUCENE_DIR);
        String luceneConfigXmlFile = handlerConfig.getMandatoryValue(Geonet.Config.LUCENE_CONFIG);

        logger.info("Data directory: " + systemDataDir);

        setProps(appPath, handlerConfig);

        importDatabaseData(context);

        // Status actions class - load it
        String statusActionsClassName = handlerConfig.getMandatoryValue(Geonet.Config.STATUS_ACTIONS_CLASS);
        @SuppressWarnings("unchecked")
        Class<StatusActions> statusActionsClass = (Class<StatusActions>) Class.forName(statusActionsClassName);

        JeevesJCS.setConfigFilename(appPath.resolve("WEB-INF/classes/cache.ccf"));

        // force caches to be config'd so shutdown hook works correctly
        JeevesJCS.getInstance(Processor.XLINK_JCS);
        JeevesJCS.getInstance(XmlResolver.XMLRESOLVER_JCS);

        //------------------------------------------------------------------------
        //--- initialize settings subsystem

        logger.info("  - Setting manager...");

        SettingManager settingMan = this._applicationContext.getBean(SettingManager.class);

        //--- initialize ThreadUtils with setting manager and rm props
        final DataSource dataSource = context.getBean(DataSource.class);
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            ThreadUtils.init(conn.getMetaData().getURL(), settingMan);
        } finally {
            if (conn != null) {
                conn.close();
            }
        }

        //------------------------------------------------------------------------
        //--- initialize Z39.50

        logger.info("  - Z39.50...");

        boolean z3950Enable = settingMan.getValueAsBool("system/z3950/enable", false);
        String z3950port = settingMan.getValue("system/z3950/port");

        logger.info("     - Z39.50 is enabled: " + z3950Enable);
        if (z3950Enable) {
            // build Z3950 repositories file first from template
            URL url = getClass().getClassLoader().getResource(Geonet.File.JZKITCONFIG_TEMPLATE);

            if (Repositories.build(url, context)) {
                logger.info("     Repositories file built from template.");

                try {
                    ConfigurableApplicationContext appContext = context.getApplicationContext();

                    // to have access to the GN context in spring-managed objects
                    ContextContainer cc = (ContextContainer) appContext.getBean("ContextGateway");
                    cc.setSrvctx(context);

                    if (!z3950Enable) {
                        logger.info("     Server is Disabled.");
                    } else {
                        logger.info("     Server is Enabled.");

                        Server.init(z3950port, appContext);
                    }
                } catch (Exception e) {
                    logger.error(
                            "     Repositories file init FAILED - Z3950 server disabled and Z3950 client services (remote search, "
                                    + "harvesting) may not work. Error is:" + e.getMessage());
                    e.printStackTrace();
                }

            } else {
                logger.error(
                        "     Repositories file builder FAILED - Z3950 server disabled and Z3950 client services (remote search, "
                                + "harvesting) may not work.");
            }
        }
        //------------------------------------------------------------------------
        //--- initialize SchemaManager

        logger.info("  - Schema manager...");

        Path schemaPluginsDir = dataDirectory.getSchemaPluginsDir();
        Path schemaCatalogueFile = dataDirectory.getConfigDir().resolve(Geonet.File.SCHEMA_PLUGINS_CATALOG);
        boolean createOrUpdateSchemaCatalog = handlerConfig
                .getMandatoryValue(Geonet.Config.SCHEMA_PLUGINS_CATALOG_UPDATE).equals("true");
        logger.info("         - Schema plugins directory: " + schemaPluginsDir);
        logger.info("         - Schema Catalog File     : " + schemaCatalogueFile);
        SchemaManager schemaMan = _applicationContext.getBean(SchemaManager.class);
        schemaMan.configure(_applicationContext, appPath, dataDirectory.getResourcesDir(), schemaCatalogueFile,
                schemaPluginsDir, context.getLanguage(),
                handlerConfig.getMandatoryValue(Geonet.Config.PREFERRED_SCHEMA), createOrUpdateSchemaCatalog);

        //------------------------------------------------------------------------
        //--- initialize search and editing

        logger.info("  - Search...");

        boolean logSpatialObject = "true"
                .equalsIgnoreCase(handlerConfig.getMandatoryValue(Geonet.Config.STAT_LOG_SPATIAL_OBJECTS));
        boolean logAsynch = "true".equalsIgnoreCase(handlerConfig.getMandatoryValue(Geonet.Config.STAT_LOG_ASYNCH));
        logger.info("  - Log spatial object: " + logSpatialObject);
        logger.info("  - Log in asynch mode: " + logAsynch);

        String luceneTermsToExclude = "";
        luceneTermsToExclude = handlerConfig.getMandatoryValue(Geonet.Config.STAT_LUCENE_TERMS_EXCLUDE);

        LuceneConfig lc = _applicationContext.getBean(LuceneConfig.class);
        lc.configure(luceneConfigXmlFile);
        logger.info("  - Lucene configuration is:");
        logger.info(lc.toString());

        try {
            _applicationContext.getBean(DataStore.class);
        } catch (NoSuchBeanDefinitionException e) {
            DataStore dataStore = createShapefileDatastore(luceneDir);
            _applicationContext.getBeanFactory().registerSingleton("dataStore", dataStore);
            //--- no datastore for spatial indexing means that we can't continue
            if (dataStore == null) {
                throw new IllegalArgumentException(
                        "GeoTools datastore creation failed - check logs for more info/exceptions");
            }
        }

        String maxWritesInTransactionStr = handlerConfig.getMandatoryValue(Geonet.Config.MAX_WRITES_IN_TRANSACTION);
        int maxWritesInTransaction = SpatialIndexWriter.MAX_WRITES_IN_TRANSACTION;
        try {
            maxWritesInTransaction = Integer.parseInt(maxWritesInTransactionStr);
        } catch (NumberFormatException nfe) {
            logger.error(
                    "Invalid config parameter: maximum number of writes to spatial index in a transaction (maxWritesInTransaction)"
                            + ", Using " + maxWritesInTransaction + " instead.");
            nfe.printStackTrace();
        }

        SettingInfo settingInfo = context.getBean(SettingInfo.class);
        searchMan = _applicationContext.getBean(SearchManager.class);
        searchMan.init(logAsynch, logSpatialObject, luceneTermsToExclude, maxWritesInTransaction);

        // if the validator exists the proxyCallbackURL needs to have the external host and
        // servlet name added so that the cas knows where to send the validation notice
        ServerBeanPropertyUpdater.updateURL(settingInfo.getSiteUrl(true) + baseURL, _applicationContext);

        //------------------------------------------------------------------------
        //--- extract intranet ip/mask and initialize AccessManager

        logger.info("  - Access manager...");

        //------------------------------------------------------------------------
        //--- get edit params and initialize DataManager

        logger.info("  - Xml serializer and Data manager...");

        SvnManager svnManager = _applicationContext.getBean(SvnManager.class);
        XmlSerializer xmlSerializer = _applicationContext.getBean(XmlSerializer.class);

        if (xmlSerializer instanceof XmlSerializerSvn && svnManager != null) {
            svnManager.setContext(context);
            Path subversionPath = dataDirectory.getMetadataRevisionDir().toAbsolutePath().normalize();
            svnManager.setSubversionPath(subversionPath.toString());
            svnManager.init();
        }

        /**
         * Initialize language detector
         */
        LanguageDetector.init(
                appPath.resolve(_applicationContext.getBean(Geonet.Config.LANGUAGE_PROFILES_DIR, String.class)));

        //------------------------------------------------------------------------
        //--- Initialize thesaurus

        logger.info("  - Thesaurus...");

        _applicationContext.getBean(ThesaurusManager.class).init(false, context, thesauriDir);

        //------------------------------------------------------------------------
        //--- initialize catalogue services for the web

        logger.info("  - Open Archive Initiative (OAI-PMH) server...");

        OaiPmhDispatcher oaipmhDis = new OaiPmhDispatcher(settingMan, schemaMan);

        GeonetContext gnContext = new GeonetContext(_applicationContext, false, statusActionsClass);

        //------------------------------------------------------------------------
        //--- return application context

        beanFactory.registerSingleton("serviceHandlerConfig", handlerConfig);
        beanFactory.registerSingleton("oaipmhDisatcher", oaipmhDis);

        _applicationContext.getBean(DataManager.class).init(context, false);
        _applicationContext.getBean(HarvestManager.class).init(context, gnContext.isReadOnly());

        _applicationContext.getBean(ThumbnailMaker.class).init(context);

        logger.info("Site ID is : " + settingMan.getSiteId());

        // Creates a default site logo, only if the logo image doesn't exists
        // This can happen if the application has been updated with a new version preserving the database and
        // images/logos folder is not copied from old application
        createSiteLogo(settingMan.getSiteId(), context, context.getAppPath());

        // Notify unregistered metadata at startup. Needed, for example, when the user enables the notifier config
        // to notify the existing metadata in database
        // TODO: Fix DataManager.getUnregisteredMetadata and uncomment next lines
        metadataNotifierControl = new MetadataNotifierControl(context);
        metadataNotifierControl.runOnce();

        // Reindex Blank template metadata
        DataManager dm = _applicationContext.getBean(DataManager.class);
        Set<Integer> metadataToReindex = new HashSet<>();
        metadataToReindex.add(new Integer(1));
        context.info("Re-indexing metadata");
        BatchOpsMetadataReindexer r = new BatchOpsMetadataReindexer(dm, metadataToReindex);
        r.process();

        //--- load proxy information from settings into Jeeves for observers such
        //--- as jeeves.utils.XmlResolver to use
        ProxyInfo pi = JeevesProxyInfo.getInstance();
        boolean useProxy = settingMan.getValueAsBool("system/proxy/use", false);
        if (useProxy) {
            String proxyHost = settingMan.getValue("system/proxy/host");
            String proxyPort = settingMan.getValue("system/proxy/port");
            String username = settingMan.getValue("system/proxy/username");
            String password = settingMan.getValue("system/proxy/password");
            pi.setProxyInfo(proxyHost, Integer.valueOf(proxyPort), username, password);
        }

        boolean inspireEnable = settingMan.getValueAsBool("system/inspire/enable", false);

        if (inspireEnable) {

            String atomType = settingMan.getValue("system/inspire/atom");
            String atomSchedule = settingMan.getValue("system/inspire/atomSchedule");

            if (StringUtils.isNotEmpty(atomType) && StringUtils.isNotEmpty(atomSchedule)
                    && atomType.equalsIgnoreCase(InspireAtomType.ATOM_REMOTE)) {
                logger.info("  - INSPIRE ATOM feed harvester ...");

                InspireAtomHarvesterScheduler.schedule(atomSchedule, context, gnContext);
            }
        }

        //
        // db heartbeat configuration -- for failover to readonly database
        //
        boolean dbHeartBeatEnabled = Boolean
                .parseBoolean(handlerConfig.getValue(Geonet.Config.DB_HEARTBEAT_ENABLED, "false"));
        if (dbHeartBeatEnabled) {
            Integer dbHeartBeatInitialDelay = Integer
                    .parseInt(handlerConfig.getValue(Geonet.Config.DB_HEARTBEAT_INITIALDELAYSECONDS, "5"));
            Integer dbHeartBeatFixedDelay = Integer
                    .parseInt(handlerConfig.getValue(Geonet.Config.DB_HEARTBEAT_FIXEDDELAYSECONDS, "60"));
            createDBHeartBeat(gnContext, dbHeartBeatInitialDelay, dbHeartBeatFixedDelay);
        }

        fillCaches(context);

        AbstractEntityListenerManager.setSystemRunning(true);
        return gnContext;
    }

    private void fillCaches(final ServiceContext context) {
        final Format formatService = context.getBean(Format.class); // this will initialize the formatter

        Thread fillCaches = new Thread(new Runnable() {
            @Override
            public void run() {
                final ServletContext servletContext = context.getServlet().getServletContext();
                context.setAsThreadLocal();
                ApplicationContextHolder.set(_applicationContext);
                GeonetWro4jFilter filter = (GeonetWro4jFilter) servletContext
                        .getAttribute(GeonetWro4jFilter.GEONET_WRO4J_FILTER_KEY);

                @SuppressWarnings("unchecked")
                List<String> wro4jUrls = _applicationContext.getBean("wro4jUrlsToInitialize", List.class);

                for (String wro4jUrl : wro4jUrls) {
                    Log.info(Geonet.GEONETWORK, "Initializing the WRO4J group: " + wro4jUrl + " cache");
                    final MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext, "GET",
                            "/static/" + wro4jUrl);
                    final MockHttpServletResponse response = new MockHttpServletResponse();
                    try {
                        filter.doFilter(servletRequest, response, new MockFilterChain());
                    } catch (Throwable t) {
                        Log.info(Geonet.GEONETWORK,
                                "Error while initializing the WRO4J group: " + wro4jUrl + " cache", t);
                    }
                }

                final Page<Metadata> metadatas = _applicationContext.getBean(MetadataRepository.class)
                        .findAll(new PageRequest(0, 1));
                if (metadatas.getNumberOfElements() > 0) {
                    Integer mdId = metadatas.getContent().get(0).getId();
                    context.getUserSession().loginAs(
                            new User().setName("admin").setProfile(Profile.Administrator).setUsername("admin"));
                    @SuppressWarnings("unchecked")
                    List<String> formattersToInitialize = _applicationContext.getBean("formattersToInitialize",
                            List.class);

                    for (String formatterName : formattersToInitialize) {
                        Log.info(Geonet.GEONETWORK, "Initializing the Formatter with id: " + formatterName);
                        final MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext);
                        final MockHttpServletResponse response = new MockHttpServletResponse();
                        try {
                            formatService.exec("eng", FormatType.html.toString(), mdId.toString(), null,
                                    formatterName, Boolean.TRUE.toString(), false, FormatterWidth._100,
                                    new ServletWebRequest(servletRequest, response));
                        } catch (Throwable t) {
                            Log.info(Geonet.GEONETWORK,
                                    "Error while initializing the Formatter with id: " + formatterName, t);
                        }
                    }
                }
            }
        });
        fillCaches.setDaemon(true);
        fillCaches.setName("Fill Caches Thread");
        fillCaches.setPriority(Thread.MIN_PRIORITY);
        fillCaches.start();
    }

    private void importDatabaseData(final ServiceContext context) {
        // check if database has any data
        final SettingRepository settingRepository = context.getBean(SettingRepository.class);
        final long count = settingRepository.count();
        if (count == 0) {
            try {
                // import data from init files
                List<Pair<String, String>> importData = context.getApplicationContext().getBean("initial-data",
                        List.class);
                final DbLib dbLib = new DbLib();
                for (Pair<String, String> pair : importData) {
                    final ServletContext servletContext = context.getServlet().getServletContext();
                    final Path appPath = context.getAppPath();
                    final Path filePath = IO.toPath(pair.one());
                    final String filePrefix = pair.two();
                    Log.warning(Geonet.DB, "Executing SQL from: " + filePath + " " + filePrefix);
                    dbLib.insertData(servletContext, context, appPath, filePath, filePrefix);
                }
                String siteUuid = UUID.randomUUID().toString();
                context.getBean(SettingManager.class).setSiteUuid(siteUuid);

                // Reload services which may be defined in
                // database creation scripts in Services table.
                context.getBean(JeevesEngine.class).loadConfigDB(context.getApplicationContext(), -1);
            } catch (Throwable t) {
                Log.error(Geonet.DB, "Error occurred while trying to execute SQL", t);
                throw new RuntimeException(t);
            }
        }
    }

    /**
     * Sets up a periodic check whether GeoNetwork can successfully write to the database. If it can't, GeoNetwork will
     * automatically switch to read-only mode.
     */
    private void createDBHeartBeat(final GeonetContext gc, Integer initialDelay, Integer fixedDelay)
            throws SchedulerException {
        logger.info("creating DB heartbeat with initial delay of " + initialDelay + " s and fixed delay of "
                + fixedDelay + " s");
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        Runnable DBHeartBeat = new Runnable() {
            @Override
            public void run() {
                try {
                    boolean readOnly = gc.isReadOnly();
                    logger.debug("DBHeartBeat: GN is read-only ? " + readOnly);
                    boolean canWrite = checkDBWrite();
                    HarvestManager hm = gc.getBean(HarvestManager.class);
                    if (readOnly && canWrite) {
                        logger.warning("GeoNetwork can write to the database, switching to read-write mode");
                        readOnly = false;
                        gc.setReadOnly(readOnly);
                        hm.setReadOnly(readOnly);
                    } else if (!readOnly && !canWrite) {
                        logger.warning("GeoNetwork can not write to the database, switching to read-only mode");
                        readOnly = true;
                        gc.setReadOnly(readOnly);
                        hm.setReadOnly(readOnly);
                    } else {
                        if (readOnly) {
                            logger.info("GeoNetwork remains in read-only mode");
                        } else {
                            logger.debug("GeoNetwork remains in read-write mode");
                        }
                    }
                } catch (Throwable x) {
                    // any uncaught exception would cause the scheduled execution to silently stop
                    logger.error("DBHeartBeat error: " + x.getMessage() + " This error is ignored.");
                    x.printStackTrace();
                }
            }

            private boolean checkDBWrite() {
                SettingRepository settingsRepo = gc.getBean(SettingRepository.class);
                try {
                    Setting newSetting = settingsRepo.save(new Setting().setName("DBHeartBeat").setValue("value"));
                    settingsRepo.flush();
                    settingsRepo.delete(newSetting);
                    return true;
                } catch (Exception x) {
                    logger.info("DBHeartBeat Exception: " + x.getMessage());
                    return false;
                }
            }
        };
        scheduledExecutorService.scheduleWithFixedDelay(DBHeartBeat, initialDelay, fixedDelay, TimeUnit.SECONDS);
    }

    /**
     * Creates a default site logo, only if the logo image doesn't exists
     *
     * @param nodeUuid
     * @param context
     * @param appPath
     */
    private void createSiteLogo(String nodeUuid, ServiceContext context, Path appPath) {
        try {
            Path logosDir = Resources.locateLogosDir(context);
            Path logo = logosDir.resolve(nodeUuid + ".png");
            if (!Files.exists(logo)) {
                final ServletContext servletContext = context.getServlet().getServletContext();
                byte[] logoData = Resources
                        .loadImage(servletContext, appPath, "images/logos/OGP-icon-24.png", new byte[0]).one();
                Files.write(logo, logoData);
            }
        } catch (Throwable e) {
            logger.error("      Error when setting the logo: " + e.getMessage());
        }
    }

    /**
     * Set system properties to those required
     *
     * @param webappDir webapp path
     */
    private void setProps(Path webappDir, ServiceConfig handlerConfig) {

        final Path configDir = IO.toPath(handlerConfig.getValue(Geonet.Config.CONFIG_DIR));
        final Path schemapluginUriCatalog = configDir.resolve("schemaplugin-uri-catalog.xml");
        Path webInf = SchemaManager.registerXmlCatalogFiles(webappDir, schemapluginUriCatalog);

        //--- Set mime-mappings
        String mimeProp = System.getProperty("mime-mappings");
        if (mimeProp == null)
            mimeProp = "";
        if (!mimeProp.equals("")) {
            logger.info("Overriding mime-mappings property (was set to " + mimeProp + ")");
        }
        mimeProp = webInf.resolve("mime-types.properties").toString();
        System.setProperty("mime-mappings", mimeProp);
        logger.info("mime-mappings property set to " + mimeProp);

    }

    //---------------------------------------------------------------------------
    //---
    //--- Stop
    //---
    //---------------------------------------------------------------------------

    public void stop() {
        logger.info("Stopping geonetwork...");
        AbstractEntityListenerManager.setSystemRunning(false);

        logger.info("shutting down CSW HarvestResponse executionService");
        CswHarvesterResponseExecutionService.getExecutionService().shutdownNow();

        //------------------------------------------------------------------------
        //--- end search
        logger.info("  - search...");

        try {
            searchMan.end();
        } catch (Exception e) {
            logger.error("Raised exception while stopping search");
            logger.error("  Exception : " + e);
            logger.error("  Message   : " + e.getMessage());
            logger.error("  Stack     : " + Util.getStackTrace(e));
        }

        logger.info("  - MetadataNotifier ...");
        try {
            metadataNotifierControl.shutDown();
        } catch (Exception e) {
            logger.error("Raised exception while stopping metadatanotifier");
            logger.error("  Exception : " + e);
            logger.error("  Message   : " + e.getMessage());
            logger.error("  Stack     : " + Util.getStackTrace(e));
        }

        logger.info("  - Harvest Manager...");
        _applicationContext.getBean(HarvestManager.class).shutdown();

        logger.info("  - Z39.50...");
        Server.end();
    }

    //---------------------------------------------------------------------------

    private DataStore createShapefileDatastore(String indexDir) throws Exception {

        File file = new File(indexDir + "/" + SpatialIndexWriter._SPATIAL_INDEX_TYPENAME + ".shp");
        if (!file.getParentFile().mkdirs() && !file.getParentFile().exists()) {
            throw new RuntimeException(
                    "Unable to create the spatial index (shapefile) directory: " + file.getParentFile());
        }
        if (!file.exists()) {
            logger.info("Creating shapefile " + file.getAbsolutePath());
        } else {
            logger.info("Using shapefile " + file.getAbsolutePath());
        }
        IndexedShapefileDataStore ids = new IndexedShapefileDataStore(file.toURI().toURL(),
                new URI("http://geonetwork.org"), false, false, IndexType.QIX, Charset.forName(Constants.ENCODING));
        CoordinateReferenceSystem crs = CRS.decode("EPSG:4326");

        if (crs != null) {
            ids.forceSchemaCRS(crs);
        }

        if (!file.exists()) {
            SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
            AttributeDescriptor geomDescriptor = new AttributeTypeBuilder().crs(DefaultGeographicCRS.WGS84)
                    .binding(MultiPolygon.class).buildDescriptor("the_geom");
            builder.setName(SpatialIndexWriter._SPATIAL_INDEX_TYPENAME);
            builder.add(geomDescriptor);
            builder.add(SpatialIndexWriter._IDS_ATTRIBUTE_NAME, String.class);
            ids.createSchema(builder.buildFeatureType());
        }

        logger.info("NOTE: Using shapefile for spatial index, this can be slow for larger catalogs");
        return ids;
    }
}