blob: 2638d02120f7ebfdc02cfb3ac68f8886027b479c [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.business.pings;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.roller.weblogger.config.PingConfig;
import org.apache.roller.weblogger.pojos.PingTarget;
import org.apache.roller.weblogger.pojos.Weblog;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Utility for sending a weblog update ping.
*
* This implements the <code>WeblogUpdates.ping<code> XML-RPC call described at
* <a href="http://www.xmlrpc.com/weblogsCom">www.xmlrpc.com</a>
* as well as some variants required to interoperate with certain
* buggy but popular ping targets.
*
*
* @author <a href="mailto:anil@busybuddha.org">Anil Gangolli</a>
* @author llavandowska (for code refactored from the now-defunct <code>RollerXmlRpcClient</code>)
*/
public final class WeblogUpdatePinger {
public static final Log LOGGER = LogFactory.getLog(WeblogUpdatePinger.class);
/**
* Conveys a ping result.
*/
public static class PingResult {
boolean error;
String message;
public PingResult(Boolean error, String message) {
this.error = error != null && error;
this.message = message != null ? message : "";
}
public boolean isError() {
return error;
}
public void setError(boolean error) {
this.error = error;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String toString() {
return "PingResult{" + "error=" + error + ", message='" + message + "'" + "}";
}
}
// Inhibit construction
private WeblogUpdatePinger() {
}
/**
* Send a weblog update ping.
*
* @param pingTarget the target site to ping
* @param website the website that changed (from which the ping originates)
* @return the result message string sent by the server.
* @throws IOException if an IOException occurs during the ping
* @throws XmlRpcException if the XML RPC client throws one
*/
public static PingResult sendPing(PingTarget pingTarget, Weblog website) throws IOException, XmlRpcException {
String websiteUrl = website.getAbsoluteURL();
String pingTargetUrl = pingTarget.getPingUrl();
Set variantOptions = PingConfig.getVariantOptions(pingTargetUrl);
// Set up the ping parameters.
List params = new ArrayList();
if (!variantOptions.contains("noname")) {
// ping variant for icerocket and anyone with similar bug, where we must omit the blog name.
params.add(website.getName());
}
params.add(websiteUrl);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Executing ping to '" + pingTargetUrl + "' for website '" + websiteUrl + "' (" + website.getName() + ")" + (variantOptions.isEmpty() ? "" : " with variant options " + variantOptions));
}
// Send the ping.
XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
config.setServerURL(new URL(pingTargetUrl));
XmlRpcClient client = new XmlRpcClient();
client.setConfig(config);
PingResult pingResult = parseResult(client.execute("weblogUpdates.ping", params.toArray()));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Ping result is: " + pingResult);
}
return pingResult;
}
private static PingResult parseResult(Object obj) {
// Deal with the fact that some buggy ping targets may not respond with the proper struct type.
if (obj == null) {
return new PingResult(null,null);
}
try {
// normal case: response is a struct (represented as a Map) with Boolean flerror and String fields.
Map result = (Map) obj;
return new PingResult((Boolean) result.get("flerror"), (String) result.get("message"));
} catch (Exception ex) {
// exception case: The caller responded with an unexpected type, though parsed at the basic XML RPC level.
// This effectively assumes flerror = false, and sets message = obj.toString();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Invalid ping result of type: " + obj.getClass().getName() + "; proceeding with stand-in representative.");
}
return new PingResult(null,obj.toString());
}
}
/**
* Decide if the given exception appears to warrant later retrial attempts.
*
* @param ex an exception thrown by the <coce>sendPing</code> operation
* @return true if the error warrants retrial
*/
public static boolean shouldRetry(Exception ex) {
// Determine if error appears transient (warranting retrial)
// We give most errors the "benefit of the doubt" by considering them transient
// This picks out a few that we consider non-transient
if (ex instanceof UnknownHostException) {
// User probably mistyped the url in the custom target.
return false;
} else if (ex instanceof MalformedURLException) {
// This should never happen due to validations but if we get here, retrial won't fix it.
return false;
}
return true;
}
}