/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE file at the root of the source * tree and available online at * * https://github.com/keeps/roda */ package org.roda.core; import java.io.BufferedReader; import java.io.Console; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.xml.validation.Schema; import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer; import org.apache.solr.client.solrj.impl.CloudSolrClient; import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.reflections.Reflections; import org.reflections.scanners.ResourcesScanner; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; import org.reflections.util.FilterBuilder; import org.roda.core.common.LdapUtility; import org.roda.core.common.Messages; import org.roda.core.common.RodaUtils; import org.roda.core.common.UserUtility; import org.roda.core.common.iterables.CloseableIterable; import org.roda.core.common.monitor.TransferUpdateStatus; import org.roda.core.common.monitor.TransferredResourcesScanner; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.common.RodaConstants.NodeType; import org.roda.core.data.common.RodaConstants.PreservationAgentType; import org.roda.core.data.common.RodaConstants.PreservationEventType; import org.roda.core.data.common.RodaConstants.SolrType; import org.roda.core.data.common.RodaConstants.StorageType; import org.roda.core.data.exceptions.AlreadyExistsException; import org.roda.core.data.exceptions.AuthorizationDeniedException; import org.roda.core.data.exceptions.GenericException; import org.roda.core.data.exceptions.IllegalOperationException; import org.roda.core.data.exceptions.NotFoundException; import org.roda.core.data.exceptions.RequestNotValidException; import org.roda.core.data.exceptions.RoleAlreadyExistsException; import org.roda.core.data.v2.common.Pair; import org.roda.core.data.v2.index.IndexResult; import org.roda.core.data.v2.index.facet.Facets; import org.roda.core.data.v2.index.filter.BasicSearchFilterParameter; import org.roda.core.data.v2.index.filter.EmptyKeyFilterParameter; import org.roda.core.data.v2.index.filter.Filter; import org.roda.core.data.v2.index.filter.FilterParameter; import org.roda.core.data.v2.index.filter.SimpleFilterParameter; import org.roda.core.data.v2.index.sort.Sorter; import org.roda.core.data.v2.index.sublist.Sublist; import org.roda.core.data.v2.ip.IndexedFile; import org.roda.core.data.v2.ip.TransferredResource; import org.roda.core.data.v2.ip.metadata.IndexedPreservationAgent; import org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent; import org.roda.core.data.v2.user.Group; import org.roda.core.data.v2.user.RODAMember; import org.roda.core.data.v2.user.User; import org.roda.core.index.IndexService; import org.roda.core.index.utils.SolrUtils; import org.roda.core.migration.MigrationManager; import org.roda.core.model.ModelService; import org.roda.core.plugins.PluginManager; import org.roda.core.plugins.PluginManagerException; import org.roda.core.plugins.PluginOrchestrator; import org.roda.core.plugins.orchestrate.AkkaDistributedPluginOrchestrator; import org.roda.core.plugins.orchestrate.AkkaEmbeddedPluginOrchestrator; import org.roda.core.plugins.orchestrate.akka.distributed.AkkaDistributedPluginWorker; import org.roda.core.storage.DefaultStoragePath; import org.roda.core.storage.Resource; import org.roda.core.storage.StorageService; import org.roda.core.storage.fedora.FedoraStorageService; import org.roda.core.storage.fs.FSUtils; import org.roda.core.storage.fs.FileStorageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.codahale.metrics.JmxReporter; import com.codahale.metrics.MetricRegistry; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.core.joran.spi.JoranException; /** * @author Hélder Silva <hsilva@keep.pt> */ public class RodaCoreFactory { private static final Logger LOGGER = LoggerFactory.getLogger(RodaCoreFactory.class); private static boolean instantiated = false; private static boolean instantiatedWithoutErrors = true; private static NodeType nodeType; private static boolean migrationMode = false; private static List<Path> toDeleteDuringShutdown = new ArrayList<>(); // Core related objects private static Path rodaHomePath; private static Path storagePath; private static Path indexDataPath; private static Optional<Path> tempIndexConfigsPath; private static Path dataPath; private static Path logPath; private static Path configPath; private static Path workingDirectoryPath; private static Path reportDirectoryPath; private static Path exampleConfigPath; private static Path defaultPath; private static StorageService storage; private static ModelService model; private static IndexService index; private static SolrClient solr; private static boolean FEATURE_OVERRIDE_INDEX_CONFIGS = true; private static boolean TEST_DEPLOY_SOLR = true; private static boolean TEST_DEPLOY_LDAP = true; private static boolean TEST_DEPLOY_SCANNER = true; private static boolean TEST_DEPLOY_ORCHESTRATOR = true; private static boolean TEST_DEPLOY_PLUGIN_MANAGER = true; private static boolean TEST_DEPLOY_DEFAULT_RESOURCES = true; private static SolrType TEST_SOLR_TYPE = null; // Metrics related objects private static MetricRegistry metricsRegistry; private static JmxReporter jmxMetricsReporter; // Orchestrator related objects private static PluginManager pluginManager; // FIXME we should have only "one" orchestrator private static PluginOrchestrator pluginOrchestrator; private static AkkaDistributedPluginOrchestrator akkaDistributedPluginOrchestrator; private static AkkaDistributedPluginWorker akkaDistributedPluginWorker; private static boolean FEATURE_DISTRIBUTED_AKKA = false; private static LdapUtility ldapUtility; private static Path rodaApacheDSDataDirectory = null; // TransferredResources related objects private static TransferredResourcesScanner transferredResourcesScanner; // Configuration related objects private static CompositeConfiguration rodaConfiguration = null; private static List<String> configurationFiles = null; // Caches private static CacheLoader<Pair<String, String>, Optional<Schema>> RODA_SCHEMAS_LOADER = new SchemasCacheLoader(); private static LoadingCache<Pair<String, String>, Optional<Schema>> RODA_SCHEMAS_CACHE = CacheBuilder.newBuilder() .build(RODA_SCHEMAS_LOADER); private static LoadingCache<Locale, Messages> I18N_CACHE = CacheBuilder.newBuilder() .build(new CacheLoader<Locale, Messages>() { @Override public Messages load(Locale locale) throws Exception { return new Messages(locale, getConfigPath().resolve(RodaConstants.CORE_I18N_FOLDER)); } }); private static Map<String, Map<String, String>> rodaPropertiesCache = null; /** Private empty constructor */ private RodaCoreFactory() { } public static boolean instantiatedWithoutErrors() { return instantiatedWithoutErrors; } public static void instantiate() { NodeType nodeType = NodeType .valueOf(getSystemProperty(RodaConstants.CORE_NODE_TYPE, RodaConstants.DEFAULT_NODE_TYPE.name())); if (nodeType == RodaConstants.NodeType.MASTER) { instantiateMaster(); } else if (nodeType == RodaConstants.NodeType.TEST) { instantiateTest(); } else if (nodeType == RodaConstants.NodeType.WORKER) { instantiateWorker(); } else { LOGGER.error("Unknown node type '{}'", nodeType); } } public static void instantiateMaster() { instantiate(NodeType.MASTER); } public static void instantiateWorker() { instantiate(NodeType.WORKER); } public static void instantiateTest(boolean deploySolr, boolean deployLdap, boolean deployTransferredResourcesScanner, boolean deployOrchestrator, boolean deployPluginManager, boolean deployDefaultResources) { TEST_DEPLOY_SOLR = deploySolr; TEST_DEPLOY_LDAP = deployLdap; TEST_DEPLOY_SCANNER = deployTransferredResourcesScanner; TEST_DEPLOY_ORCHESTRATOR = deployOrchestrator; TEST_DEPLOY_PLUGIN_MANAGER = deployPluginManager; TEST_DEPLOY_DEFAULT_RESOURCES = deployDefaultResources; instantiated = false; instantiate(NodeType.TEST); } public static void instantiateTest(boolean deploySolr, boolean deployLdap, boolean deployTransferredResourcesScanner, boolean deployOrchestrator, boolean deployPluginManager, boolean deployDefaultResources, SolrType solrType) { TEST_SOLR_TYPE = solrType; instantiateTest(deploySolr, deployLdap, deployTransferredResourcesScanner, deployOrchestrator, deployPluginManager, deployDefaultResources); } public static void instantiateTest() { instantiated = false; instantiate(NodeType.TEST); } private static void instantiate(NodeType nodeType) { RodaCoreFactory.nodeType = nodeType; if (!instantiated) { try { // determine RODA HOME rodaHomePath = determineRodaHomePath(); LOGGER.debug("RODA HOME is {}", rodaHomePath); // instantiate essential directories instantiateEssentialDirectories(nodeType); LOGGER.debug("Finished instantiating essential directories"); // load core configurations rodaConfiguration = new CompositeConfiguration(); configurationFiles = new ArrayList<>(); rodaPropertiesCache = new HashMap<>(); addConfiguration("roda-core.properties"); addConfiguration("roda-core-formats.properties"); LOGGER.debug("Finished loading roda-core.properties & roda-core-formats.properties"); addConfiguration("roda-roles.properties"); LOGGER.debug("Finished loading roda-roles.properties"); // initialize working directory initializeWorkingDirectory(); // initialize reports directory initializeReportsDirectory(); // initialize metrics stuff initializeMetrics(); // instantiate storage and model service instantiateStorageAndModel(); LOGGER.debug("Finished instantiating storage & model"); // instantiate solr and index service instantiateSolrAndIndexService(); LOGGER.debug("Finished instantiating solr & index"); instantiateNodeSpecificObjects(nodeType); LOGGER.debug("Finished instantiating node specific objects"); // verify if is necessary to perform a model/index migration MigrationManager migrationManager = new MigrationManager(dataPath); if (NodeType.MASTER == nodeType && migrationManager.isNecessaryToPerformMigration(getSolr(), tempIndexConfigsPath)) { // migrationManager.setupModelMigrations(); // migrationManager.performModelMigrations(); throw new GenericException("It's necessary to do a model/index migration"); } instantiateDefaultObjects(); LOGGER.debug("Finished instantiating default objects"); // instantiate plugin manager // 20160920 hsilva: this must be the last thing to be instantiated as // problems may araise when instantiating objects at the same time the // plugin manager is loading both internal & external plugins (it looks // like Reflections is the blame) instantiatePluginManager(); LOGGER.debug("Finished instantiating plugin manager"); instantiated = true; } catch (ConfigurationException e) { LOGGER.error("Error loading roda properties", e); instantiatedWithoutErrors = false; } catch (URISyntaxException e) { LOGGER.error("Error instantiating solr/index model", e); instantiatedWithoutErrors = false; } catch (GenericException e) { if (!migrationMode) { LOGGER.error("Error instantiating storage model", e); } instantiatedWithoutErrors = false; } catch (Exception e) { LOGGER.error("Error instantiating " + RodaCoreFactory.class.getSimpleName(), e); instantiatedWithoutErrors = false; } // last log message that state if system was loaded without errors or not LOGGER.info("RODA Core loading completed {}", migrationMode ? "(migration mode)" : (instantiatedWithoutErrors ? "with success!" : "with some errors!!! See logs because these errors might cause instability in the system.")); } } private static void initializeWorkingDirectory() { try { workingDirectoryPath = Paths .get(getRodaConfiguration().getString("core.workingdirectory", getSystemProperty("java.io.tmpdir", "tmp"))); Files.createDirectories(workingDirectoryPath); } catch (IOException e) { throw new RuntimeException("Unable to create RODA WORKING DIRECTORY " + workingDirectoryPath + ". Aborting...", e); } } private static void initializeReportsDirectory() { try { reportDirectoryPath = getRodaHomePath().resolve(RodaConstants.CORE_REPORT_FOLDER); Files.createDirectories(reportDirectoryPath); } catch (IOException e) { throw new RuntimeException("Unable to create RODA Reports DIRECTORY " + reportDirectoryPath + ". Aborting...", e); } } private static void initializeMetrics() { metricsRegistry = new MetricRegistry(); if (getSystemProperty("com.sun.management.jmxremote", null) != null) { jmxMetricsReporter = JmxReporter.forRegistry(metricsRegistry).inDomain("RODA").build(); jmxMetricsReporter.start(); } } private static Path determineRodaHomePath() { Path rodaHomePath; if (System.getProperty(RodaConstants.INSTALL_FOLDER_SYSTEM_PROPERTY) != null) { rodaHomePath = Paths.get(System.getProperty(RodaConstants.INSTALL_FOLDER_SYSTEM_PROPERTY)); } else if (System.getenv(RodaConstants.INSTALL_FOLDER_ENVIRONEMNT_VARIABLE) != null) { rodaHomePath = Paths.get(System.getenv(RodaConstants.INSTALL_FOLDER_ENVIRONEMNT_VARIABLE)); } else { // last attempt (using user home and hidden directory called .roda) String userHome = System.getProperty("user.home"); rodaHomePath = Paths.get(userHome, ".roda"); if (!FSUtils.exists(rodaHomePath)) { try { Files.createDirectories(rodaHomePath); } catch (IOException e) { throw new RuntimeException("Unable to create RODA HOME " + rodaHomePath + ". Aborting...", e); } } // set roda.home in order to correctly configure logging even if no // property has been defined System.setProperty(RodaConstants.INSTALL_FOLDER_SYSTEM_PROPERTY, rodaHomePath.toString()); } // instantiate essential directories configPath = rodaHomePath.resolve(RodaConstants.CORE_CONFIG_FOLDER); exampleConfigPath = rodaHomePath.resolve(RodaConstants.CORE_EXAMPLE_CONFIG_FOLDER); defaultPath = rodaHomePath.resolve(RodaConstants.CORE_DEFAULT_FOLDER); dataPath = rodaHomePath.resolve(RodaConstants.CORE_DATA_FOLDER); logPath = dataPath.resolve(RodaConstants.CORE_LOG_FOLDER); storagePath = dataPath.resolve(RodaConstants.CORE_STORAGE_FOLDER); indexDataPath = dataPath.resolve(RodaConstants.CORE_INDEX_FOLDER); // configure logback if (nodeType != NodeType.TEST) { configureLogback(); } return rodaHomePath; } private static void configureLogback() { try { LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(context); context.reset(); configurator.doConfigure(getConfigurationFile("logback.xml")); } catch (JoranException e) { LOGGER.error("Error configuring logback", e); } } private static void instantiateEssentialDirectories(NodeType nodeType) { List<Path> essentialDirectories = new ArrayList<>(); essentialDirectories.add(configPath); essentialDirectories.add(rodaHomePath.resolve(RodaConstants.CORE_LOG_FOLDER)); essentialDirectories.add(dataPath); essentialDirectories.add(logPath); essentialDirectories.add(storagePath); essentialDirectories.add(indexDataPath); essentialDirectories.add(exampleConfigPath); for (Path path : essentialDirectories) { try { if (!FSUtils.exists(path)) { Files.createDirectories(path); } } catch (IOException e) { LOGGER.error("Unable to create " + path, e); instantiatedWithoutErrors = false; } } if (!nodeType.equals(NodeType.TEST)) { // copy configs folder from classpath to example folder try { FSUtils.deletePathQuietly(exampleConfigPath); Files.createDirectories(exampleConfigPath); copyFilesFromClasspath(RodaConstants.CORE_CONFIG_FOLDER + "/", exampleConfigPath, true, Arrays.asList(RodaConstants.CORE_CONFIG_FOLDER + "/" + RodaConstants.CORE_LDAP_FOLDER, RodaConstants.CORE_CONFIG_FOLDER + "/" + RodaConstants.CORE_INDEX_FOLDER, RodaConstants.CORE_CONFIG_FOLDER + "/" + RodaConstants.CORE_I18N_FOLDER + "/" + RodaConstants.CORE_I18N_CLIENT_FOLDER, RodaConstants.CORE_CONFIG_FOLDER + "/" + RodaConstants.CORE_I18N_FOLDER + "/" + RodaConstants.CORE_I18_GWT_XML_FILE)); } catch (IOException e) { LOGGER.error("Unable to create " + exampleConfigPath, e); instantiatedWithoutErrors = false; } } } private static void instantiateDefaultObjects() { if (TEST_DEPLOY_DEFAULT_RESOURCES) { try { CloseableIterable<Resource> resources = storage.listResourcesUnderContainer(DefaultStoragePath.parse(""), true); Iterator<Resource> resourceIterator = resources.iterator(); boolean hasFileResources = false; while (resourceIterator.hasNext() && !hasFileResources) { Resource resource = resourceIterator.next(); if (!resource.isDirectory()) { hasFileResources = true; } } IOUtils.closeQuietly(resources); if (!hasFileResources) { copyFilesFromClasspath(RodaConstants.CORE_DEFAULT_FOLDER + "/", rodaHomePath, true); // 20160712 hsilva: it needs to be this way as the resources are // copied to the file system and storage can be of a different type // (e.g. fedora) FileStorageService fileStorageService = new FileStorageService(storagePath); index.reindexRisks(fileStorageService); index.reindexFormats(fileStorageService); index.reindexAIPs(); // reindex other default objects HERE } } catch (AuthorizationDeniedException | RequestNotValidException | NotFoundException | GenericException e) { LOGGER.error("Cannot load default objects", e); } } } private static void copyFilesFromClasspath(String classpathPrefix, Path destinationDirectory, boolean removeClasspathPrefixFromFinalPath) { copyFilesFromClasspath(classpathPrefix, destinationDirectory, removeClasspathPrefixFromFinalPath, Collections.emptyList()); } private static void copyFilesFromClasspath(String classpathPrefix, Path destinationDirectory, boolean removeClasspathPrefixFromFinalPath, List<String> excludePaths) { List<ClassLoader> classLoadersList = new LinkedList<>(); classLoadersList.add(ClasspathHelper.contextClassLoader()); Reflections reflections = new Reflections( new ConfigurationBuilder().filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix(classpathPrefix))) .setScanners(new ResourcesScanner()) .setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[] {})))); Set<String> resources = reflections.getResources(Pattern.compile(".*")); LOGGER.info("Copy files from classpath prefix={}, destination={}, removePrefix={}, excludePaths={}, #resources={}", classpathPrefix, destinationDirectory, removeClasspathPrefixFromFinalPath, excludePaths, resources.size()); for (String resource : resources) { boolean exclude = false; for (String excludePath : excludePaths) { if (resource.startsWith(excludePath)) { exclude = true; break; } } if (exclude) { continue; } InputStream originStream = RodaCoreFactory.class.getClassLoader().getResourceAsStream(resource); Path destinyPath; String resourceFileName = resource; // Removing ":" escape resourceFileName = resourceFileName.replace("::", ":"); if (removeClasspathPrefixFromFinalPath) { destinyPath = destinationDirectory.resolve(resourceFileName.replaceFirst(classpathPrefix, "")); } else { destinyPath = destinationDirectory.resolve(resourceFileName); } try { // create all parent directories Files.createDirectories(destinyPath.getParent()); // copy file Files.copy(originStream, destinyPath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { LOGGER.error("Error copying file from classpath: {} to {} (reason: {})", originStream, destinyPath, e.getMessage()); instantiatedWithoutErrors = false; } finally { RodaUtils.closeQuietly(originStream); } } } private static void copyFilesFromClasspath(String classpathPrefix, Path destinationDirectory) { copyFilesFromClasspath(classpathPrefix, destinationDirectory, false); } private static void instantiatePluginManager() { if (nodeType == NodeType.MASTER || nodeType == NodeType.WORKER || TEST_DEPLOY_PLUGIN_MANAGER) { try { pluginManager = PluginManager.instantiatePluginManager(getConfigPath(), getPluginsPath()); } catch (PluginManagerException e) { LOGGER.error("Error instantiating PluginManager", e); instantiatedWithoutErrors = false; } } } private static void instantiateStorageAndModel() throws GenericException { storage = instantiateStorage(); LOGGER.debug("Finished instantiating storage..."); model = new ModelService(storage); LOGGER.debug("Finished instantiating model..."); } private static StorageService instantiateStorage() throws GenericException { StorageType storageType = StorageType.valueOf( getRodaConfiguration().getString(RodaConstants.CORE_STORAGE_TYPE, RodaConstants.DEFAULT_STORAGE_TYPE.toString())); if (storageType == RodaConstants.StorageType.FEDORA4) { String url = getRodaConfiguration().getString(RodaConstants.CORE_STORAGE_FEDORA4_URL, "http://localhost:8983/solr/"); String username = getRodaConfiguration().getString(RodaConstants.CORE_STORAGE_FEDORA4_USERNAME, ""); String password = getRodaConfiguration().getString(RodaConstants.CORE_STORAGE_FEDORA4_PASSWORD, ""); if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) { LOGGER.debug("Going to instantiate Fedora with url '{}' username '{}' & password '{}'", url, username, password); return new FedoraStorageService(url, username, password); } else { LOGGER.debug("Going to instantiate Fedora with url '{}'", url); return new FedoraStorageService(url); } } else if (storageType == RodaConstants.StorageType.FILESYSTEM) { LOGGER.debug("Going to instantiate Filesystem on '{}'", storagePath); String trashDirName = getRodaConfiguration().getString("core.storage.filesystem.trash", "trash"); return new FileStorageService(storagePath, trashDirName); } else { LOGGER.error("Unknown storage service '{}'", storageType.name()); throw new GenericException(); } } /** * <p> * Warnings like * </p> * <code>2016-03-21 11:21:34,319 WARN org.apache.solr.core.Config - Beginning with Solr 5.5, <maxMergeDocs> is deprecated, configure it on the relevant <mergePolicyFactory> instead.</code> * <br/> * <code>2016-03-21 11:21:34,327 WARN org.apache.solr.core.Config - Beginning with Solr 5.5, <mergeFactor> is deprecated, configure it on the relevant <mergePolicyFactory> instead.</code> * <p> * are due to a bug, explained in * https://issues.apache.org/jira/browse/SOLR-8734, as we don't declare those * parameters in RODA solr configurations. The warning will be removed and as * soon as that happens, this messages should be deleted as well. * </p> * */ private static void instantiateSolrAndIndexService() throws URISyntaxException { if (nodeType == NodeType.MASTER) { tempIndexConfigsPath = Optional.empty(); Path solrHome = configPath.resolve(RodaConstants.CORE_INDEX_FOLDER); if (!FSUtils.exists(solrHome) || FEATURE_OVERRIDE_INDEX_CONFIGS) { try { Path tempConfig = Files.createTempDirectory(getWorkingDirectory(), RodaConstants.CORE_INDEX_FOLDER); toDeleteDuringShutdown.add(tempConfig); tempIndexConfigsPath = Optional.of(tempConfig); copyFilesFromClasspath(RodaConstants.CORE_CONFIG_FOLDER + "/" + RodaConstants.CORE_INDEX_FOLDER + "/", tempConfig); solrHome = tempConfig.resolve(RodaConstants.CORE_CONFIG_FOLDER).resolve(RodaConstants.CORE_INDEX_FOLDER); LOGGER.info("Using SOLR home: {}", solrHome); } catch (IOException e) { LOGGER.error("Error creating temporary SOLR home", e); instantiatedWithoutErrors = false; } } // instantiate solr solr = instantiateSolr(solrHome); // instantiate index related object index = new IndexService(solr, model); } else if (nodeType == NodeType.TEST && TEST_DEPLOY_SOLR) { try { Path tempConfig = Files.createTempDirectory(getWorkingDirectory(), RodaConstants.CORE_INDEX_FOLDER); copyFilesFromClasspath(RodaConstants.CORE_CONFIG_FOLDER + "/" + RodaConstants.CORE_INDEX_FOLDER + "/", tempConfig, true); // instantiate solr solr = instantiateSolr(tempConfig); // instantiate index related object index = new IndexService(solr, model); } catch (IOException e) { LOGGER.error("Unable to instantiate Solr in TEST mode", e); instantiatedWithoutErrors = false; } } } private static SolrClient instantiateSolr(Path solrHome) { SolrType solrType = SolrType.valueOf( getRodaConfiguration().getString(RodaConstants.CORE_SOLR_TYPE, RodaConstants.DEFAULT_SOLR_TYPE.toString())); if (TEST_SOLR_TYPE != null) { solrType = TEST_SOLR_TYPE; } if (solrType == RodaConstants.SolrType.HTTP) { String solrBaseUrl = getRodaConfiguration().getString(RodaConstants.CORE_SOLR_HTTP_URL, "http://localhost:8983/solr/"); return new HttpSolrClient(solrBaseUrl); } else if (solrType == RodaConstants.SolrType.HTTP_CLOUD) { String solrCloudZooKeeperUrls = getRodaConfiguration().getString(RodaConstants.CORE_SOLR_HTTP_CLOUD_URLS, "localhost:2181,localhost:2182,localhost:2183"); return new CloudSolrClient(solrCloudZooKeeperUrls); } else { // default to Embedded setSolrSystemProperties(); return new EmbeddedSolrServer(solrHome, "test"); } } private static void setSolrSystemProperties() { System.setProperty("solr.data.dir", indexDataPath.toString()); System.setProperty("solr.data.dir.aip", indexDataPath.resolve(RodaConstants.CORE_AIP_FOLDER).toString()); System.setProperty("solr.data.dir.representations", indexDataPath.resolve(RodaConstants.CORE_REPRESENTATION_FOLDER).toString()); System.setProperty("solr.data.dir.file", indexDataPath.resolve(RodaConstants.CORE_FILE_FOLDER).toString()); System.setProperty("solr.data.dir.preservationevent", indexDataPath.resolve(RodaConstants.CORE_PRESERVATIONEVENT_FOLDER).toString()); System.setProperty("solr.data.dir.preservationagent", indexDataPath.resolve(RodaConstants.CORE_PRESERVATIONAGENT_FOLDER).toString()); System.setProperty("solr.data.dir.actionlog", indexDataPath.resolve(RodaConstants.CORE_ACTIONLOG_FOLDER).toString()); System.setProperty("solr.data.dir.members", indexDataPath.resolve(RodaConstants.CORE_MEMBERS_FOLDER).toString()); System.setProperty("solr.data.dir.transferredresource", indexDataPath.resolve(RodaConstants.CORE_TRANSFERREDRESOURCE_FOLDER).toString()); System.setProperty("solr.data.dir.job", indexDataPath.resolve(RodaConstants.CORE_JOB_FOLDER).toString()); System.setProperty("solr.data.dir.jobreport", indexDataPath.resolve(RodaConstants.CORE_JOBREPORT_FOLDER).toString()); System.setProperty("solr.data.dir.risk", indexDataPath.resolve(RodaConstants.CORE_RISK_FOLDER).toString()); System.setProperty("solr.data.dir.agent", indexDataPath.resolve(RodaConstants.CORE_AGENT_FOLDER).toString()); System.setProperty("solr.data.dir.format", indexDataPath.resolve(RodaConstants.CORE_FORMAT_FOLDER).toString()); System.setProperty("solr.data.dir.notification", indexDataPath.resolve(RodaConstants.CORE_NOTIFICATION_FOLDER).toString()); System.setProperty("solr.data.dir.riskincidence", indexDataPath.resolve(RodaConstants.CORE_RISKINCIDENCE_FOLDER).toString()); System.setProperty("solr.data.dir.dip", indexDataPath.resolve(RodaConstants.CORE_DIP_FOLDER).toString()); System.setProperty("solr.data.dir.dipfile", indexDataPath.resolve(RodaConstants.CORE_DIP_FILE_FOLDER).toString()); } private static void instantiateNodeSpecificObjects(NodeType nodeType) { if (nodeType == NodeType.MASTER) { instantiateMasterNodeSpecificObjects(); } else if (nodeType == NodeType.WORKER) { instantiateWorkerNodeSpecificObjects(); } else if (nodeType == NodeType.TEST) { instantiateTestNodeSpecificObjects(); } else { LOGGER.error("Unknown node type '{}'", nodeType); instantiatedWithoutErrors = false; } } private static void instantiateMasterNodeSpecificObjects() { if (FEATURE_DISTRIBUTED_AKKA) { akkaDistributedPluginOrchestrator = new AkkaDistributedPluginOrchestrator( getSystemProperty(RodaConstants.CORE_NODE_HOSTNAME, RodaConstants.DEFAULT_NODE_HOSTNAME), getSystemProperty(RodaConstants.CORE_NODE_PORT, RodaConstants.DEFAULT_NODE_PORT)); akkaDistributedPluginOrchestrator.cleanUnfinishedJobs(); } else { // pluginOrchestrator = new EmbeddedActionOrchestrator(); pluginOrchestrator = new AkkaEmbeddedPluginOrchestrator(); pluginOrchestrator.cleanUnfinishedJobs(); } startApacheDS(); instantiateTransferredResourcesScanner(); processPreservationEventTypeProperties(); } private static void instantiateWorkerNodeSpecificObjects() { akkaDistributedPluginWorker = new AkkaDistributedPluginWorker( getSystemProperty(RodaConstants.CORE_CLUSTER_HOSTNAME, RodaConstants.DEFAULT_NODE_HOSTNAME), getSystemProperty(RodaConstants.CORE_CLUSTER_PORT, RodaConstants.DEFAULT_NODE_PORT), getSystemProperty(RodaConstants.CORE_NODE_HOSTNAME, RodaConstants.DEFAULT_NODE_HOSTNAME), getSystemProperty(RodaConstants.CORE_NODE_PORT, "0")); } private static void instantiateTestNodeSpecificObjects() { if (TEST_DEPLOY_LDAP) { startApacheDS(); } if (TEST_DEPLOY_SCANNER) { instantiateTransferredResourcesScanner(); } if (TEST_DEPLOY_ORCHESTRATOR) { pluginOrchestrator = new AkkaEmbeddedPluginOrchestrator(); } } public static void addLogger(String loggerConfigurationFile) { URL loggerConfigurationFileUrl = getConfigurationFile(loggerConfigurationFile); if (loggerConfigurationFileUrl != null) { System.setProperty("roda.logback.include", loggerConfigurationFileUrl.toString()); configureLogback(); } } private static String getSystemProperty(String property, String defaultValue) { String ret = defaultValue; if (System.getProperty(property) != null) { ret = System.getProperty(property); } return ret; } public static void shutdown() throws IOException { if (instantiated) { if (nodeType == NodeType.MASTER) { solr.close(); stopApacheDS(); pluginManager.shutdown(); pluginOrchestrator.shutdown(); } else if (nodeType == NodeType.WORKER) { pluginManager.shutdown(); } else if (nodeType == NodeType.TEST) { if (TEST_DEPLOY_SOLR) { solr.close(); } if (TEST_DEPLOY_LDAP) { stopApacheDS(); } if (TEST_DEPLOY_ORCHESTRATOR) { pluginOrchestrator.shutdown(); } // final cleanup FSUtils.deletePathQuietly(workingDirectoryPath); } // stop jmx metrics reporter if (getSystemProperty("com.sun.management.jmxremote", null) != null) { jmxMetricsReporter.stop(); } // delete resources that are no longer needed toDeleteDuringShutdown.forEach(e -> FSUtils.deletePathQuietly(e)); } } public static MetricRegistry getMetrics() { return metricsRegistry; } /** * Start ApacheDS. */ private static void startApacheDS() { rodaApacheDSDataDirectory = RodaCoreFactory.getDataPath().resolve(RodaConstants.CORE_LDAP_FOLDER); try { final Configuration rodaConfig = RodaCoreFactory.getRodaConfiguration(); final boolean ldapStartServer = rodaConfig.getBoolean("ldap.startServer", false); final int ldapPort = rodaConfig.getInt("ldap.port", RodaConstants.CORE_LDAP_DEFAULT_PORT); final String ldapBaseDN = rodaConfig.getString("ldap.baseDN", "dc=roda,dc=org"); final String ldapPeopleDN = rodaConfig.getString("ldap.peopleDN", "ou=users,dc=roda,dc=org"); final String ldapGroupsDN = rodaConfig.getString("ldap.groupsDN", "ou=groups,dc=roda,dc=org"); final String ldapRolesDN = rodaConfig.getString("ldap.rolesDN", "ou=groups,dc=roda,dc=org"); final String ldapAdminDN = rodaConfig.getString("ldap.adminDN", "ou=groups,dc=roda,dc=org"); final String ldapAdminPassword = rodaConfig.getString("ldap.adminPassword", "roda"); final String ldapPasswordDigestAlgorithm = rodaConfig.getString("ldap.passwordDigestAlgorithm", "MD5"); final List<String> ldapProtectedUsers = RodaUtils.copyList(rodaConfig.getList("ldap.protectedUsers")); final List<String> ldapProtectedGroups = RodaUtils.copyList(rodaConfig.getList("ldap.protectedGroups")); final String rodaGuestDN = rodaConfig.getString("ldap.rodaGuestDN", "uid=guest,ou=users,dc=roda,dc=org"); final String rodaAdminDN = rodaConfig.getString("ldap.rodaAdminDN", "uid=admin,ou=users,dc=roda,dc=org"); final String rodaAdministratorsDN = rodaConfig.getString("ldap.rodaAdministratorsDN", "cn=administrators,ou=groups,dc=roda,dc=org"); RodaCoreFactory.ldapUtility = new LdapUtility(ldapStartServer, ldapPort, ldapBaseDN, ldapPeopleDN, ldapGroupsDN, ldapRolesDN, ldapAdminDN, ldapAdminPassword, ldapPasswordDigestAlgorithm, ldapProtectedUsers, ldapProtectedGroups, rodaGuestDN, rodaAdminDN, rodaApacheDSDataDirectory); ldapUtility.setRODAAdministratorsDN(rodaAdministratorsDN); UserUtility.setLdapUtility(ldapUtility); if (!FSUtils.exists(rodaApacheDSDataDirectory)) { Files.createDirectories(rodaApacheDSDataDirectory); final List<String> ldifFileNames = Arrays.asList("users.ldif", "groups.ldif", "roles.ldif"); final List<String> ldifs = new ArrayList<>(); for (String ldifFileName : ldifFileNames) { final InputStream ldifInputStream = RodaCoreFactory .getConfigurationFileAsStream(RodaConstants.CORE_LDAP_FOLDER + "/" + ldifFileName); ldifs.add(IOUtils.toString(ldifInputStream, RodaConstants.DEFAULT_ENCODING)); RodaUtils.closeQuietly(ldifInputStream); } RodaCoreFactory.ldapUtility.initDirectoryService(ldifs); indexUsersAndGroupsFromLDAP(); } else { RodaCoreFactory.ldapUtility.initDirectoryService(); } createRoles(rodaConfig); indexUsersAndGroupsFromLDAP(); } catch (final Exception e) { LOGGER.error("Error starting up embedded ApacheDS", e); instantiatedWithoutErrors = false; } } private static void stopApacheDS() { try { RodaCoreFactory.ldapUtility.stopService(); } catch (final Exception e) { LOGGER.error("Error while shutting down ApacheDS embedded server", e); } } /** * For each role in roda-roles.properties create the role in LDAP if it don't * exist already. * * @param rodaConfig * roda configuration * @throws GenericException * if something unexpected happens creating roles. */ private static void createRoles(final Configuration rodaConfig) throws GenericException { final Iterator<String> keys = rodaConfig.getKeys("core.roles"); final Set<String> roles = new HashSet<>(); while (keys.hasNext()) { roles.addAll(Arrays.asList(rodaConfig.getStringArray(keys.next()))); } for (final String role : roles) { try { if (StringUtils.isNotBlank(role)) { RodaCoreFactory.ldapUtility.addRole(role); LOGGER.debug("Created LDAP role {}", role); } } catch (final RoleAlreadyExistsException e) { LOGGER.debug("Role {} already exists.", role); LOGGER.trace(e.getMessage(), e); } } } private static void indexUsersAndGroupsFromLDAP() throws GenericException, IllegalOperationException, NotFoundException, AlreadyExistsException { for (User user : model.listUsers()) { LOGGER.debug("User to be indexed: {}", user); model.notifyUserUpdated(user); } for (Group group : model.listGroups()) { LOGGER.debug("Group to be indexed: {}", group); model.notifyGroupUpdated(group); } } public static void instantiateTransferredResourcesScanner() { try { String transferredResourcesFolder = getRodaConfiguration().getString("transferredResources.folder", RodaConstants.CORE_TRANSFERREDRESOURCE_FOLDER); Path transferredResourcesFolderPath = dataPath.resolve(transferredResourcesFolder); if (!FSUtils.exists(transferredResourcesFolderPath)) { Files.createDirectories(transferredResourcesFolderPath); } transferredResourcesScanner = new TransferredResourcesScanner(transferredResourcesFolderPath, getIndexService()); } catch (final Exception e) { LOGGER.error("Error starting Transferred Resources Scanner: " + e.getMessage(), e); instantiatedWithoutErrors = false; } } public static boolean getTransferredResourcesScannerUpdateStatus(Optional<String> folderRelativePath) { return TransferUpdateStatus.getInstance().isUpdatingStatus(folderRelativePath); } public static void setTransferredResourcesScannerUpdateStatus(Optional<String> folderRelativePath, boolean isUpdating) { TransferUpdateStatus.getInstance().setUpdatingStatus(folderRelativePath, isUpdating); } public static StorageService getStorageService() { return storage; } public static ModelService getModelService() { return model; } public static IndexService getIndexService() { return index; } public static SolrClient getSolr() { return solr; } public static void setSolr(SolrClient solr) { RodaCoreFactory.solr = solr; } public static PluginManager getPluginManager() { return PluginManager.getInstance(); } public static PluginOrchestrator getPluginOrchestrator() { return pluginOrchestrator; } public static AkkaDistributedPluginOrchestrator getAkkaDistributedPluginOrchestrator() { return akkaDistributedPluginOrchestrator; } public static TransferredResourcesScanner getTransferredResourcesScanner() { return transferredResourcesScanner; } public static NodeType getNodeType() { return nodeType; } public static Path getRodaHomePath() { return rodaHomePath; } public static Path getConfigPath() { return configPath; } public static Path getDefaultPath() { return defaultPath; } public static Path getWorkingDirectory() { return workingDirectoryPath; } public static Path getReportsDirectory() { return reportDirectoryPath; } public static Path getDataPath() { return dataPath; } public static Path getStoragePath() { return storagePath; } public static Path getLogPath() { return logPath; } public static Path getPluginsPath() { return configPath.resolve(RodaConstants.CORE_PLUGINS_FOLDER); } public static void closeSolrServer() { try { solr.close(); } catch (IOException e) { LOGGER.error("Error while shutting down solr", e); } } /* * Configuration related functionalities */ public static void addConfiguration(String configurationFile) throws ConfigurationException { Configuration configuration = getConfiguration(configurationFile); rodaConfiguration.addConfiguration(configuration); configurationFiles.add(configurationFile); } public static Configuration getConfiguration(String configurationFile) throws ConfigurationException { Path config = RodaCoreFactory.getConfigPath().resolve(configurationFile); PropertiesConfiguration propertiesConfiguration = new PropertiesConfiguration(); propertiesConfiguration.setDelimiterParsingDisabled(true); propertiesConfiguration.setEncoding(RodaConstants.DEFAULT_ENCODING); if (FSUtils.exists(config)) { LOGGER.trace("Loading configuration from file {}", config); propertiesConfiguration.load(config.toFile()); RodaPropertiesReloadStrategy rodaPropertiesReloadStrategy = new RodaPropertiesReloadStrategy(); rodaPropertiesReloadStrategy.setRefreshDelay(5000); propertiesConfiguration.setReloadingStrategy(rodaPropertiesReloadStrategy); } else { InputStream inputStream = RodaCoreFactory.class .getResourceAsStream("/" + RodaConstants.CORE_CONFIG_FOLDER + "/" + configurationFile); if (inputStream != null) { LOGGER.trace("Loading configuration from classpath {}", configurationFile); propertiesConfiguration.load(inputStream); } else { LOGGER.trace("Configuration {} doesn't exist", configurationFile); } } return propertiesConfiguration; } public static URL getConfigurationFile(String configurationFile) { Path config = RodaCoreFactory.getConfigPath().resolve(configurationFile); URL configUri; if (FSUtils.exists(config) && !FSUtils.isDirectory(config) && config.toAbsolutePath().startsWith(getConfigPath().toAbsolutePath().toString())) { try { configUri = config.toUri().toURL(); } catch (MalformedURLException e) { LOGGER.error("Configuration {} doesn't exist", configurationFile); configUri = null; } } else { URL resource = RodaCoreFactory.class .getResource("/" + RodaConstants.CORE_CONFIG_FOLDER + "/" + configurationFile); if (resource != null) { configUri = resource; } else { LOGGER.error("Configuration {} doesn't exist", configurationFile); configUri = null; } } return configUri; } public static InputStream getConfigurationFileAsStream(String configurationFile) { Path config = getConfigPath().resolve(configurationFile); InputStream inputStream = null; try { if (FSUtils.exists(config) && !FSUtils.isDirectory(config) && config.toAbsolutePath().startsWith(getConfigPath().toAbsolutePath().toString())) { inputStream = Files.newInputStream(config); LOGGER.trace("Loading configuration from file {}", config); } } catch (IOException e) { // do nothing } if (inputStream == null) { inputStream = RodaCoreFactory.class .getResourceAsStream("/" + RodaConstants.CORE_CONFIG_FOLDER + "/" + configurationFile); LOGGER.trace("Loading configuration from classpath {}", configurationFile); } return inputStream; } public static InputStream getConfigurationFileAsStream(String configurationFile, String fallbackConfigurationFile) { InputStream inputStream = getConfigurationFileAsStream(configurationFile); if (inputStream == null) { inputStream = getConfigurationFileAsStream(fallbackConfigurationFile); } return inputStream; } public static InputStream getDefaultFileAsStream(String defaultFile, ClassLoader... extraClassLoaders) { Path defaultPath = getDefaultPath().resolve(defaultFile); InputStream inputStream = null; try { if (FSUtils.exists(defaultPath) && !FSUtils.isDirectory(defaultPath) && defaultPath.toAbsolutePath().startsWith(getDefaultPath().toAbsolutePath().toString())) { inputStream = Files.newInputStream(defaultPath); LOGGER.debug("Trying to load default from file {}", defaultPath); } } catch (IOException e) { // do nothing } if (inputStream == null) { String fileClassPath = "/" + RodaConstants.CORE_DEFAULT_FOLDER + "/" + defaultFile; inputStream = RodaCoreFactory.class.getResourceAsStream(fileClassPath); LOGGER.debug("Trying to load default file from classpath {}", fileClassPath); } if (inputStream == null) { String fileClassPath = RodaConstants.CORE_DEFAULT_FOLDER + "/" + defaultFile; for (ClassLoader classLoader : extraClassLoaders) { LOGGER.debug("Trying to load default file from extra class loader {}", fileClassPath); inputStream = classLoader.getResourceAsStream(fileClassPath); if (inputStream != null) { break; } } } return inputStream; } public static void clearRodaCachableObjectsAfterConfigurationChange() { rodaPropertiesCache.clear(); RODA_SCHEMAS_CACHE.invalidateAll(); I18N_CACHE.invalidateAll(); processPreservationEventTypeProperties(); LOGGER.info("Reloaded roda configurations after file change!"); } private static void processPreservationEventTypeProperties() { String prefix = "core.preservation_event_type"; for (PreservationEventType preservationEventType : PreservationEventType.values()) { String value = getRodaConfigurationAsString(prefix, preservationEventType.name()); if (StringUtils.isNotBlank(value)) { preservationEventType.setText(value); } else { preservationEventType.setText(preservationEventType.getOriginalText()); } } } public static Optional<Schema> getRodaSchema(String metadataType, String metadataVersion) { Optional<Schema> schema = Optional.empty(); try { schema = RODA_SCHEMAS_CACHE.get(Pair.of(metadataType, metadataVersion)); } catch (ExecutionException e) { if (StringUtils.isNotBlank(metadataType)) { try { schema = RODA_SCHEMAS_CACHE.get(Pair.of(metadataType, null)); } catch (ExecutionException e2) { // Do nothing } } } return schema; } public static Configuration getRodaConfiguration() { return rodaConfiguration; } public static String getConfigurationKey(String... keyParts) { StringBuilder sb = new StringBuilder(); for (String part : keyParts) { if (sb.length() != 0) { sb.append('.'); } sb.append(part); } return sb.toString(); } public static String getRodaConfigurationAsString(String... keyParts) { return rodaConfiguration.getString(getConfigurationKey(keyParts)); } public static int getRodaConfigurationAsInt(int defaultValue, String... keyParts) { return rodaConfiguration.getInt(getConfigurationKey(keyParts), defaultValue); } public static List<String> getRodaConfigurationAsList(String... keyParts) { String[] array = rodaConfiguration.getStringArray(getConfigurationKey(keyParts)); return Arrays.asList(array).stream().filter(v -> StringUtils.isNotBlank(v)).collect(Collectors.toList()); } public static int getRodaConfigurationAsInt(String... keyParts) { return getRodaConfigurationAsInt(0, keyParts); } public static List<String> getFixityAlgorithms() { List<String> algorithms = RodaCoreFactory.getRodaConfigurationAsList("core", "premis", "fixity", "algorithms"); if (algorithms == null || algorithms.isEmpty()) { algorithms = RodaConstants.DEFAULT_ALGORITHMS; } return algorithms; } public static Set<String> getFilenamesInsideConfigFolder(String folder) throws IOException { Set<String> fileNames = new HashSet<>(); // get from external config Set<String> externalFileNames = new HashSet<>(); Path configPath = RodaCoreFactory.getConfigPath().resolve(folder); Files.walkFileTree(configPath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { externalFileNames.add(file.getFileName().toString()); return FileVisitResult.CONTINUE; } }); fileNames.addAll(externalFileNames); // get from internal config List<ClassLoader> classLoadersList = new LinkedList<>(); classLoadersList.add(ClasspathHelper.contextClassLoader()); Set<String> internalFilesPath = new Reflections(new ConfigurationBuilder() .filterInputsBy( new FilterBuilder().include(FilterBuilder.prefix("" + RodaConstants.CORE_CONFIG_FOLDER + "/" + folder))) .setScanners(new ResourcesScanner()) .setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))) .getResources(Pattern.compile(".*")); for (String internalFilePath : internalFilesPath) { fileNames.add(Paths.get(internalFilePath).getFileName().toString()); } return fileNames; } public static Map<String, String> getPropertiesFromCache(String cacheName, List<String> prefixesToCache) { if (rodaPropertiesCache.get(cacheName) == null) { fillInPropertiesToCache(cacheName, prefixesToCache); } return rodaPropertiesCache.get(cacheName); } private static void fillInPropertiesToCache(String cacheName, List<String> prefixesToCache) { if (rodaPropertiesCache.get(cacheName) == null) { HashMap<String, String> newCacheEntry = new HashMap<>(); Configuration configuration = RodaCoreFactory.getRodaConfiguration(); Iterator<String> keys = configuration.getKeys(); while (keys.hasNext()) { String key = String.class.cast(keys.next()); String value = configuration.getString(key, ""); for (String prefixToCache : prefixesToCache) { if (key.startsWith(prefixToCache)) { newCacheEntry.put(key, value); break; } } } rodaPropertiesCache.put(cacheName, newCacheEntry); } } public static Messages getI18NMessages(Locale locale) { checkForChangesInI18N(); try { return I18N_CACHE.get(locale); } catch (ExecutionException e) { LOGGER.debug("Could not load messages", e); return null; } } private static void checkForChangesInI18N() { // i18n is cached and that cache is re-done when changes occur to // roda-*.properties (for convenience) getRodaConfiguration().getString(""); } /* * Command-line accessible functionalities */ public static void runReindex(List<String> args) { String entity = args.get(2); if (StringUtils.isNotBlank(entity)) { if ("users_and_groups".equalsIgnoreCase(entity)) { try { indexUsersAndGroupsFromLDAP(); } catch (IllegalOperationException | GenericException | NotFoundException | AlreadyExistsException e) { LOGGER.error("Unable to reindex users & groups from LDAP.", e); } } } } private static void runSolrQuery(List<String> args) { String collection = args.get(2); String solrQueryString = args.get(3); try { QueryResponse executeSolrQuery = SolrUtils.executeSolrQuery(solr, collection, solrQueryString); SolrDocumentList results = executeSolrQuery.getResults(); System.out.println("Size: " + results.getNumFound() + "; Returned: " + results.size()); for (SolrDocument solrDocument : results) { System.out.println(">" + solrDocument); } } catch (SolrServerException | IOException e) { e.printStackTrace(System.err); } } private static void printIndexMembers(List<String> args, Filter filter, Sorter sorter, Sublist sublist, Facets facets) throws GenericException, RequestNotValidException { System.out.println("Index list " + args.get(2)); IndexResult<RODAMember> users = index.find(RODAMember.class, filter, sorter, sublist, facets, new ArrayList<>()); for (RODAMember rodaMember : users.getResults()) { System.out.println("\t" + rodaMember); } } private static void printCountSips(Sorter sorter, Sublist sublist, Facets facets) throws GenericException, RequestNotValidException { Filter filter = new Filter(new SimpleFilterParameter(RodaConstants.TRANSFERRED_RESOURCE_ISFILE, "true")); long countFiles = index.count(TransferredResource.class, filter); filter = new Filter(new SimpleFilterParameter(RodaConstants.TRANSFERRED_RESOURCE_ISFILE, "false")); long countDirectories = index.count(TransferredResource.class, filter); Filter f1 = new Filter(); FilterParameter p1 = new EmptyKeyFilterParameter(RodaConstants.TRANSFERRED_RESOURCE_PARENT_ID); FilterParameter p2 = new BasicSearchFilterParameter(RodaConstants.TRANSFERRED_RESOURCE_ISFILE, "false"); f1.add(p1); f1.add(p2); long countSIP = index.count(TransferredResource.class, f1); System.out.println("Total number of directories: " + countDirectories); System.out.println("Total number of files: " + countFiles); System.out.println("Total number of SIPs: " + countSIP); } private static void printFiles(Sorter sorter, Sublist sublist) throws GenericException, RequestNotValidException { Filter filter = new Filter(new SimpleFilterParameter(RodaConstants.FILE_SEARCH, "OLA-OLÁ-1234-XXXX_K")); IndexResult<IndexedFile> res = index.find(IndexedFile.class, filter, sorter, sublist, new ArrayList<>()); for (IndexedFile sf : res.getResults()) { System.out.println(sf.toString()); } } private static void printEvents(Sorter sorter, Sublist sublist) throws GenericException, RequestNotValidException { Filter filter = new Filter( new SimpleFilterParameter(RodaConstants.PRESERVATION_EVENT_TYPE, "format identification")); IndexResult<IndexedPreservationEvent> res = index.find(IndexedPreservationEvent.class, filter, sorter, sublist, new ArrayList<>()); for (IndexedPreservationEvent ipe : res.getResults()) { System.out.println(ipe.toString()); } } private static void printAgents(Sorter sorter, Sublist sublist) throws GenericException, RequestNotValidException { Filter filter = new Filter( new SimpleFilterParameter(RodaConstants.PRESERVATION_AGENT_TYPE, PreservationAgentType.SOFTWARE.toString())); IndexResult<IndexedPreservationAgent> res = index.find(IndexedPreservationAgent.class, filter, sorter, sublist, new ArrayList<>()); for (IndexedPreservationAgent ipa : res.getResults()) { System.out.println(ipa.toString()); } } private static String readPassword(final String message) throws IOException { final Console console = System.console(); if (console == null) { final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); System.out.print(String.format("%s (INSECURE - password will be shown): ", message)); return reader.readLine(); } else { return new String(console.readPassword("%s: ", message)); } } private static void resetAdminAccess() throws GenericException { try { final String password = readPassword("New admin password"); final String passwordConfirmation = readPassword("Repeat admin password"); if (password.equals(passwordConfirmation)) { RodaCoreFactory.ldapUtility.resetAdminAccess(password); try { indexUsersAndGroupsFromLDAP(); } catch (final Exception e) { LOGGER.warn("Error reindexing users and groups - " + e.getMessage(), e); System.err.println("Error reindexing users and groups (" + e.getMessage() + ")."); } System.out.println("Password for 'admin' changed successfully."); } else { throw new GenericException("Passwords don't match."); } } catch (final IOException e) { throw new GenericException(e.getMessage(), e); } finally { try { RodaCoreFactory.shutdown(); } catch (final Exception e) { e.printStackTrace(System.err); } } } private static void printMainUsage() { System.err.println("WARNING: if using Apache Solr embedded, the index related commands"); System.err.println("cannot be run while RODA is running (i.e. deployed in Tomcat for example)."); System.err.println("Stop RODA before running index commands."); System.err.println(); System.err.println("Usage: java -jar roda-core.jar command [arguments]"); System.err.println("Available commands:"); System.err.println("\tindex"); System.err .println("\t\treindex aip|job|risk|agent|format|notification|transferred_resources|actionlogs|users_and_groups"); System.err.println("\t\tlist users|groups|sips|file"); System.err.println("\torphans [newParentID]"); System.err.println("\tfixity"); System.err.println("\tantivirus"); System.err.println("\tpremisskeleton"); System.err.println("\treset admin"); System.err.println("\tmigrate model"); System.err.println("\tmigrate index"); } private static void printResetUsage() { System.err.println("Reset command parameters:"); System.err.println("\tadmin - resets admin user password and grant it all permissions."); } private static void printMigrateUsage() { System.err.println("Migrate command parameters:"); System.err.println("\tmodel - performs model related migrations."); } private static void mainMasterTasks(final List<String> args) throws GenericException, RequestNotValidException { if ("index".equals(args.get(0))) { if ("list".equals(args.get(1)) && ("users".equals(args.get(2)) || "groups".equals(args.get(2)))) { final Filter filter = new Filter( new SimpleFilterParameter(RodaConstants.MEMBERS_IS_USER, "users".equals(args.get(2)) ? "true" : "false")); printIndexMembers(args, filter, null, new Sublist(0, 10000), null); } else if ("list".equals(args.get(1)) && ("sips".equals(args.get(2)))) { printCountSips(null, new Sublist(0, 10000), null); } else if ("list".equals(args.get(1)) && ("file".equals(args.get(2)))) { printFiles(null, new Sublist(0, 10000)); } else if ("list".equals(args.get(1)) && ("event".equals(args.get(2)))) { printEvents(null, new Sublist(0, 10000)); } else if ("list".equals(args.get(1)) && ("agent".equals(args.get(2)))) { printAgents(null, new Sublist(0, 10000)); } else if ("query".equals(args.get(1)) && args.size() == 4 && StringUtils.isNotBlank(args.get(2)) && StringUtils.isNotBlank(args.get(3))) { runSolrQuery(args); } else if ("reindex".equals(args.get(1))) { runReindex(args); } } else if ("reset".equals(args.get(0))) { final List<String> resetParams = args.subList(1, args.size()); if (resetParams.isEmpty()) { printResetUsage(); } else { final String resetParam = resetParams.get(0); if ("admin".equals(resetParam)) { resetAdminAccess(); } else { System.err.println("ERROR: Unknown parameter '" + resetParam + "'"); printResetUsage(); } } } else if ("migrate".equals(args.get(0))) { final List<String> migrateParams = args.subList(1, args.size()); if (migrateParams.isEmpty()) { printMigrateUsage(); } else { final String migrateParam = migrateParams.get(0); MigrationManager migrationManager = new MigrationManager(RodaCoreFactory.dataPath); if ("model".equals(migrateParam)) { migrationManager.setupModelMigrations(); migrationManager.performModelMigrations(); } else { printMigrateUsage(); } } } else { printMainUsage(); } } private static void preInstantiateSteps(List<String> args) { if (!args.isEmpty() && "migrate".equals(args.get(0))) { migrationMode = true; } } public static void main(final String[] argsArray) throws InterruptedException, GenericException, RequestNotValidException { final List<String> args = Arrays.asList(argsArray); preInstantiateSteps(args); instantiate(); if (getNodeType() == NodeType.MASTER) { if (!args.isEmpty()) { mainMasterTasks(args); } else { printMainUsage(); } } else if (getNodeType() == NodeType.WORKER) { Thread.currentThread().join(); } else { printMainUsage(); } System.exit(0); } }