fr.dutra.confluence2wordpress.wp.WordpressClient.java Source code

Java tutorial

Introduction

Here is the source code for fr.dutra.confluence2wordpress.wp.WordpressClient.java

Source

/**
 * Copyright 2011-2012 Alexandre Dutra
 *
 *    Licensed 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.
 */
package fr.dutra.confluence2wordpress.wp;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.TimeZone;
import java.util.Vector;

import org.apache.commons.lang.StringUtils;
import org.apache.xmlrpc.XmlRpcException;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import fr.dutra.confluence2wordpress.util.CollectionUtils;
import fr.dutra.confluence2wordpress.xmlrpc.CommonsXmlRpcTransportFactory;
import fr.dutra.confluence2wordpress.xmlrpc.XmlRpcClient;

/**
 * 
 * @see "http://codex.wordpress.org/XML-RPC_wp"
 * 
 * @author Alexandre Dutra
 * 
 */
public class WordpressClient {

    private static final String CREATE_POST_METHOD_NAME = "c2w.newPost";

    private static final String UPDATE_POST_METHOD_NAME = "c2w.editPost";

    private static final String FIND_POST_BY_ID_METHOD_NAME = "metaWeblog.getPost"; // "blogger.getPost";

    private static final String GET_USERS_METHOD_NAME = "c2w.getAuthors"; // "wp.getAuthors";

    private static final String GET_CATEGORIES_METHOD_NAME = "wp.getCategories";

    private static final String GET_TAGS_METHOD_NAME = "wp.getTags";

    private static final String UPLOAD_FILE_METHOD_NAME = "c2w.uploadFile"; // "wp.uploadFile";

    private static final String FIND_PAGE_ID_BY_SLUG_METHOD_NAME = "c2w.findPageIdBySlug";

    private static final String PING_METHOD_NAME = "c2w.ping";

    /**
     * Very, VERY old version of the lib bundled with Confluence. Does not even know about Lists and Maps, only Vectors and Hashtables.
     * Has bugs related to thread-safety.
     */
    private final XmlRpcClient client;

    private final WordpressConnection wordpressConnection;

    private final ExecutorService pool;

    public WordpressClient(WordpressConnection wordpressConnection) {
        this.wordpressConnection = wordpressConnection;
        CommonsXmlRpcTransportFactory factory;
        URL url = wordpressConnection.getUrl();
        if (this.wordpressConnection.getProxyHost() != null && this.wordpressConnection.getProxyPort() != null) {
            factory = new CommonsXmlRpcTransportFactory(url, wordpressConnection.getProxyHost(),
                    wordpressConnection.getProxyPort(), wordpressConnection.getMaxConnections());
        } else {
            factory = new CommonsXmlRpcTransportFactory(url, wordpressConnection.getMaxConnections());
        }
        this.client = new XmlRpcClient(url, factory);
        this.client.setMaxThreads(wordpressConnection.getMaxConnections());
        this.pool = Executors.newFixedThreadPool(wordpressConnection.getMaxConnections(),
                new ThreadFactoryBuilder().setDaemon(true).setNameFormat("wp-client-%s").build());
    }

    public synchronized void destroy() {
        this.pool.shutdownNow();
    }

    public String ping(String text) throws WordpressXmlRpcException {
        Vector<Object> params = new Vector<Object>();
        params.add(wordpressConnection.getBlogId());
        params.add(wordpressConnection.getUsername());
        params.add(wordpressConnection.getPassword());
        params.add(text);
        return invoke(PING_METHOD_NAME, params);
    }

    /**
     * @see "http://codex.wordpress.org/XML-RPC_wp#wp.getAuthors"
     * @return the list of {@link WordpressUser}s of the blog.
     * @throws WordpressXmlRpcException
     */
    public Future<List<WordpressUser>> getUsers() {
        return pool.submit(new Callable<List<WordpressUser>>() {

            @Override
            public List<WordpressUser> call() throws Exception {

                Vector<Object> params = new Vector<Object>();

                params.add(wordpressConnection.getBlogId());
                params.add(wordpressConnection.getUsername());
                params.add(wordpressConnection.getPassword());

                List<Map<String, Object>> rows = invoke(GET_USERS_METHOD_NAME, params);

                List<WordpressUser> users = new ArrayList<WordpressUser>(rows.size());
                for (Map<String, Object> row : rows) {
                    WordpressUser user = new WordpressUser();
                    user.setId(Integer.valueOf(row.get("user_id").toString()));
                    user.setLogin(row.get("user_login").toString());
                    user.setDisplayName((String) row.get("display_name"));
                    user.setFirstName((String) row.get("first_name"));
                    user.setLastName((String) row.get("last_name"));
                    user.setNiceName((String) row.get("user_nicename"));
                    if (row.get("user_level") != null && StringUtils.isNotEmpty(row.get("user_level").toString())) {
                        user.setLevel(Integer.valueOf(row.get("user_level").toString()));
                    }
                    users.add(user);
                }

                return users;
            }

        });
    }

