blob: efbc443b902dd58f0ac3e5fe4690576d050f68dc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*/
package org.apache.roller.weblogger.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.roller.weblogger.WebloggerException;
import org.apache.roller.weblogger.business.pings.PingTargetManager;
import org.apache.roller.weblogger.business.WebloggerFactory;
import org.apache.roller.weblogger.pojos.PingTarget;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// This may need to move to a different package, but it seems appropriate here in the current structure.
// Previous placement in the presentation.pings package introduced the undesirable dependency of the
// business package on the presentation package.
/**
* Thin wrapper around WebloggerConfig and WebloggerRuntimeConfig for centralizing access to the many configurable
* settings for pings.
*
* @author <a href="mailto:anil@busybuddha.org">Anil Gangolli</a>
*/
public final class PingConfig {
private static final Log LOGGER = LogFactory.getLog(PingConfig.class);
// Config property for maximum ping attempts.
static final String MAX_PING_ATTEMPTS_PROP = "pings.maxPingAttempts";
private static final int MAX_PING_ATTEMPTS_DEFAULT = 3;
private static final int MAX_PING_ATTEMPTS_MIN = 1;
private static final int MAX_PING_ATTEMPTS_MAX = 10;
// Config property for queue processing interval
private static final String QUEUE_PROCESSING_INTERVAL_PROP = "pings.queueProcessingIntervalMins";
private static final int QUEUE_PROCESSING_INTERVAL_DEFAULT = 5;
private static final int QUEUE_PROCESSING_INTERVAL_MIN = 0;
private static final int QUEUE_PROCESSING_INTERVAL_MAX = 120;
// PingConfig property for logging pings (not actually performing them). Used for debugging.
private static final String PINGS_LOG_ONLY_PROP = "pings.logOnly";
private static final boolean PINGS_LOG_ONLY_DEFAULT = false;
// PingConfig property for controlling whether or not to allow usage of pings
// ("Weblog:Pings" page and actions). If absent, this defaults to false
// NOTE: If this property name is changed, editor-menu.xml must also be adjusted.
private static final String PINGS_DISABLE_PING_USAGE_PROP = "pings.disablePingUsage";
private static final boolean PINGS_DISABLE_PING_USAGE_DEFAULT = false;
// PingConfig property for controlling suspending the processing of pings. If true,
// new auto ping requests are not queued, any existing queued requests are not processed,
// and sending a manual ping results in a message saying pings have been disabled.
// NOTE: This is a "runtime" property settable on the Admin:PingConfig page, default is false.
private static final String PINGS_SUSPEND_PING_PROCESSING_PROP = "pings.suspendPingProcessing";
// PingConfig property determining the initial common ping targets. If the list of common
// ping targets is empty on startup, the value of this property is used to populate initial values.
// The value takes the form of comma-separated ping targets where each ping target is specified in
// the form {{name}{pingurl}}. If an administrator wants to disable this initialization, in order to
// maintain an empty list of common targets, the administrator can disable the initialization by
// commenting out this property in the config file.
private static final String PINGS_INITIAL_COMMON_TARGETS_PROP = "pings.initialCommonTargets";
// PingConfig property determining the known WeblogUpdates.ping variants/bugs
// in popular ping targets, which we are used when invoking pings on those targets.
// The value takes the form of a comma separated list of ping target urls and
// variant options, where each one is in the form {{pingurl}{option[[,option]...]}}.
private static final String PINGS_VARIANT_OPTIONS_PROP = "pings.variantOptions";
// Map of configured ping variants. Maps a ping target hostname to a set of
// Strings representing variant options to be used when pinging this target.
// This was introduced in order to support certain buggy (but popular) ping
// targets that implement minor variants of the WeblogUpdates.ping call.
// This is initialized once at startup, and referenced when pings are made.
private static final Map<String, Set<String>> CONFIGURED_VARIANTS = new HashMap<>();
// Pattern used to parse common ping targets as well as ping variants.
// Each initial commmon ping target is specified in the format {{name}{url}}
// Ping variants are also specified in a nested brace format {{url}{options}}
private static final Pattern NESTED_BRACE_PAIR = Pattern.compile("\\{\\{(.*?)\\}\\{(.*?)\\}\\}");
// Inhibit construction
private PingConfig() {
}
/**
* Get the maximum number of ping attempts that should be made for each ping queue entry before we give up. If we
* get apparently transient failures while trying to perform the ping, the entry is requeued for processing on later
* passes through the queue until this number of attempts has been reached.
*
* @return the configured (or default) maximum number of ping attempts
*/
public static int getMaxPingAttempts() {
return getIntegerProperty(MAX_PING_ATTEMPTS_PROP, MAX_PING_ATTEMPTS_DEFAULT,
MAX_PING_ATTEMPTS_MIN, MAX_PING_ATTEMPTS_MAX);
}
/**
* Get the ping queue processing interval in minutes.
*
* @return the configured (or default) queue processing interval in minutes.
*/
public static int getQueueProcessingIntervalMins() {
return getIntegerProperty(QUEUE_PROCESSING_INTERVAL_PROP, QUEUE_PROCESSING_INTERVAL_DEFAULT,
QUEUE_PROCESSING_INTERVAL_MIN, QUEUE_PROCESSING_INTERVAL_MAX);
}
/**
* Get the logs only setting. Get configuration value determining whether pings are to be logged only (not sent).
* This configuration setting is used for development and debugging.
*
* @return the configured (or default) value of the logs only setting.
*/
public static boolean getLogPingsOnly() {
return getBooleanProperty(PINGS_LOG_ONLY_PROP, PINGS_LOG_ONLY_DEFAULT);
}
/**
* Determine whether the configuration disables ping usage (configuration of auto pings and sending of manual
* pings). If this is true, all auto ping configus are removed at startup, the Weblog:Pings UI and the associated
* actions are disabled.
*
* @return the configured (or default) value of the enable ping usage setting.
*/
public static boolean getDisablePingUsage() {
return getBooleanProperty(PINGS_DISABLE_PING_USAGE_PROP, PINGS_DISABLE_PING_USAGE_DEFAULT);
}
/**
* Determine whether ping processing is suspended. If this is true, new auto ping requests are not
* queued, any existing queued requests are not processed, and sending a manual ping results in a message saying
* pings have been disabled.
*
* @return the configured (or default) value of the suspend ping processing setting.
*/
public static boolean getSuspendPingProcessing() {
return WebloggerRuntimeConfig.getBooleanProperty(PINGS_SUSPEND_PING_PROCESSING_PROP);
}
/**
* Initialize the common ping targets from the configuration properties. If the current list of common ping targets
* is empty, and the <code>PINGS_INITIAL_COMMON_TARGETS_PROP</code> property is present in the configuration then,
* this method will use that value to initialize the common targets. This is called on each server startup.
* <p/>
* Note: this is expected to be called during initialization with transaction demarcation being handled by the
* caller.
*
* @see org.apache.roller.weblogger.ui.core.RollerContext#contextInitialized(javax.servlet.ServletContextEvent)
*/
public static void initializeCommonTargets() throws WebloggerException {
String configuredVal = WebloggerConfig.getProperty(PINGS_INITIAL_COMMON_TARGETS_PROP);
if (configuredVal == null || configuredVal.isBlank()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("No (or empty) value of " + PINGS_INITIAL_COMMON_TARGETS_PROP + " present in the configuration. Skipping initialization of commmon targets.");
}
return;
}
PingTargetManager pingTargetMgr = WebloggerFactory.getWeblogger().getPingTargetManager();
if (!pingTargetMgr.getCommonPingTargets().isEmpty()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Some common ping targets are present in the database already. Skipping initialization.");
}
return;
}
String[] configuredTargets = configuredVal.trim().split(",");
for (int i = 0; i < configuredTargets.length; i++) {
// Trim space around the target spec
String thisTarget = configuredTargets[i].trim();
// skip empty ones
if (thisTarget.length() == 0) {
continue;
}
// parse the ith target and store it
Matcher m = NESTED_BRACE_PAIR.matcher(thisTarget);
if (m.matches() && m.groupCount() == 2) {
String name = m.group(1).trim();
String url = m.group(2).trim();
LOGGER.info("Creating common ping target '" + name + "' from configuration properties.");
PingTarget pingTarget = new PingTarget(null, name, url, false);
pingTargetMgr.savePingTarget(pingTarget);
} else {
LOGGER.error("Unable to parse configured initial ping target '" + thisTarget + "'. Skipping this target. Check your setting of the property " + PINGS_INITIAL_COMMON_TARGETS_PROP);
}
}
}
/**
* Initialize known ping variants from the configuration.
*/
public static void initializePingVariants() {
String configuredVal = WebloggerConfig.getProperty(PINGS_VARIANT_OPTIONS_PROP);
if (configuredVal == null || configuredVal.isBlank()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("No (or empty) value of " + PINGS_VARIANT_OPTIONS_PROP + " present in the configuration. Skipping initialization of ping variants.");
}
return;
}
String[] variants = configuredVal.trim().split(",");
for (int i = 0; i < variants.length; i++) {
String thisVariant = variants[i].trim();
if (thisVariant.length() == 0) {
continue;
}
Matcher m = NESTED_BRACE_PAIR.matcher(thisVariant);
if (m.matches() && m.groupCount() == 2) {
String url = m.group(1).trim();
String optionsList = m.group(2).trim();
Set<String> variantOptions = new HashSet<>();
String[] options = optionsList.split(",");
for (int j = 0; j < options.length; j++) {
String option = options[j].trim().toLowerCase();
if (option.length() > 0) {
variantOptions.add(option);
}
}
if (!variantOptions.isEmpty()) {
CONFIGURED_VARIANTS.put(url, variantOptions);
} else {
LOGGER.warn("Ping variant entry for url '" + url
+ "' has an empty variant options list. Ignored.");
}
} else {
LOGGER.error("Unable to parse configured ping variant '"
+ thisVariant + "'. Skipping this variant. Check your setting of the property "
+ PINGS_VARIANT_OPTIONS_PROP);
}
}
}
/**
* Get the set of variant options configured for the given ping target url.
*
* @param pingTargetUrl
* @return the set of variant options configured for the given ping target url, or
* the empty set if there are no variants configured.
*/
public static Set<String> getVariantOptions(String pingTargetUrl) {
Set<String> variantOptions = CONFIGURED_VARIANTS.get(pingTargetUrl);
if (variantOptions == null) {
variantOptions = Collections.emptySet();
}
return variantOptions;
}
// TODO: Refactor functionality below to WebloggerConfig?
/**
* Get the value of an integer configuration property.
*
* @param propName the property name
* @param defaultValue the default value if the property is not present
* @param min the minimum allowed value
* @param max the maximum allowed value
* @return the value as an integer; the default value if no configured value is present or if the configured value
* is out of the specified range.
*/
private static int getIntegerProperty(String propName, int defaultValue, int min, int max) {
String configuredVal = WebloggerConfig.getProperty(propName);
if (configuredVal == null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("PingConfig property '" + propName
+ "' is not present in the configuration. Using default value: " + defaultValue);
}
return defaultValue;
}
int val;
try {
val = Integer.parseInt(configuredVal);
} catch (NumberFormatException ex) {
LOGGER.error("ERROR: PingConfig property '" + propName
+ "' is not an integer value. Using default value: " + defaultValue);
return defaultValue;
}
if (val < min || val > max) {
LOGGER.error("ERROR: PingConfig property '" + propName
+ "' is outside the required range ("
+ min + ", " + max + "). Using default value: " + defaultValue);
return defaultValue;
}
return val;
}
/**
* Get the value of a boolean property with specified default.
*
* @param propName the property name
* @param defaultValue the default value if the property is not present
* @return the configured value or the default if it the configured value is not present.
*/
private static boolean getBooleanProperty(String propName, boolean defaultValue) {
String configuredVal = WebloggerConfig.getProperty(propName);
if (configuredVal == null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("PingConfig property '" + propName
+ "' is not present in the configuration. Using default value: " + defaultValue);
}
return defaultValue;
}
return Boolean.valueOf(configuredVal);
}
}