// BlogBridge -- RSS feed reader, manager, and web based service // Copyright (C) 2002-2006 by R. Pito Salas // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software Foundation; // either version 2 of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along with this program; // if not, write to the Free Software Foundation, Inc., 59 Temple Place, // Suite 330, Boston, MA 02111-1307 USA // // Contact: R. Pito Salas // mailto:pitosalas@users.sourceforge.net // More information: about BlogBridge // http://www.blogbridge.com // http://sourceforge.net/projects/blogbridge // // $Id: ServerService.java,v 1.52 2007/07/04 09:38:30 spyromus Exp $ // package com.salas.bb.service; import com.jgoodies.uif.application.Application; import com.jgoodies.uif.util.ResourceUtils; import com.salas.bb.domain.CommunityFields; import com.salas.bb.utils.ResourceID; import com.salas.bb.utils.StringUtils; import com.salas.bb.utils.i18n.Strings; import org.apache.xmlrpc.XmlRpcClientSimple; import org.apache.xmlrpc.XmlRpcException; import org.apache.xmlrpc.XmlRpcHandler; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.security.NoSuchAlgorithmException; import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; /** * BlogBridge server service. Communicates with server and exchanges information * with it. */ public final class ServerService { private static final Logger LOG = Logger.getLogger(ServerService.class.getName()); private static final String MSG_COM_PROBLEM = "Communication problem."; private static final String MSG_PROCESSING_ERROR = "Processing error."; private static final String MSG_UNABLE_TO_STORE_DATA = "Unable to store data on the server."; // getSessionId error codes private static final int ERROR_ACCOUNT_NOT_ACTIVATED = -1; private static final int ERROR_PASSWORD_INCORRECT = -2; private static final int ERROR_ACCOUNT_NOT_REGISTERED = -3; private static final URL SERVICE_URL; private static ServerService instance; private static final int OPML_VERSION = 1; private final static Object opmlURLsLock = new Object(); private static Map opmlURLs = null; static { String serviceURL = System.getProperty(ResourceID.URL_SERVICE); if (serviceURL == null) serviceURL = ResourceUtils.getString(ResourceID.URL_SERVICE); LOG.config("Server service URL: " + serviceURL); URL url = null; try { url = new URL(serviceURL); } catch (MalformedURLException e) { LOG.severe(MessageFormat.format("Failed to initialize service with bad URL: {0}", serviceURL)); } finally { SERVICE_URL = url; } } /** * Private singleton constructor. */ private ServerService() { } /** * Returns instances of service. * * @return instance. */ public static synchronized ServerService getInstance() { if (instance == null) instance = new ServerService(); return instance; } /** * Returns server XML-RPC client. * * @return client or null in case of error. */ public static XmlRpcHandler getClient() { return new XmlRpcClientSimple(SERVICE_URL); } /** * Registers user on the server. * * @param fullName full user name. * @param email user's email. * @param password user's password. * @param locale user's locale. * @param notifyOnUpdates if user allowed to use his email for updates notifications. * * @throws ServerServiceException in case of any errors. */ public void registerUser(String fullName, String email, String password, String locale, boolean notifyOnUpdates) throws ServerServiceException { XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(5); params.add(fullName); params.add(email); params.add(password); params.add(locale); params.add(notifyOnUpdates); String errMessage; try { errMessage = (String)cl.execute("accounts.registerAccount", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError(Strings.error("service.unable.to.process.registration.on.server"), e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } // If not-empty error message, then throw error. if (errMessage != null && errMessage.length() > 0) { throw new ServerServiceException(errMessage); } } /** * Simple ping showing whether the service is there or no. * * @return <code>TRUE</code> if service is accessible. */ public static boolean ping() { boolean online = false; XmlRpcHandler cl = getClient(); Vector params = new Vector(0); try { String response = (String)cl.execute("ping.ping", params); online = "pong".equals(response); } catch (XmlRpcException e) { // Service is offline } catch (Exception e) { logError(Level.WARNING, "Invalid response.", e); } return online; } /** * Sends ping with information to the server. * * @param installationId ID of application instance. * @param appVersion version of application. * @param runs number of runs. * @param os OS name and version. * @param javaVersion JRE version. * @param email user's account email (nullable). * @param password user's account password (nullable). * * @throws ServerServiceException in case of communication problem. */ public void ping(long installationId, String appVersion, int runs, String os, String javaVersion, String email, String password) throws ServerServiceException { XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(7); params.add(Long.toString(installationId)); params.add(appVersion); params.add(runs); params.add(os); params.add(javaVersion); if (!StringUtils.isEmpty(email) && !StringUtils.isEmpty(password)) { try { params.add(email); params.add(StringUtils.digestMD5(email, password)); } catch (NoSuchAlgorithmException e) { LOG.log(Level.SEVERE, "There's no necessary digesting algorithm implemetned.", e); } } try { cl.execute("ping.ping1", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError("Can't ping the server.", e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } } /** * Stores data on server. * * @param email email of account. * @param password password of account. * @param opml opml with all necessary information. * * @return returns the ID of the user by the session ID. * * @throws ServerServiceException in case of any errors. */ public static int syncStore(String email, String password, String opml) throws ServerServiceException { int userId; byte[] opmlBytes = StringUtils.toUTF8(opml); Integer sessionId = getSessionId(email, password); XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(2); params.add(sessionId); params.add(opmlBytes); String errMessage; try { errMessage = (String)cl.execute("sync.storeInUtf8", params); userId = getUserId(sessionId); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError(MSG_UNABLE_TO_STORE_DATA, e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } if (errMessage != null && errMessage.length() > 0) { throw new ServerServiceException(errMessage); } return userId; } /** * Gets current user ID using the session ID and stores it in the cache. * * @param sessionId session ID. * * @return id. */ private static int getUserId(Integer sessionId) { // Small trick here. We would need to ask the service if only we didn't know // that sessionId is our user id. :) return sessionId == null ? -1 : sessionId; } /** * Stores preferences on the server. * * @param email email of account. * @param password password of account. * @param prefs preferences to store. * * @throws ServerServiceException in case of any errors. */ public static void syncStorePrefs(String email, String password, Map prefs) throws ServerServiceException { Integer sessionId = getSessionId(email, password); XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(2); params.add(sessionId); params.add(prefs); try { cl.execute("sync.storePrefs", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError(MSG_UNABLE_TO_STORE_DATA, e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } } /** * Restores information stored on the server. * * @param email user's account email. * @param password user's account password. * * @return OPML with set of guides for replacement. * * @throws ServerServiceException in case of errors with communication or account. */ public static String syncRestore(String email, String password) throws ServerServiceException { Integer sessionId = getSessionId(email, password); XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(2); params.add(sessionId); params.add(OPML_VERSION); byte[] opmlBytes; try { opmlBytes = (byte[])cl.execute("sync.restoreInUtf8", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError("Unable to restore data from the server.", e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } // Convert result to string return StringUtils.fromUTF8(opmlBytes); } /** * Restores preferences previously stored on the server. * * @param email user's account email. * @param password user's account password. * * @return map of stored preferences. * * @throws ServerServiceException in case of errors with communication or account. */ public static Map syncRestorePrefs(String email, String password) throws ServerServiceException { Integer sessionId = getSessionId(email, password); XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(1); params.add(sessionId); Map prefs; try { prefs = (Map)cl.execute("sync.restorePrefs", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError("Unable to restore preferences from the server.", e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } return prefs; } /** * Takes session id using account information. * * @param email user's account email. * @param password user's account password. * * @return id of account. * * @throws ServerServiceException in case of errors with communication or account. */ private static Integer getSessionId(String email, String password) throws ServerServiceException { XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(2); params.add(email); params.add(password); Integer idInt; try { idInt = (Integer)cl.execute("accounts.getSessionId", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError("Unable to get session id.", e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } int sessionId = idInt; switch (sessionId) { case ERROR_ACCOUNT_NOT_ACTIVATED: throw new ServerServiceException(Strings.error("service.you.need.active.account")); case ERROR_PASSWORD_INCORRECT: throw new ServerServiceException(Strings.error("service.account.password.is.incorrect")); case ERROR_ACCOUNT_NOT_REGISTERED: throw new ServerServiceException(Strings.error("service.account.is.not.registered")); default: } return idInt; } /** * Fetches the list of shared tags for a given URL. * * @param email user's email. * @param password user's password. * @param aLink link to get tags for. * * @return list of tags. * * @throws ServerServiceException in case of errors with communication or account. */ public static List tagsFetch(String email, String password, URL aLink) throws ServerServiceException { XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(2); params.add(getSessionId(email, password)); params.add(StringUtils.toUTF8(aLink.toString())); Vector tags; try { tags = (Vector)cl.execute("tags.getTags", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } return tags; } /** * Stores user's tags for a given URL. * * @param email user's email. * @param password user's password. * @param aLink link to assign tags to. * @param aFeed <code>TRUE</code> if link corresponds to feed. * @param aUserTags list of tags to assign. * @param aDescription link description. * @param aExtended extended description. * * @throws ServerServiceException in case of errors with communication or account. */ public static void tagsStore(String email, String password, URL aLink, boolean aFeed, String[] aUserTags, String aDescription, String aExtended) throws ServerServiceException { XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(6); params.add(getSessionId(email, password)); params.add(StringUtils.toUTF8(aLink.toString())); params.add(aFeed); params.add(StringUtils.toUTF8(StringUtils.arrayToQuotedKeywords(aUserTags))); params.add(StringUtils.toUTF8(aDescription)); params.add(StringUtils.toUTF8(aExtended)); String error; try { error = (String)cl.execute("tags.tag", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError(MessageFormat.format("Could not save user tags for: {0}", aLink), e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } if (error != null && error.length() > 0) { throw new ServerServiceException(error); } } /** * Sends request to the server to resend password on a given email. * * @param email email address of registered account. * * @throws ServerServiceException in case of any errors. */ public void requestPasswordResending(String email) throws ServerServiceException { XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(1); params.add(email); String msg; try { msg = (String)cl.execute("accounts.requestPasswordResending", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError("Could not process password resending request.", e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } if (msg.length() > 0) throw new ServerServiceException(msg); } /** * Performs discovery of feed by specified <code>ref</code>. * * @param ref reference string (URL, word or whatever). * * @return result of resolution. * * @throws ServerServiceException in case of any errors. */ public Map discover(String ref) throws ServerServiceException { XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(1); params.add(ref); Map result; try { result = (Map)cl.execute("meta.getBlogByUrlInUtf8", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError("Could not process the discovery request.", e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } // Convert community fields into client-side format Map serviceFields = (Map)result.get("communityFields"); if (serviceFields != null) { result.put("communityFields", convertToClientFields(serviceFields)); } return result; } /** * Suggests the Feed URL associated with the reference. If this reference still has no * association in service database or associated with BadBlog record then it will be * (re)associated to a newly discovered blog (if it will be discovered, of course). * * @param reference original reference. * @param suggestedFeedUrl suggested Feed Url. */ public static void metaSuggestFeedUrl(String reference, String suggestedFeedUrl) { if (LOG.isLoggable(Level.FINE)) { LOG.fine("Suggesting Feed URL: Ref=" + reference + ", Feed URL=" + suggestedFeedUrl); } XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(2); params.add(reference); params.add(suggestedFeedUrl); try { cl.execute("meta.suggestFeedUrl", params); } catch (XmlRpcException e) { // No feedback here or we can get in dead cycle } catch (Exception e) { // No feedback here or we can get in dead cycle logError(Level.WARNING, "Could not suggest feed URL.", e); } } /** * Loads community fields for the given blog. * * @param dataUrl data URL of the blog. * * @return fields map (Name:Value) where Name is String and Value is String or String[]. * * @throws ServerServiceException in case of any errors. */ public static CommunityFields metaGetCommunityFields(String dataUrl) throws ServerServiceException { if (dataUrl == null) return null; XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(1); params.add(dataUrl); Map fields; try { fields = (Map)cl.execute("meta.getCommunityFields", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError("Could not load community fields.", e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } return new CommunityFields(convertToClientFields(fields)); } /** * Saves community fields. * * @param email user account email. * @param password user account password. * @param dataUrl feed URL of the blog. * @param fields fields to save (Name:Vector(Value)). Name should be String. * * @throws ServerServiceException in case of any errors. */ public static void metaSetCommunityFields(String email, String password, String dataUrl, CommunityFields fields) throws ServerServiceException { if (fields == null || fields.size() == 0) return; XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(3); params.add(getSessionId(email, password)); params.add(dataUrl); params.add(convertToServiceFields(fields)); String message; try { message = (String)cl.execute("meta.setCommunityFields", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError("Could not save community fields.", e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } if (message.length() > 0) { throw new ServerServiceException(message); } } /** * Converts the values assigned to keys into valid values understood by client. * * @param serviceFields service fields map. * * @return result. */ static Map convertToClientFields(Map serviceFields) { if (serviceFields == null) return null; Map<String, Object> clientFields = new HashMap<String, Object>(serviceFields.size()); for (Object o : serviceFields.entrySet()) { Map.Entry entry = (Map.Entry)o; String name = (String)entry.getKey(); Object value = convertFieldToClientValue(entry.getValue()); if (value != null) clientFields.put(name, value); } return clientFields; } /** * Converts byte[] to String and Vector(byte[]) to String[]. * * @param serviceValue service value to convert. * * @return client value. */ static Object convertFieldToClientValue(Object serviceValue) { if (serviceValue == null) return null; Object clientValue = null; if (serviceValue instanceof byte[]) { clientValue = StringUtils.fromUTF8((byte[])serviceValue); } else if (serviceValue instanceof Vector) { Vector vector = (Vector)serviceValue; String[] values = new String[vector.size()]; for (int i = 0; i < vector.size(); i++) { byte[] value = (byte[])vector.get(i); values[i] = StringUtils.fromUTF8(value); } clientValue = values; } return clientValue; } /** * Converts the values assigned to keys into valid values understood by service. * * @param clientFields source fields map. * * @return result. */ static Map convertToServiceFields(Map clientFields) { if (clientFields == null) return null; Map<String, Object> serviceFields = new HashMap<String, Object>(clientFields.size()); for (Object o : clientFields.entrySet()) { Map.Entry entry = (Map.Entry)o; String name = entry.getKey().toString(); Object value = convertFieldToServiceValue(entry.getValue()); if (value != null) serviceFields.put(name, value); } return serviceFields; } /** * Converts String to byte[], arrayof(Object) to Vector(byte[]). * * @param aValue String, Vector or array. * * @return value or NULL if conversion is not possible. */ static Object convertFieldToServiceValue(Object aValue) { if (aValue == null) return null; Object value; boolean isList = false; if (aValue instanceof byte[]) { value = aValue; } else if (aValue instanceof Object[] || (isList = aValue instanceof List)) { Object[] array = !isList ? (Object[])aValue : ((List)aValue).toArray(); Vector<byte[]> vector = new Vector<byte[]>(array.length); for (Object o : array) vector.add(StringUtils.toUTF8(o.toString())); value = vector; } else { value = StringUtils.toUTF8(aValue.toString()); } return value; } /** * Sends client error report to the service. * * @param message error message. * @param details error details. */ public static void reportClientError(final String message, final String details) { XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(3); params.add(message); params.add(details); params.add(Application.getDescription().getVersion()); try { cl.execute("reports.clientError", params); } catch (Exception e) { // No feedback here or we can get in dead cycle } } /** * Sends feedback to the service. * * @param message feedback message (UTF-8 is supported). * * @return TRUE if successfully sent. */ public static boolean reportFeedback(String message) { XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(1); params.add(StringUtils.toUTF8(message)); boolean sent = false; try { cl.execute("reports.feedbackMessage", params); sent = true; } catch (XmlRpcException e) { // No feedback here or we can get in dead cycle } catch (Exception e) { // No feedback here or we can get in dead cycle logError(Level.WARNING, "Could not send feedback message.", e); } return sent; } /** * Reports exception. * * @param message message. * @param description description of exception. * @param exception exception to report. */ public static void reportClientError(String message, String description, Throwable exception) { StringBuffer details = new StringBuffer(); if (description != null) details.append(description).append("\n\n"); if (exception != null) { dumpThrowable(exception, details); } else details.append("No stack dump."); reportClientError(message, details.toString()); } /** * Recursively dumps throwable. * * @param exception exception. * @param details details buffer. */ static void dumpThrowable(Throwable exception, StringBuffer details) { details.append(exception.toString()); for (int i = 0; i < exception.getStackTrace().length; i++) { StackTraceElement element = exception.getStackTrace()[i]; details.append("\n\t").append(element); } Throwable cause = exception.getCause(); if (cause != null) { details.append("\nCaused by:\n"); dumpThrowable(cause, details); } } /** * Asks service for available updates. * * @param aCurrentVersion current version. * * @return service XML response or empty packet if no updates available. * * @throws ServerServiceException in case of any errors. */ public static String checkForUpdates(String aCurrentVersion) throws ServerServiceException { // We use this property to test new release detection // When set the service provides the list of all updates since // current version as if the latest version were final. boolean productionOnly = System.getProperty("service.checkupdates.allversions") == null; XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(2); params.add(aCurrentVersion); params.add(productionOnly); String response; try { response = (String)cl.execute("updates.checkForUpdates", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError("Could not check for updates.", e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } return response; } /** * Questions the service for the list of available forums. * * @return forums list. * * @throws ServerServiceException in case of any errors. */ public static Map forumGetForums() throws ServerServiceException { XmlRpcHandler cl = getClient(); Vector params = new Vector(0); Map response; try { response = (Map)cl.execute("forum.getForums", params); } catch (XmlRpcException e) { throw new ServerServiceException(MSG_COM_PROBLEM, e); } catch (Exception e) { logError("Could not fetch forums list.", e); throw new ServerServiceException(MSG_PROCESSING_ERROR, e); } return response; } /** * Posts message to the forum. * * @param aName name of the author. * @param aEmail email address of the author. * @param aForumId ID of selected forum. * @param aSubject subject of the message. * @param aMessage message text. * * @return <code>TRUE</code> if the message has been delivered. */ public static boolean forumPost(String aName, String aEmail, int aForumId, String aSubject, String aMessage) { XmlRpcHandler cl = getClient(); Vector<Object> params = new Vector<Object>(5); params.add(StringUtils.toUTF8(aName)); params.add(StringUtils.toUTF8(aEmail)); params.add(aForumId); params.add(StringUtils.toUTF8(aSubject)); params.add(StringUtils.toUTF8(aMessage)); boolean sent = false; try { cl.execute("forum.post", params); sent = true; } catch (XmlRpcException e) { // No feedback here or we can get in dead cycle } catch (Exception e) { // No feedback here or we can get in dead cycle logError(Level.WARNING, "Could not send forum message.", e); } return sent; } /** * Returns starting points OPML URL. * * @return starting points OPML URL. */ public static URL getStartingPointsURL() { return getOPMLURLbyKey("opml.starting.points.url"); } /** * Returns experts OPML URL. * * @return experts OPML URL. */ public static URL getExpertsURL() { return getOPMLURLbyKey("opml.experts.url"); } /** * Loads OPML URLs list if necessary from the service. * * @param urlKey key to get URL. * * @return URL or NULL if opml URLs aren't loaded or the key is missing. */ private static URL getOPMLURLbyKey(String urlKey) { URL url = getSystemURLProperty(urlKey); if (url == null) { synchronized (opmlURLsLock) { loadOPMLURLs(); // There can be a problem with the service not responding. if (opmlURLs != null) { String urlS = StringUtils.fromUTF8((byte[])opmlURLs.get(urlKey)); try { if (urlS == null) url = null; else url = new URL(urlS); } catch (MalformedURLException e) { url = null; } } } } return url; } /** * Checks if there's a system property for the given key. * * @param key key. * * @return URL. */ private static URL getSystemURLProperty(String key) { URL url = null; String val = System.getProperty(key); if (val != null) { try { url = new URL(val); } catch (MalformedURLException e) { LOG.log(Level.WARNING, MessageFormat.format( "Invalid overriden URL for key {0}", key), e); } } return url; } /** * Loads the list of OPML URLs from the service. */ private static void loadOPMLURLs() { if (opmlURLs == null) { XmlRpcHandler cl = getClient(); Vector params = new Vector(0); try { opmlURLs = (Map)cl.execute("meta.getOPMLURLs", params); } catch (Exception e) { logError("Could not retrieve OPML URLs.", e); } } } // ------------------------------------------------------------------------ // Plan and Feature related calls to the service // ------------------------------------------------------------------------ /** * Returns the plan hash to compare with the current and decided if it's time * to update the features list or no. * * @param email user email. * @param password user password. * * @return the hash. */ public static String getPlanHash(String email, String password) { XmlRpcHandler cl = getClient(); Vector<String> params = new Vector<String>(2); params.add(email); params.add(password); String hash = null; try { hash = (String)cl.execute("plans.getHash", params); } catch (Exception e) { // No feedback here or we can get in dead cycle LOG.log(Level.WARNING, "Failed to get plan hash.", e); } return hash; } public static Map<String, String> getPlanFeatures(String email, String password) { XmlRpcHandler cl = getClient(); Vector<String> params = new Vector<String>(2); params.add(email); params.add(password); Map<String, String> features = null; try { features = (Map<String, String>)cl.execute("plans.getFeatures", params); } catch (Exception e) { // No feedback here or we can get in dead cycle logError(Level.WARNING, "Failed to get plan features.", e); } return features; } private static void logError(String msg, Exception ex) { logError(Level.SEVERE, msg, ex); } private static void logError(Level level, String msg, Exception ex) { level = ex instanceof IOException ? Level.FINE : level; LOG.log(level, msg, ex); } }