    /**
     * @see "http://codex.wordpress.org/XML-RPC_wp#wp.getCategories"
     * @return the list of {@link WordpressCategory}s of the blog.
     * @throws WordpressXmlRpcException
     */
    public Future<List<WordpressCategory>> getCategories() {
        return pool.submit(new Callable<List<WordpressCategory>>() {

            @Override
            public List<WordpressCategory> call() throws Exception {
                Vector<Object> params = new Vector<Object>();

                params.add(wordpressConnection.getBlogId());
                params.add(wordpressConnection.getUsername());
                params.add(wordpressConnection.getPassword());

                List<Map<String, Object>> rows = invoke(GET_CATEGORIES_METHOD_NAME, params);

                List<WordpressCategory> categories = new ArrayList<WordpressCategory>(rows.size());
                for (Map<String, Object> row : rows) {
                    WordpressCategory category = new WordpressCategory();
                    category.setId(Integer.valueOf(row.get("categoryId").toString()));
                    category.setParentId(Integer.valueOf(row.get("parentId").toString()));
                    category.setDescription((String) row.get("description"));
                    category.setCategoryName((String) row.get("categoryName"));
                    category.setHtmlUrl((String) row.get("htmlUrl"));
                    category.setRssUrl((String) row.get("rssUrl"));
                    categories.add(category);
                }

                return categories;
            }
        });
    }

    /**
     * @see "http://codex.wordpress.org/XML-RPC_wp#wp.getTags"
     * @return the list of {@link WordpressCategory}s of the blog.
     * @throws WordpressXmlRpcException
     */
    public Future<List<WordpressTag>> getTags() {
        return pool.submit(new Callable<List<WordpressTag>>() {

            @Override
            public List<WordpressTag> call() throws Exception {
                Vector<Object> params = new Vector<Object>();

                params.add(wordpressConnection.getBlogId());
                params.add(wordpressConnection.getUsername());
                params.add(wordpressConnection.getPassword());

                List<Map<String, Object>> rows = invoke(GET_TAGS_METHOD_NAME, params);

                List<WordpressTag> tags = new ArrayList<WordpressTag>(rows.size());
                for (Map<String, Object> row : rows) {
                    WordpressTag tag = new WordpressTag();
                    tag.setId(Integer.valueOf(row.get("tag_id").toString()));
                    tag.setName((String) row.get("name"));
                    tag.setCount(Integer.valueOf(row.get("count").toString()));
                    tag.setSlug((String) row.get("slug"));
                    tag.setHtmlUrl((String) row.get("html_url"));
                    tag.setRssUrl((String) row.get("rss_url"));
                    tags.add(tag);
                }

                return tags;
            }
        });
    }

    /**
     * @see "http://xmlrpc.free-conversant.com/docs/bloggerAPI#getPost"
     * @see "http://joysofprogramming.com/blogger-getpost/"
     * @see "http://stackoverflow.com/questions/3083039/how-can-i-get-a-post-with-xml-rpc-in-wordpress"
     * 
     * @param postId
     * @return post
     * @throws WordpressXmlRpcException
     */
    public WordpressPost findPostById(int postId) throws WordpressXmlRpcException {
        Vector<Object> params = new Vector<Object>();
        // params.add(wordpressConnection.getBlogId());
        params.add(postId);
        params.add(wordpressConnection.getUsername());
        params.add(wordpressConnection.getPassword());
        Map<String, Object> map = invoke(FIND_POST_BY_ID_METHOD_NAME, params);
        return convertToPost(map);
    }

