Java tutorial
//============================================================================= //=== 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 java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.sql.SQLException; import java.util.List; import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.servlet.ServletContext; import jeeves.JeevesJCS; import jeeves.JeevesProxyInfo; import jeeves.config.springutil.ServerBeanPropertyUpdater; import jeeves.constants.Jeeves; import jeeves.interfaces.ApplicationHandler; import jeeves.interfaces.Logger; import jeeves.resources.dbms.Dbms; import jeeves.server.ServiceConfig; import jeeves.server.context.ServiceContext; import jeeves.server.overrides.ConfigurationOverrides; import jeeves.server.resources.ResourceManager; import jeeves.utils.ProxyInfo; import jeeves.utils.Util; import jeeves.utils.Xml; import jeeves.utils.XmlResolver; import jeeves.xlink.Processor; import org.fao.geonet.constants.Geonet; import org.fao.geonet.csw.common.Csw; import org.fao.geonet.kernel.AccessManager; import org.fao.geonet.kernel.DataManager; import org.fao.geonet.kernel.DataManagerParameter; 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.XmlSerializerDb; import org.fao.geonet.kernel.XmlSerializerSvn; import org.fao.geonet.kernel.csw.CatalogConfiguration; import org.fao.geonet.kernel.csw.CatalogDispatcher; import org.fao.geonet.kernel.csw.CswHarvesterResponseExecutionService; import org.fao.geonet.kernel.harvest.HarvestManager; 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.Pair; 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.languages.IsoLanguagesMapper; import org.fao.geonet.languages.LanguageDetector; import org.fao.geonet.lib.DatabaseType; import org.fao.geonet.lib.Lib; import org.fao.geonet.lib.ServerLib; import org.fao.geonet.notifier.MetadataNotifierControl; import org.fao.geonet.notifier.MetadataNotifierManager; import org.fao.geonet.resources.Resources; import org.fao.geonet.services.util.z3950.Repositories; import org.fao.geonet.services.util.z3950.Server; import org.fao.geonet.util.ThreadPool; import org.fao.geonet.util.ThreadUtils; 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.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.vividsolutions.jts.geom.MultiPolygon; import java.lang.reflect.InvocationTargetException; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.BooleanUtils; import org.fao.geonet.services.metadata.StatusActions; /** * This is the main class, it handles http connections and inits the system. */ public class Geonetwork implements ApplicationHandler { private Logger logger; private String path; private SearchManager searchMan; private HarvestManager harvestMan; private ThesaurusManager thesaurusMan; private MetadataNotifierControl metadataNotifierControl; private ThreadPool threadPool; private String FS = File.separator; private Element dbConfiguration; private static final String SPATIAL_INDEX_FILENAME = "spatialindex"; private static final String IDS_ATTRIBUTE_NAME = "id"; //--------------------------------------------------------------------------- //--- //--- GetContextName //--- //--------------------------------------------------------------------------- public String getContextName() { return Geonet.CONTEXT_NAME; } //--------------------------------------------------------------------------- //--- //--- Start //--- //--------------------------------------------------------------------------- /** * Inits the engine, loading all needed data. */ @SuppressWarnings(value = "unchecked") public Object start(Element config, ServiceContext context) throws Exception { logger = context.getLogger(); path = context.getAppPath(); String baseURL = context.getBaseUrl(); String webappName = baseURL.substring(1); // TODO : if webappName is "". ie no context ServletContext servletContext = null; if (context.getServlet() != null) { servletContext = context.getServlet().getServletContext(); } ServerLib sl = new ServerLib(servletContext, path); String version = sl.getVersion(); String subVersion = sl.getSubVersion(); logger.info("Initializing GeoNetwork " + version + "." + subVersion + " ..."); // Get main service config handler ServiceConfig handlerConfig = new ServiceConfig(config.getChildren()); // Init configuration directory new GeonetworkDataDirectory(webappName, path, 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 dataDir = handlerConfig.getMandatoryValue(Geonet.Config.DATA_DIR); String luceneConfigXmlFile = handlerConfig.getMandatoryValue(Geonet.Config.LUCENE_CONFIG); String summaryConfigXmlFile = handlerConfig.getMandatoryValue(Geonet.Config.SUMMARY_CONFIG); logger.info("Data directory: " + systemDataDir); setProps(path, handlerConfig); Class statusActionsClass = setupStatusActions(handlerConfig); String languageProfilesDir = handlerConfig.getMandatoryValue(Geonet.Config.LANGUAGE_PROFILES_DIR); JeevesJCS.setConfigFilename(path + "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); // --- Check current database and create database if an emty one is found String dbConfigurationFilePath = path + "/WEB-INF/config-db.xml"; dbConfiguration = Xml.loadFile(dbConfigurationFilePath); ConfigurationOverrides.DEFAULT.updateWithOverrides(dbConfigurationFilePath, servletContext, path, dbConfiguration); Pair<Dbms, Boolean> pair = initDatabase(context); Dbms dbms = pair.one(); Boolean created = pair.two(); //------------------------------------------------------------------------ //--- initialize thread pool logger.info(" - Thread Pool..."); threadPool = new ThreadPool(); //------------------------------------------------------------------------ //--- initialize settings subsystem logger.info(" - Setting manager..."); SettingManager settingMan = new SettingManager(dbms, context.getProviderManager()); // --- Migrate database if an old one is found migrateDatabase(servletContext, dbms, settingMan, version, subVersion, context.getAppPath()); //--- initialize ThreadUtils with setting manager and rm props ThreadUtils.init(context.getResourceManager().getProps(Geonet.Res.MAIN_DB), settingMan); //------------------------------------------------------------------------ //--- initialize Z39.50 logger.info(" - Z39.50..."); boolean z3950Enable = settingMan.getValueAsBool("system/z3950/enable", false); String z3950port = settingMan.getValue("system/z3950/port"); // null means not initialized ApplicationContext app_context = null; // 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 { app_context = new ClassPathXmlApplicationContext(Geonet.File.JZKITAPPLICATIONCONTEXT); // to have access to the GN context in spring-managed objects ContextContainer cc = (ContextContainer) app_context.getBean("ContextGateway"); cc.setSrvctx(context); if (!z3950Enable) logger.info(" Server is Disabled."); else { logger.info(" Server is Enabled."); Server.init(z3950port, app_context); } } 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..."); String schemaPluginsDir = handlerConfig.getMandatoryValue(Geonet.Config.SCHEMAPLUGINS_DIR); String schemaCatalogueFile = systemDataDir + "config" + File.separator + 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 = SchemaManager.getInstance(path, Resources.locateResourcesDir(context), 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 = new LuceneConfig(path, servletContext, luceneConfigXmlFile); logger.info(" - Lucene configuration is:"); logger.info(lc.toString()); DataStore dataStore = context.getResourceManager().getDataStore(Geonet.Res.MAIN_DB); if (dataStore == null) { dataStore = createShapefileDatastore(luceneDir); } //--- 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(); } String htmlCacheDir = handlerConfig.getMandatoryValue(Geonet.Config.HTMLCACHE_DIR); SettingInfo settingInfo = new SettingInfo(settingMan); searchMan = new SearchManager(path, luceneDir, htmlCacheDir, thesauriDir, summaryConfigXmlFile, lc, logAsynch, logSpatialObject, luceneTermsToExclude, dataStore, maxWritesInTransaction, settingInfo, schemaMan, servletContext); // 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, servletContext); //------------------------------------------------------------------------ //--- extract intranet ip/mask and initialize AccessManager logger.info(" - Access manager..."); AccessManager accessMan = new AccessManager(dbms, settingMan); //------------------------------------------------------------------------ //--- get edit params and initialize DataManager logger.info(" - Xml serializer and Data manager..."); String useSubversion = handlerConfig.getMandatoryValue(Geonet.Config.USE_SUBVERSION); SvnManager svnManager = null; XmlSerializer xmlSerializer = null; if (useSubversion.equals("true")) { String subversionPath = handlerConfig.getValue(Geonet.Config.SUBVERSION_PATH); svnManager = new SvnManager(context, settingMan, subversionPath, dbms, created); xmlSerializer = new XmlSerializerSvn(settingMan, svnManager); } else { xmlSerializer = new XmlSerializerDb(settingMan); } DataManagerParameter dataManagerParameter = new DataManagerParameter(); dataManagerParameter.context = context; dataManagerParameter.svnManager = svnManager; dataManagerParameter.searchManager = searchMan; dataManagerParameter.xmlSerializer = xmlSerializer; dataManagerParameter.schemaManager = schemaMan; dataManagerParameter.accessManager = accessMan; dataManagerParameter.dbms = dbms; dataManagerParameter.settingsManager = settingMan; dataManagerParameter.baseURL = baseURL; dataManagerParameter.dataDir = dataDir; dataManagerParameter.thesaurusDir = thesauriDir; dataManagerParameter.appPath = path; DataManager dataMan = new DataManager(dataManagerParameter); /** * Initialize iso languages mapper */ IsoLanguagesMapper.getInstance().init(dbms); /** * Initialize language detector */ LanguageDetector.init(path + languageProfilesDir, context, dataMan); //------------------------------------------------------------------------ //--- Initialize thesaurus logger.info(" - Thesaurus..."); thesaurusMan = ThesaurusManager.getInstance(context, path, dataMan, context.getResourceManager(), thesauriDir); //------------------------------------------------------------------------ //--- initialize catalogue services for the web logger.info(" - Catalogue services for the web..."); CatalogConfiguration.loadCatalogConfig(path, Csw.CONFIG_FILE); CatalogDispatcher catalogDis = new CatalogDispatcher(new File(path, summaryConfigXmlFile), lc); //------------------------------------------------------------------------ //--- initialize catalogue services for the web logger.info(" - Open Archive Initiative (OAI-PMH) server..."); OaiPmhDispatcher oaipmhDis = new OaiPmhDispatcher(settingMan, schemaMan); //------------------------------------------------------------------------ //--- initialize metadata notifier subsystem MetadataNotifierManager metadataNotifierMan = new MetadataNotifierManager(dataMan); logger.info(" - Metadata notifier ..."); //------------------------------------------------------------------------ //--- initialize metadata notifier subsystem logger.info(" - Metadata notifier ..."); //------------------------------------------------------------------------ //--- return application context GeonetContext gnContext = new GeonetContext(); gnContext.accessMan = accessMan; gnContext.dataMan = dataMan; gnContext.searchMan = searchMan; gnContext.schemaMan = schemaMan; gnContext.config = handlerConfig; gnContext.catalogDis = catalogDis; gnContext.settingMan = settingMan; gnContext.thesaurusMan = thesaurusMan; gnContext.oaipmhDis = oaipmhDis; gnContext.app_context = app_context; gnContext.metadataNotifierMan = metadataNotifierMan; gnContext.threadPool = threadPool; gnContext.xmlSerializer = xmlSerializer; gnContext.svnManager = svnManager; gnContext.statusActionsClass = statusActionsClass; logger.info("Site ID is : " + gnContext.getSiteId()); //------------------------------------------------------------------------ //--- initialize harvesting subsystem logger.info(" - Harvest manager..."); harvestMan = new HarvestManager(context, gnContext, settingMan, dataMan); dataMan.setHarvestManager(harvestMan); gnContext.harvestMan = harvestMan; // 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(gnContext.getSiteId(), servletContext, 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, gnContext); metadataNotifierControl.runOnce(); //--- 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); } // // 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(context.getResourceManager(), gnContext, dbHeartBeatInitialDelay, dbHeartBeatFixedDelay); } return gnContext; } /** * Init StatusActions. * */ private Class setupStatusActions(ServiceConfig handlerConfig) throws ClassNotFoundException { // Status actions class - load it String statusActionsClassName = handlerConfig.getMandatoryValue(Geonet.Config.STATUS_ACTIONS_CLASS); Class statusActionsClass = Class.forName(statusActionsClassName); String useHtml5uiParam = handlerConfig.getValue(Geonet.Config.USE_HTML5_GUI); boolean useHtml5ui; if (useHtml5uiParam != null) { useHtml5ui = BooleanUtils.isTrue(BooleanUtils.toBooleanObject(useHtml5uiParam)); } else { logger.info("Parameter " + Geonet.Config.USE_HTML5_GUI + " not found. Default to true."); useHtml5ui = true; } // init html5 value if possible try { final String HTML5_PROP_NAME = "useHtml5ui"; StatusActions statusActionsImpl = (StatusActions) statusActionsClass.newInstance(); if (PropertyUtils.isWriteable(statusActionsImpl, HTML5_PROP_NAME)) { BeanUtils.setProperty(statusActionsImpl, HTML5_PROP_NAME, useHtml5ui); logger.info(Geonet.Config.USE_HTML5_GUI + " set to " + useHtml5ui); } else { logger.warning("Could not find setter for " + Geonet.Config.USE_HTML5_GUI); } } catch (Exception ex) { logger.warning("Could not inject " + Geonet.Config.USE_HTML5_GUI + " value: " + ex.getMessage()); } return statusActionsClass; } /** * 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 ResourceManager rm, final GeonetContext gc, Integer initialDelay, Integer fixedDelay) { 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() { private static final String INSERT = "INSERT INTO Settings(id, parentId, name, value) VALUES(?, ?, ?, ?)"; private static final String REMOVE = "DELETE FROM Settings WHERE id=?"; /** * */ @Override public void run() { try { boolean readOnly = gc.isReadOnly(); logger.debug("DBHeartBeat: GN is read-only ? " + readOnly); boolean canWrite = checkDBWrite(); HarvestManager hm = gc.getHarvestManager(); 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"); } } } // any uncaught exception would cause the scheduled execution to silently stop catch (Throwable x) { logger.error("DBHeartBeat error: " + x.getMessage() + " This error is ignored."); x.printStackTrace(); } } /** * * @return */ private boolean checkDBWrite() { Dbms dbms = null; try { Integer testId = Integer.valueOf("100000"); dbms = (Dbms) rm.openDirect(Geonet.Res.MAIN_DB); dbms.execute(INSERT, testId, Integer.valueOf("1"), "DBHeartBeat", "Yeah !"); dbms.execute(REMOVE, testId); return true; } catch (Exception x) { logger.info("DBHeartBeat Exception: " + x.getMessage()); return false; } finally { try { if (dbms != null) rm.close(Geonet.Res.MAIN_DB, dbms); } catch (Exception x) { logger.error("DBHeartBeat failed to close DB connection. Your system is unstable! Error: " + x.getMessage()); x.printStackTrace(); } } } }; scheduledExecutorService.scheduleWithFixedDelay(DBHeartBeat, initialDelay, fixedDelay, TimeUnit.SECONDS); } /** * * @param webappName * @param handlerConfig * @param dataDir * @return */ private String locateThesaurusDir(String webappName, ServiceConfig handlerConfig, String dataDir) { String defaultThesaurusDir = handlerConfig.getValue(Geonet.Config.CODELIST_DIR, null); String thesaurusSystemDir = System.getProperty(webappName + ".codeList.dir"); String thesauriDir = (thesaurusSystemDir != null ? thesaurusSystemDir : (defaultThesaurusDir != null ? defaultThesaurusDir : dataDir + "/codelist/")); thesauriDir = new File(thesauriDir).getAbsoluteFile().getPath(); handlerConfig.setValue(Geonet.Config.CODELIST_DIR, thesauriDir); System.setProperty(webappName + ".codeList.dir", thesauriDir); return thesauriDir; } /** * Parses a version number removing extra "-*" element and returning an integer. "2.7.0-SNAPSHOT" is returned as 270. * * @param number The version number to parse * @return The version number as an integer * @throws Exception */ private int parseVersionNumber(String number) throws Exception { // Remove extra "-SNAPSHOT" info which may be in version number int dashIdx = number.indexOf("-"); if (dashIdx != -1) { number = number.substring(0, number.indexOf("-")); } return Integer.valueOf(number.replaceAll("\\.", "")); } /** * Checks if current database is running same version as the web application. * If not, apply migration SQL script : * resources/sql/migration/{version}-to-{version}-{dbtype}.sql. * eg. 2.4.3-to-2.5.0-default.sql * * @param servletContext * @param dbms * @param settingMan * @param webappVersion * @param subVersion * @param appPath */ private void migrateDatabase(ServletContext servletContext, Dbms dbms, SettingManager settingMan, String webappVersion, String subVersion, String appPath) { logger.info(" - Migration ..."); // Get db version and subversion String dbVersion = settingMan.getValue("system/platform/version"); String dbSubVersion = settingMan.getValue("system/platform/subVersion"); // Migrate db if needed logger.info(" Webapp version:" + webappVersion + " subversion:" + subVersion); logger.info(" Database version:" + dbVersion + " subversion:" + dbSubVersion); if (dbVersion == null || webappVersion == null) { logger.warning( " Database does not contain any version information. Check that the database is a GeoNetwork database with data." + " Migration step aborted."); return; } int from = 0, to = 0; try { from = parseVersionNumber(dbVersion); to = parseVersionNumber(webappVersion); } catch (Exception e) { logger.warning(" Error parsing version numbers: " + e.getMessage()); e.printStackTrace(); } if (from == to //&& subVersion.equals(dbSubVersion) Check only on version number ) { logger.info(" Webapp version = Database version, no migration task to apply."); } else if (to > from) { boolean anyMigrationAction = false; boolean anyMigrationError = false; try { new UpdateHarvesterIdsTask().update(settingMan, dbms); } catch (Exception e) { logger.info(" Errors occurs during SQL migration file: " + e.getMessage()); e.printStackTrace(); anyMigrationError = true; } // Migrating from 2.0 to 2.5 could be done 2.0 -> 2.3 -> 2.4 -> 2.5 String dbType = DatabaseType.lookup(dbms).toString(); logger.debug(" Migrating from " + from + " to " + to + " (dbtype:" + dbType + ")..."); logger.info(" Loading SQL migration step configuration from config-db.xml ..."); @SuppressWarnings(value = "unchecked") List<Element> versions = dbConfiguration.getChild("migrate").getChildren(); for (Element version : versions) { int versionNumber = Integer.valueOf(version.getAttributeValue("id")); if (versionNumber > from && versionNumber <= to) { logger.info(" - running tasks for " + versionNumber + "..."); @SuppressWarnings(value = "unchecked") List<Element> versionConfiguration = version.getChildren(); for (Element file : versionConfiguration) { if (file.getName().equals("java")) { try { String className = file.getAttributeValue("class"); logger.info(" - Java migration class:" + className); settingMan.refresh(dbms); DatabaseMigrationTask task = (DatabaseMigrationTask) Class.forName(className) .newInstance(); task.update(settingMan, dbms); } catch (Exception e) { logger.info( " Errors occurs during Java migration file: " + e.getMessage()); e.printStackTrace(); anyMigrationError = true; } } else { String filePath = path + file.getAttributeValue("path"); String filePrefix = file.getAttributeValue("filePrefix"); anyMigrationAction = true; logger.info( " - SQL migration file:" + filePath + " prefix:" + filePrefix + " ..."); try { Lib.db.insertData(servletContext, dbms, path, filePath, filePrefix); } catch (Exception e) { logger.info(" Errors occurs during SQL migration file: " + e.getMessage()); e.printStackTrace(); anyMigrationError = true; } } } } } // Refresh setting manager in case the migration task added some new settings. try { settingMan.refresh(dbms); } catch (Exception e) { logger.info(" Errors occurs during settings manager refresh during migration. Error is: " + e.getMessage()); e.printStackTrace(); anyMigrationError = true; } // Update the logo String siteId = settingMan.getValue("system/site/siteId"); initLogo(servletContext, dbms, siteId, appPath); // TODO : Maybe a force rebuild index is required in such situation. if (anyMigrationAction && !anyMigrationError) { logger.info(" Successfull migration.\n" + " Catalogue administrator still need to update the catalogue\n" + " logo and data directory in order to complete the migration process.\n" + " Lucene index rebuild is also recommended after migration."); } if (!anyMigrationAction) { logger.warning(" No migration task found between webapp and database version.\n" + " The system may be unstable or may failed to start if you try to run \n" + " the current GeoNetwork " + webappVersion + " with an older database (ie. " + dbVersion + "\n" + " ). Try to run the migration task manually on the current database\n" + " before starting the application or start with a new empty database.\n" + " Sample SQL scripts for migration could be found in WEB-INF/sql/migrate folder.\n"); } if (anyMigrationError) { logger.warning(" Error occurs during migration. Check the log file for more details."); } // TODO : Maybe some migration stuff has to be done in Java ? } else { logger.info(" Running on a newer database version."); } } /** * Database initialization. If no table in current database * create the GeoNetwork database. If an existing GeoNetwork database * exists, try to migrate the content. * * @param context * @return Pair with Dbms channel and Boolean set to true if db created * @throws Exception */ private Pair<Dbms, Boolean> initDatabase(ServiceContext context) throws Exception { Dbms dbms = null; try { dbms = (Dbms) context.getResourceManager().open(Geonet.Res.MAIN_DB); } catch (Exception e) { logger.error(" Failed to open database connection, Check config.xml db file configuration."); logger.error(Util.getStackTrace(e)); throw new IllegalArgumentException("No database connection"); } String dbURL = dbms.getURL(); logger.info(" - Database connection on " + dbURL + " ..."); ServletContext servletContext = null; if (context.getServlet() != null) { servletContext = context.getServlet().getServletContext(); } Boolean created = false; // Create db if empty if (!Lib.db.touch(dbms)) { logger.info(" " + dbURL + " is an empty database (Metadata table not found)."); @SuppressWarnings(value = "unchecked") List<Element> createConfiguration = dbConfiguration.getChild("create").getChildren(); for (Element file : createConfiguration) { String filePath = path + file.getAttributeValue("path"); String filePrefix = file.getAttributeValue("filePrefix"); logger.info(" - SQL create file:" + filePath + " prefix:" + filePrefix + " ..."); // Do we need to remove object before creating the database ? Lib.db.removeObjects(servletContext, dbms, path, filePath, filePrefix); Lib.db.createSchema(servletContext, dbms, path, filePath, filePrefix); } @SuppressWarnings(value = "unchecked") List<Element> dataConfiguration = dbConfiguration.getChild("data").getChildren(); for (Element file : dataConfiguration) { String filePath = path + file.getAttributeValue("path"); String filePrefix = file.getAttributeValue("filePrefix"); logger.info(" - SQL data file:" + filePath + " prefix:" + filePrefix + " ..."); Lib.db.insertData(servletContext, dbms, path, filePath, filePrefix); } dbms.commit(); // Copy logo String uuid = UUID.randomUUID().toString(); initLogo(servletContext, dbms, uuid, context.getAppPath()); created = true; } else { logger.info(" Found an existing GeoNetwork database."); } return Pair.read(dbms, created); } /** * Copy the default dummy logo to the logo folder based on uuid * * @param servletContext * @param dbms * @param nodeUuid * @param appPath * @throws FileNotFoundException * @throws IOException * @throws SQLException */ private void initLogo(ServletContext servletContext, Dbms dbms, String nodeUuid, String appPath) { createSiteLogo(nodeUuid, servletContext, appPath); try { dbms.execute("UPDATE Settings SET value=? WHERE name='siteId'", nodeUuid); } catch (SQLException e) { logger.error(" Error when setting siteId values: " + e.getMessage()); } } /** * Creates a default site logo, only if the logo image doesn't exists * * @param nodeUuid * @param servletContext * @param appPath */ private void createSiteLogo(String nodeUuid, ServletContext servletContext, String appPath) { try { String logosDir = Resources.locateLogosDir(servletContext, appPath); File logo = new File(logosDir, nodeUuid + ".gif"); if (!logo.exists()) { FileOutputStream os = new FileOutputStream(logo); try { os.write(Resources.loadImage(servletContext, appPath, "images/logos/dummy.gif", new byte[0]) .one()); logger.info(" Setting catalogue logo for current node identified by: " + nodeUuid); } finally { os.close(); } } } catch (Exception e) { logger.error(" Error when setting the logo: " + e.getMessage()); } } /** * Set system properties to those required * @param path webapp path */ private void setProps(String path, ServiceConfig handlerConfig) { String webapp = path + "WEB-INF" + FS; //--- Set jeeves.xml.catalog.files property //--- this is critical to schema support so must be set correctly String catalogProp = System.getProperty(Jeeves.XML_CATALOG_FILES); if (catalogProp == null) catalogProp = ""; if (!catalogProp.equals("")) { logger.info("Overriding " + Jeeves.XML_CATALOG_FILES + " property (was set to " + catalogProp + ")"); } catalogProp = webapp + "oasis-catalog.xml;" + handlerConfig.getValue(Geonet.Config.CONFIG_DIR) + File.separator + "schemaplugin-uri-catalog.xml"; System.setProperty(Jeeves.XML_CATALOG_FILES, catalogProp); logger.info(Jeeves.XML_CATALOG_FILES + " property set to " + catalogProp); String blankXSLFile = path + "xsl" + FS + "blanks.xsl"; System.setProperty(Jeeves.XML_CATALOG_BLANKXSLFILE, blankXSLFile); logger.info(Jeeves.XML_CATALOG_BLANKXSLFILE + " property set to " + blankXSLFile); //--- 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 = webapp + "mime-types.properties"; System.setProperty("mime-mappings", mimeProp); logger.info("mime-mappings property set to " + mimeProp); } //--------------------------------------------------------------------------- //--- //--- Stop //--- //--------------------------------------------------------------------------- public void stop() { logger.info("Stopping geonetwork..."); 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(" - ThreadPool ..."); threadPool.shutDown(); 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..."); harvestMan.shutdown(); logger.info(" - Z39.50..."); Server.end(); } //--------------------------------------------------------------------------- private DataStore createShapefileDatastore(String indexDir) throws Exception { File file = new File(indexDir + "/" + SPATIAL_INDEX_FILENAME + ".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("UTF-8")); 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(SPATIAL_INDEX_FILENAME); builder.add(geomDescriptor); builder.add(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; } }