diff --git a/.ansible-lint b/.ansible-lint index 27fb9d4..8d34551 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -1,3 +1,6 @@ +exclude_paths: + - ./.github + - ./docker skip_list: - no-changed-when # Commands should not change things if nothing needs doing - no-handler # Tasks that run when changed should likely be handlers diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..f43f032 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,70 @@ +# This is a locally modified version of the upstream CKAN docker compose file. +# It contains image names for pushing locally built images to a central registry at ECR + +# docker-compose build && docker-compose up -d +# If "docker-compose logs ckan" shows DB not ready, run "docker-compose restart ckan" a few times. +version: "3" + +volumes: + ckan_config: + ckan_home: + ckan_storage: + ckan_coveragedata: + pg_data: + solr_data: + nginx_config: + nginx_certificates: + +services: + ckan: + container_name: ckan + image: epos-msl-ckan:latest + links: + - db + - solr + - redis + depends_on: + - db + - solr + - redis + ports: + - "0.0.0.0:${CKAN_PORT}:5000" + environment: + - CKAN_SQLALCHEMY_URL=postgresql://ckan:${POSTGRES_PASSWORD}@db/ckan_default + - CKAN_SOLR_URL=http://solr:8983/solr/ckan + - CKAN_REDIS_URL=redis://redis:6379/1 + - CKAN_SITE_URL=${CKAN_SITE_URL} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + volumes: + - ckan_config:/etc/ckan + - ckan_home:/usr/lib/ckan + - ckan_storage:/var/lib/ckan + - ckan_coveragedata:/coverage + + nginx: + container_name: revproxy + image: nginx:1.27 + volumes: + - nginx_config:/etc/nginx/conf.d + - nginx_certificates:/etc/certificates + + db: + container_name: db + image: postgres:12.20 + environment: + - POSTGRES_DB=ckan_default + - POSTGRES_USER=ckan + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - PGDATA=/var/lib/postgresql/data/db + volumes: + - pg_data:/var/lib/postgresql/data + + solr: + container_name: solr + image: epos-msl-solr:latest + volumes: + - solr_data:/opt/solr/server/solr/ckan/data + + redis: + container_name: redis + image: redis:6.2 diff --git a/docker/images/ckan/Dockerfile b/docker/images/ckan/Dockerfile new file mode 100644 index 0000000..2a095d7 --- /dev/null +++ b/docker/images/ckan/Dockerfile @@ -0,0 +1,105 @@ +# This is a locally modified version of the upstream CKAN Dockerfile + +# See CKAN docs on installation from Docker Compose on usage +FROM ubuntu:focal-20210119 +MAINTAINER Yoda team + +# Set timezone +ENV TZ=UTC +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# Setting the locale +ENV LC_ALL=en_US.UTF-8 +RUN adduser ckan +RUN apt-get update +RUN apt-get install --no-install-recommends -y locales +RUN sed -i "/$LC_ALL/s/^# //g" /etc/locale.gen +RUN dpkg-reconfigure --frontend=noninteractive locales +RUN update-locale LANG=${LC_ALL} + +# Install required system packages +RUN apt-get -q -y update \ + && DEBIAN_FRONTEND=noninteractive apt-get -q -y upgrade \ + && apt-get -q -y install \ + python3.8 \ + python3-dev \ + python3-pip \ + python3-venv \ + python3-wheel \ + libpq-dev \ + python3-pastescript \ + python3-virtualenv \ + libxml2-dev \ + libxslt-dev \ + libgeos-dev \ + libssl-dev \ + libffi-dev \ + postgresql-client \ + build-essential \ + git-core \ + vim \ + wget \ + curl \ + nmap \ + sqlite3 \ + pwgen \ + uuid-runtime \ + && apt-get -q clean \ + && rm -rf /var/lib/apt/lists/* + +# Define environment variables +ENV CKAN_HOME /usr/lib/ckan +ENV CKAN_VENV $CKAN_HOME/default +ENV CKAN_CONFIG /etc/ckan +ENV CKAN_STORAGE_PATH=/ckanstorage +ENV CKAN_VERSION=2.9.11 +ENV CKAN_SCHEMING_VERSION=release-2.1.0 +ENV CKAN_MSL_CORE_VERSION=1.4.0 +ENV CKAN_MSL_UTIL_VERSION=1.0.0 + +# Create storage path +RUN mkdir -p $CKAN_STORAGE_PATH/webassets $CKAN_STORAGE_PATH/storage + +# Build-time variables specified by docker-compose.yml / .env +ARG CKAN_SITE_URL + +# Setup virtual environment for CKAN +RUN mkdir -p $CKAN_VENV $CKAN_CONFIG/default && \ + python3 -m venv $CKAN_VENV && \ + ln -s $CKAN_VENV/bin/pip3 /usr/local/bin/ckan-pip3 &&\ + ln -s $CKAN_VENV/bin/ckan /usr/local/bin/ckan +ADD ckan.ini /etc/ckan/default/ckan.ini +ADD wsgi.py /etc/ckan/default/wsgi.py +ADD ckan-uwsgi.ini /etc/ckan/default/ckan-uwsgi.ini + +# Virtual environment binaries/scripts to be used first +ENV PATH=${CKAN_VENV}/bin:${PATH} +ENV EPOS_MSL_FQDN=epos-msl.local +ENV CKAN_ADMIN_PASSWORD="testtest" + +# Install CKAN and plugins +RUN ckan-pip3 install -U pip && \ + ckan-pip3 install setuptools==44.1.0 && \ + ckan-pip3 install --upgrade pip && \ + ckan-pip3 install wheel && \ + ckan-pip3 install -e "git+https://github.com/ckan/ckan@ckan-${CKAN_VERSION}#egg=ckan[requirements]" && \ + ckan-pip3 install uwsgi && \ + ckan-pip3 install -e "git+https://github.com/ckan/ckanext-scheming@${CKAN_SCHEMING_VERSION}#egg=ckanext-scheming" && \ + ckan-pip3 install -e "git+https://github.com/UtrechtUniversity/msl_ckan_core@${CKAN_MSL_CORE_VERSION}#egg=ckanext-msl_ckan" && \ + ckan-pip3 install -e "git+https://github.com/UtrechtUniversity/msl_ckan_util@${CKAN_MSL_UTIL_VERSION}#egg=ckanext-msl_ckan_util" && \ + ln -s $CKAN_VENV/src/ckan/ckan/config/who.ini $CKAN_CONFIG/who.ini && \ + cp -v $CKAN_VENV/src/ckan/contrib/docker/ckan-entrypoint.sh /ckan-entrypoint.sh && \ + chmod +x /ckan-entrypoint.sh && \ + chown -R ckan:ckan $CKAN_HOME $CKAN_VENV $CKAN_CONFIG $CKAN_STORAGE_PATH $COVERAGE_DIR && \ + rm /usr/lib/ckan/default/src/ckan/ckan/config/solr/schema.xml && \ + ln -sf /usr/lib/ckan/default/src/ckanext-msl-ckan/ckanext/msl_ckan/config/solr/schema.xml /usr/lib/ckan/default/src/ckan/ckan/config/solr/schema.xml && \ + perl -n -i.bak -e 'print unless /defaultSearchField/ or /solrQueryParser/' /usr/lib/ckan/default/src/ckan/ckan/config/solr/schema.xml + +ADD ./ckan-entrypoint.sh /ckan-entrypoint.sh +ENTRYPOINT ["/ckan-entrypoint.sh"] +RUN chmod +x /ckan-entrypoint.sh + +USER ckan +EXPOSE 5000 + +CMD ["/ckan-entrypoint.sh"] diff --git a/docker/images/ckan/ckan-entrypoint.sh b/docker/images/ckan/ckan-entrypoint.sh new file mode 100644 index 0000000..19c4b2d --- /dev/null +++ b/docker/images/ckan/ckan-entrypoint.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +set -u + +check_port() { + local host="$1" + local port="$2" + + echo "Checking service availability at ${host}:${port}..." + + while true; do + # Use nmap to scan the specified port on the given host + nmap -p "${port}" "${host}" | grep -q "open" + + # If the port is open, exit the loop + if [ $? -eq 0 ]; then + echo "Server is available at ${host}:${port}" + break + else + echo "Server at ${host}:${port} is not available. Retrying in 1 seconds..." + sleep 1 + fi + done +} + +## Check DB up +check_port db 5432 + +## Check Solr up +check_port solr 8983 + +## Check Redis up +check_port redis 6379 + +## Initialize CKAN config, database and admin account +CKAN_CONFIG_FILE=/etc/ckan/default/ckan.ini +CKAN_INIT_STATUS_FILE=/etc/ckan/default/.ckan_initialized + +if test -f "$CKAN_INIT_STATUS_FILE" +then echo "Configuration and database already initialized." +else echo "Initializing configuration ..." + BEAKER_SESSION_SECRET=$(openssl rand -base64 32) + APP_INSTANCE_UUID=$(uuidgen --name "$EPOS_MSL_FQDN" --namespace "@url" --sha1) + CKAN_DATABASE_PASSWORD=$(pwgen -n 16 -N 1) + CKAN_MSL_VOCABULARIES_ENDPOINT="https://${EPOS_MSL_FQDN}/webservice/api/vocabularies" + perl -pi.bak -e '$beaker_session_secret=$ENV{BEAKER_SESSION_SECRET}; s/BEAKER_SESSION_SECRET/$beaker_session_secret/gee' "$CKAN_CONFIG_FILE" + perl -pi.bak -e '$app_instance_uuid=$ENV{APP_INSTANCE_UUID}; s/APP_INSTANCE_UUID/$app_instance_uuid/gee' "$CKAN_CONFIG_FILE" + perl -pi.bak -e '$ckan_database_password=$ENV{CKAN_DATABASE_PASSWORD}; s/CKAN_DATABASE_PASSWORD/$ckan_database_password/gee' "$CKAN_CONFIG_FILE" + perl -pi.bak -e '$epos_msl_fqdn=$ENV{EPOS_MSL_FQDN}; s/EPOS_MSL_FQDN/$epos_msl_fqdn/gee' "$CKAN_CONFIG_FILE" + perl -pi.bak -e '$ckan_msl_vocabularies_endpoint=$ENV{CKAN_MSL_VOCABULARIES_ENDPOINT}; s/CKAN_MSL_VOCABULARIES_ENDPOINT/$ckan_msl_vocabularies_endpoint/gee' "$CKAN_CONFIG_FILE" + echo "Initializing database ..." + /usr/lib/ckan/default/bin/ckan -c "$CKAN_CONFIG_FILE" db init + touch "$CKAN_INIT_STATUS_FILE" + echo "Configuration and database initialization finished." +fi + +## Start CKAN +echo "Starting CKAN ..." +while true +do /usr/lib/ckan/default/bin/uwsgi -i /etc/ckan/default/ckan-uwsgi.ini + sleep 1 + echo "CKAN process terminated; trying to restart ..." +done diff --git a/docker/images/ckan/ckan-uwsgi.ini b/docker/images/ckan/ckan-uwsgi.ini new file mode 100644 index 0000000..68ffefd --- /dev/null +++ b/docker/images/ckan/ckan-uwsgi.ini @@ -0,0 +1,15 @@ +[uwsgi] + +http = 127.0.0.1:8080 +uid = www-data +gid = www-data +wsgi-file = /etc/ckan/default/wsgi.py +virtualenv = /usr/lib/ckan/default +module = wsgi:application +master = true +pidfile = /tmp/%n.pid +harakiri = 50 +max-requests = 5000 +vacuum = true +callable = application +strict = true diff --git a/docker/images/ckan/ckan.ini b/docker/images/ckan/ckan.ini new file mode 100644 index 0000000..c1f591b --- /dev/null +++ b/docker/images/ckan/ckan.ini @@ -0,0 +1,274 @@ +# +# CKAN - Pylons configuration +# +# These are some of the configuration options available for your CKAN +# instance. Check the documentation in 'doc/configuration.rst' or at the +# following URL for a description of what they do and the full list of +# available options: +# +# http://docs.ckan.org/en/latest/maintaining/configuration.html +# +# The %(here)s variable will be replaced with the parent directory of this file +# + +[DEFAULT] + +# WARNING: *THIS SETTING MUST BE SET TO FALSE ON A PUBLIC ENVIRONMENT* +# With debug mode enabled, a visitor to your site could execute malicious commands. +debug = false + +[app:main] +use = egg:ckan + +## Development settings +ckan.devserver.host = localhost +ckan.devserver.port = 5000 + +## Session settings +cache_dir = /tmp/%(ckan.site_id)s/ +beaker.session.key = ckan + +# This is the secret token that the beaker library uses to hash the cookie sent +# to the client. `ckan generate config` generates a unique value for this each +# time it generates a config file. +beaker.session.secret = BEAKER_SESSION_SECRET + +# `ckan generate config` generates a unique value for this each time it generates +# a config file. +app_instance_uuid = APP_INSTANCE_UUID + +# Default number of search facets returned in a query +search.facets.limit = 10000 + +# repoze.who config +who.config_file = %(here)s/who.ini +who.log_level = warning +who.log_file = %(cache_dir)s/who_log.ini +# Session timeout (user logged out after period of inactivity, in seconds). +# Inactive by default, so the session doesn't expire. +who.timeout = 3600 + +## Database Settings +sqlalchemy.url = postgresql://ckan:CKAN_DATABASE_PASSWORD@db/ckan_default + +#ckan.datastore.write_url = postgresql://ckan_default:pass@localhost/datastore_default +#ckan.datastore.read_url = postgresql://datastore_default:pass@localhost/datastore_default + +# PostgreSQL' full-text search parameters +ckan.datastore.default_fts_lang = english +ckan.datastore.default_fts_index_method = gist + + +## Site Settings + +ckan.site_url = https://EPOS_MSL_FQDN +#ckan.use_pylons_response_cleanup_middleware = true + +# Default timeout for Requests +#ckan.requests.timeout = 10 + + +## Authorization Settings + +ckan.auth.anon_create_dataset = false +ckan.auth.create_unowned_dataset = false +ckan.auth.create_dataset_if_not_in_organization = false +ckan.auth.user_create_groups = false +ckan.auth.user_create_organizations = false +ckan.auth.user_delete_groups = true +ckan.auth.user_delete_organizations = true +ckan.auth.create_user_via_api = false +ckan.auth.create_user_via_web = false +ckan.auth.roles_that_cascade_to_sub_groups = admin +ckan.auth.public_user_details = false +ckan.auth.public_activity_stream_detail = false +ckan.auth.allow_dataset_collaborators = false +ckan.auth.create_default_api_keys = false + +## API Token Settings +api_token.nbytes = 60 +api_token.jwt.encode.secret = string:HvlXPoquK7PcVZ1eMo7Lwf_Rs +api_token.jwt.decode.secret = string:HvlXPoquK7PcVZ1eMo7Lwf_Rs +api_token.jwt.algorithm = HS256 + +## API Token: expire_api_token plugin +expire_api_token.default_lifetime = 3600 + +## Search Settings + +ckan.site_id = default + +solr_url = http://solr:8983/solr/ckan + +## Redis Settings + +# URL to your Redis instance, including the database to be used. +ckan.redis.url = redis://redis:6379/0 + + +## CORS Settings + +# If cors.origin_allow_all is true, all origins are allowed. +# If false, the cors.origin_whitelist is used. +# ckan.cors.origin_allow_all = true +# cors.origin_whitelist is a space separated list of allowed domains. +# ckan.cors.origin_whitelist = http://example1.com http://example2.com + + +## Plugins Settings + +# Note: Add ``datastore`` to enable the CKAN DataStore +# Add ``datapusher`` to enable DataPusher +# Add ``resource_proxy`` to enable resorce proxying and get around the +# same origin policy +#ckan.plugins = stats text_view image_view recline_view +ckan.plugins = stats text_view image_view recline_view msl_ckan scheming_datasets scheming_groups scheming_organizations msl_custom_facets msl_repeating_fields + +scheming.dataset_schemas = ckanext.msl_ckan:schemas/datasets/data_publication.yml ckanext.msl_ckan:schemas/datasets/labs.json +scheming.organization_schemas = ckanext.msl_ckan:schemas/organizations/organization.json + +mslfacets.dataset_config = ckanext.msl_ckan:config/facets.json + +mslindexfields.field_config = ckanext.msl_ckan:config/msl_index_fields.json + +mslvocabularies.endpoint_root = CKAN_MSL_VOCABULARIES_ENDPOINT + +# Define which views should be created by default +# (plugins must be loaded in ckan.plugins) +ckan.views.default_views = image_view text_view recline_view + +# Customize which text formats the text_view plugin will show +#ckan.preview.json_formats = json +#ckan.preview.xml_formats = xml rdf rdf+xml owl+xml atom rss +#ckan.preview.text_formats = text plain text/plain + +# Customize which image formats the image_view plugin will show +#ckan.preview.image_formats = png jpeg jpg gif + +## Front-End Settings + +ckan.site_title = CKAN +ckan.site_logo = /base/images/ckan-logo.png +ckan.site_description = +ckan.favicon = /base/images/ckan.ico +ckan.gravatar_default = identicon +ckan.preview.direct = png jpg gif +ckan.preview.loadable = html htm rdf+xml owl+xml xml n3 n-triples turtle plain atom csv tsv rss txt json +ckan.display_timezone = server + +# package_hide_extras = for_search_index_only +#package_edit_return_url = http://another.frontend/dataset/ +#package_new_return_url = http://another.frontend/dataset/ +#ckan.recaptcha.publickey = +#ckan.recaptcha.privatekey = +#licenses_group_url = http://licenses.opendefinition.org/licenses/groups/ckan.json +# ckan.template_footer_end = + + +## Internationalisation Settings +ckan.locale_default = en +ckan.locale_order = en pt_BR ja it cs_CZ ca es fr el sv sr sr@latin no sk fi ru de pl nl bg ko_KR hu sa sl lv +ckan.locales_offered = +ckan.locales_filtered_out = en_GB + +## Feeds Settings + +ckan.feeds.authority_name = +ckan.feeds.date = +ckan.feeds.author_name = +ckan.feeds.author_link = + +## Storage Settings + +ckan.storage_path = /ckanstorage +#ckan.max_resource_size = 10 +#ckan.max_image_size = 2 + +## Webassets Settings +#ckan.webassets.use_x_sendfile = false +#ckan.webassets.path = /var/lib/ckan/webassets + + +## Datapusher settings + +# Make sure you have set up the DataStore + +#ckan.datapusher.formats = csv xls xlsx tsv application/csv application/vnd.ms-excel application/vnd.openxmlformats-officedocument.spreadsheetml.sheet +#ckan.datapusher.url = http://127.0.0.1:8800/ +#ckan.datapusher.assume_task_stale_after = 3600 + +# Resource Proxy settings +# Preview size limit, default: 1MB +#ckan.resource_proxy.max_file_size = 1048576 +# Size of chunks to read/write. +#ckan.resource_proxy.chunk_size = 4096 +# Default timeout for fetching proxied items +#ckan.resource_proxy.timeout = 10 + +## Activity Streams Settings + +#ckan.activity_streams_enabled = true +#ckan.activity_list_limit = 31 +#ckan.activity_streams_email_notifications = true +#ckan.email_notifications_since = 2 days +ckan.hide_activity_from_users = %(ckan.site_id)s + + +## Email settings + +#email_to = errors@example.com +#error_email_from = ckan-errors@example.com +#smtp.server = localhost +#smtp.starttls = False +#smtp.user = username@example.com +#smtp.password = your_password +#smtp.mail_from = +#smtp.reply_to = + +## Background Job Settings +ckan.jobs.timeout = 180 + +## Logging configuration +[loggers] +keys = root, ckan, ckanext, werkzeug + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console + +[logger_werkzeug] +level = WARNING +handlers = console +qualname = werkzeug +propagate = 0 + +[logger_ckan] +level = INFO +handlers = console +qualname = ckan +propagate = 0 + +[logger_ckanext] +level = DEBUG +handlers = console +qualname = ckanext +propagate = 0 + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s + +# msl_ckan_util settings +ckan.mslfacets.dataset_config = ckanext.msl_ckan_util:samples/facets.json +ckan.mslindexfields.fields_config = ckanext.msl_ckan_util:samples/msl_index_fields.json diff --git a/docker/images/ckan/wsgi.py b/docker/images/ckan/wsgi.py new file mode 100644 index 0000000..9880af9 --- /dev/null +++ b/docker/images/ckan/wsgi.py @@ -0,0 +1,12 @@ +# -- coding: utf-8 -- + +import os +from ckan.config.middleware import make_app +from ckan.cli import CKANConfigLoader +from logging.config import fileConfig as loggingFileConfig +config_filepath = os.path.join( + os.path.dirname(os.path.abspath(__file__)), 'ckan.ini') +abspath = os.path.join(os.path.dirname(os.path.abspath(__file__))) +loggingFileConfig(config_filepath) +config = CKANConfigLoader(config_filepath).get_config() +application = make_app(config) diff --git a/docker/images/solr/Dockerfile b/docker/images/solr/Dockerfile new file mode 100644 index 0000000..e9028da --- /dev/null +++ b/docker/images/solr/Dockerfile @@ -0,0 +1,27 @@ +# Image for Solr configured for CKAN + +FROM solr:8.11 +MAINTAINER Yoda team + +ENV SOLR_CONFIG_DIR="/opt/solr/server/solr/configsets" + +# Create a CKAN configset based on the default one +USER root +RUN cp -R $SOLR_CONFIG_DIR/_default $SOLR_CONFIG_DIR/ckan + +# Download the EPOS-MSL core plugin (for the schema) +RUN cd /usr/lib && \ + apt update && \ + apt install -y git && \ + git clone https://github.com/UtrechtUniversity/msl_ckan_core.git + +# Change the CKAN configset to use the EPOS-MSL schema, and enable Query Elevation +USER root +ADD solrconfig.xml /opt/solr-8.11.4/server/solr/configsets/ckan/conf/solrconfig.xml +RUN ln -s /usr/lib/msl_ckan_core/ckanext/msl_ckan/config/solr/schema.xml $SOLR_CONFIG_DIR/ckan/conf/schema.xml && \ + perl -n -i.bak -e 'print unless /defaultSearchField/ or /solrQueryParser/' $SOLR_CONFIG_DIR/ckan/conf/schema.xml && \ + cp $SOLR_CONFIG_DIR/ckan/conf/schema.xml $SOLR_CONFIG_DIR/ckan/conf/managed-schema && \ + cp /opt/solr/server/solr/configsets/sample_techproducts_configs/conf/elevate.xml $SOLR_CONFIG_DIR/ckan/conf/elevate.xml + +USER solr +CMD ["sh", "-c", "solr-precreate ckan $SOLR_CONFIG_DIR/ckan"] diff --git a/docker/images/solr/solrconfig.xml b/docker/images/solr/solrconfig.xml new file mode 100644 index 0000000..eb88d16 --- /dev/null +++ b/docker/images/solr/solrconfig.xml @@ -0,0 +1,1364 @@ + + + + + + + + + + 6.6.3 + + + + + + + + + + + + + + + + + + + + ${solr.data.dir:} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${solr.lock.type:native} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${solr.ulog.dir:} + ${solr.ulog.numVersionBuckets:65536} + + + + + ${solr.autoCommit.maxTime:15000} + false + + + + + + ${solr.autoSoftCommit.maxTime:-1} + + + + + + + + + + + + + 1024 + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + 20 + + + 200 + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + AND + explicit + 10 + + + + + + + + + + + + + + + explicit + json + true + + + + + + + + explicit + + + + + + _text_ + + + + + + + true + ignored_ + _text_ + + + + + + + + + text_general + + + + + + default + _text_ + solr.DirectSolrSpellChecker + + internal + + 0.5 + + 2 + + 1 + + 5 + + 4 + + 0.01 + + + + + + + + + + + + default + on + true + 10 + 5 + 5 + true + true + 10 + 5 + + + spellcheck + + + + + + + + + + true + + + tvComponent + + + + + + + + + + + + true + false + + + terms + + + + + + + + string + elevate.xml + + + + + + explicit + + + elevator + + + + + + + + + + + 100 + + + + + + + + 70 + + 0.5 + + [-\w ,/\n\"']{20,200} + + + + + + + ]]> + ]]> + + + + + + + + + + + + + + + + + + + + + + + + ,, + ,, + ,, + ,, + ,]]> + ]]> + + + + + + 10 + .,!? + + + + + + + WORD + + + en + US + + + + + + + + + + + + + + [^\w-\.] + _ + + + + + + + yyyy-MM-dd'T'HH:mm:ss.SSSZ + yyyy-MM-dd'T'HH:mm:ss,SSSZ + yyyy-MM-dd'T'HH:mm:ss.SSS + yyyy-MM-dd'T'HH:mm:ss,SSS + yyyy-MM-dd'T'HH:mm:ssZ + yyyy-MM-dd'T'HH:mm:ss + yyyy-MM-dd'T'HH:mmZ + yyyy-MM-dd'T'HH:mm + yyyy-MM-dd HH:mm:ss.SSSZ + yyyy-MM-dd HH:mm:ss,SSSZ + yyyy-MM-dd HH:mm:ss.SSS + yyyy-MM-dd HH:mm:ss,SSS + yyyy-MM-dd HH:mm:ssZ + yyyy-MM-dd HH:mm:ss + yyyy-MM-dd HH:mmZ + yyyy-MM-dd HH:mm + yyyy-MM-dd + + + + + + + + + + + + + + + + + + + + + + + text/plain; charset=UTF-8 + + + + + ${velocity.template.base.dir:} + ${velocity.solr.resource.loader.enabled:true} + ${velocity.params.resource.loader.enabled:false} + + + + + 5 + + + + + + + + + + + + + + +