    /**
     * 
     * @param postSlug
     * @return postId
     * @throws WordpressXmlRpcException
     */
    public Integer findPageIdBySlug(String postSlug) throws WordpressXmlRpcException {
        Vector<Object> params = new Vector<Object>();
        params.add(wordpressConnection.getBlogId());
        params.add(wordpressConnection.getUsername());
        params.add(wordpressConnection.getPassword());
        params.add(postSlug);
        Integer result = (Integer) invoke(FIND_PAGE_ID_BY_SLUG_METHOD_NAME, params);
        if (result != null && result == 0) {
            return null;
        }
        return result;
    }

    /**
     * 
     * @see "http://www.xmlrpc.com/metaWeblogApi" 
     * @see "http://mindsharestrategy.com/wp-xmlrpc-metaweblog/" 
     * @see "http://www.perkiset.org/forum/perl/metaweblognewpost_to_wordpress_blog_xmlrpcphp-t1307.0.html"
     * @see "http://joysofprogramming.com/wordpress-xmlrpc-metaweblog-newpost/"
     * @see "http://life.mysiteonline.org/archives/161-Automatic-Post-Creation-with-Wordpress,-PHP,-and-XML-RPC.html"
     * 
     * @param post
     *            to create
     * @return the created post
     * @throws WordpressXmlRpcException
     */
    public WordpressPost post(WordpressPost post) throws WordpressXmlRpcException {

        Vector<Object> params = new Vector<Object>();

        params.add(wordpressConnection.getBlogId());
        params.add(wordpressConnection.getUsername());
        params.add(wordpressConnection.getPassword());
        if (post.getPostId() != null) {
            params.add(post.getPostId());
        }

        Hashtable<String, Object> map = new Hashtable<String, Object>();
        map.put("title", post.getTitle());
        map.put("description", post.getBody());
        map.put("wp_author_id", post.getAuthorId());
        map.put("wp_slug", post.getPostSlug());
        Date dateCreated = post.getDateCreated();
        if (dateCreated != null) {
            map.put("dateCreated", convertToNaiveUTC(dateCreated));
        }
        if (post.getCategoryNames() != null) {
            map.put("categories", new Vector<String>(post.getCategoryNames()));
        } else {
            map.put("categories", new Vector<String>());
        }
        if (post.getTagNames() != null) {
            map.put("mt_keywords", new Vector<String>(post.getTagNames()));
        } else {
            map.put("mt_keywords", new Vector<String>());
        }
        params.add(map);

        // to publish ?
        params.add(!post.isDraft());

        String methodName;
        if (post.getPostId() == null) {
            methodName = CREATE_POST_METHOD_NAME;
        } else {
            methodName = UPDATE_POST_METHOD_NAME;
        }
        Map<String, Object> ret = invoke(methodName, params);
        return convertToPost(ret);
    }

    /**
     * @param file
     * @return
     * @throws WordpressXmlRpcException
     */
    public Future<WordpressFile> uploadFile(final WordpressFile file) {

        return pool.submit(new Callable<WordpressFile>() {

            @Override
            public WordpressFile call() throws Exception {

                Vector<Object> params = new Vector<Object>();
                params.add(wordpressConnection.getBlogId());
                params.add(wordpressConnection.getUsername());
                params.add(wordpressConnection.getPassword());

                Hashtable<String, Object> map = new Hashtable<String, Object>();
                map.put("name", file.getFileName());
                map.put("type", file.getMimeType());
                map.put("bits", file.getData());
                // see http://core.trac.wordpress.org/ticket/17604
                map.put("overwrite", true);
                params.add(map);

                Map<String, ?> response = invoke(UPLOAD_FILE_METHOD_NAME, params);

                file.setFileName((String) response.get("file"));
                file.setMimeType((String) response.get("type"));
                file.setUrl((String) response.get("url"));

                String baseUrl = StringUtils.substringBeforeLast(file.getUrl(), "/");

                if (response.get("meta") != null && response.get("meta") instanceof Map) {
                    @SuppressWarnings("unchecked")
                    Map<String, ?> meta = (Map<String, ?>) response.get("meta");
                    if (meta != null) {
                        if (meta.containsKey("height")) {
                            file.setHeight((Integer) meta.get("height"));
                        }
                        if (meta.containsKey("width")) {
                            file.setWidth((Integer) meta.get("width"));
                        }
                        @SuppressWarnings("unchecked")
                        Map<String, ?> sizes = (Map<String, ?>) meta.get("sizes");
                        if (sizes != null) {
                            for (Entry<String, ?> entry : sizes.entrySet()) {
                                @SuppressWarnings("unchecked")
                                Map<String, ?> value = (Map<String, ?>) entry.getValue();
                                WordpressFile alternative = createAlternative(value, file.getMimeType(), baseUrl);
                                file.putAlternative(entry.getKey(), alternative);
                            }
                        }
                    }
                }
                return file;
            }
        });

    }

    private WordpressFile createAlternative(Map<String, ?> value, String mimeType, String baseUrl) {
        WordpressFile alternative = new WordpressFile((String) value.get("file"));
        alternative.setAlternative(true);
        alternative.setMimeType(mimeType);
        String fileName = (String) value.get("file");
        alternative.setFileName(fileName);
        alternative.setUrl(baseUrl + "/" + fileName);
        if (value.containsKey("height")) {
            alternative.setHeight((Integer) value.get("height"));
        }
        if (value.containsKey("width")) {
            alternative.setWidth((Integer) value.get("width"));
        }
        return alternative;
    }

    private WordpressPost convertToPost(Map<String, Object> map) {

        WordpressPost post = new WordpressPost();

        Object postId = map.get("postid");
        post.setPostId(Integer.valueOf(postId.toString()));
        post.setDraft(false);

        Object authorId = map.get("wp_author_id");
        post.setAuthorId(authorId == null ? null : Integer.valueOf(authorId.toString()));

        Date dateUtc = (Date) map.get("dateCreated");
        post.setDateCreated(convertFromNaiveUTC(dateUtc));

        StringBuilder body = new StringBuilder();
        if (map.get("description") != null) {
            body.append((String) map.get("description"));
        }
        if (map.get("mt_text_more") != null) {
            //tricky: if the post is saved on wordpress side,
            //even without modification, wordpress will append an "\n" before the "more" tag
            //but if the post has never been edited on wordpress,
            //then the "\n" is missing
            if (body.length() > 0 && body.charAt(body.length() - 1) != '\n') {
                body.append("\n");
            }
            body.append("<!--more-->");
            body.append((String) map.get("mt_text_more"));
        }
        post.setBody(body.toString());

        String title = (String) map.get("title");
        post.setTitle(title);

        post.setDraft(!"publish".equals(map.get("post_status")));

        @SuppressWarnings("unchecked")
        List<String> categoryNames = (List<String>) map.get("categories");
        post.setCategoryNames(new ArrayList<String>(categoryNames));

        //optional field
        List<String> tagNames = CollectionUtils.split((String) map.get("mt_keywords"), ",");
        if (tagNames != null) {
            post.setTagNames(new ArrayList<String>(tagNames));
        }

        String slug = (String) map.get("wp_slug");
        post.setPostSlug(slug);

        String permaLink = (String) map.get("permaLink");
        post.setLink(permaLink);

        return post;
    }

    /**
     * Date values are sent with no time zone information;
     * Wordpress assumes they are in UTC.
     * Hence the need to convert to a fake date bearing the correct information,
     * but in a wrong time zone.
     * @param date
     * @return
     */
    private Date convertToNaiveUTC(Date date) {
        TimeZone zone = TimeZone.getDefault();
        long time = date.getTime();
        return new Date(time - zone.getOffset(time));
    }

    /**
     * Date values are received with no time zone information;
     * Wordpress assumes they are in UTC.
     * Hence the need to convert from a fake date bearing the correct information,
     * but in a wrong time zone.
     * @param date
     * @return
     */
    private Date convertFromNaiveUTC(Date date) {
        TimeZone zone = TimeZone.getDefault();
        long time = date.getTime();
        return new Date(time + zone.getOffset(time));
    }

    @SuppressWarnings("unchecked")
    private <T> T invoke(String methodName, Vector<Object> params) throws WordpressXmlRpcException {
        try {
            return (T) client.execute(methodName, params);
        } catch (XmlRpcException e) {
            throw new WordpressXmlRpcException("Error invoking method: " + methodName + ": " + e.getMessage(), e);
        } catch (IOException e) {
            throw new WordpressXmlRpcException("Error invoking method: " + methodName + ": " + e.getMessage(), e);
        }
    }

}