All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaushell.shaarlijavaapi.ShaarliClient Maven / Gradle / Ivy

There is a newer version: 1.0.6
Show newest version
/*
 * Copyright (C) 2013 Fabien Vauchelles (fabien_AT_vauchelles_DOT_com).
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3, 29 June 2007, of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */

package com.vaushell.shaarlijavaapi;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Shaarli client. Now we have a JAVA access to Sebsauvage Shaarli (see http://sebsauvage.net/wiki/doku.php?id=php:shaarli)
 *
 * @author Fabien Vauchelles (fabien_AT_vauchelles_DOT_com)
 */
public class ShaarliClient
    implements AutoCloseable
{
    // PUBLIC
    /**
     * Construct the DAO with a specific http client.
     *
     * @param client Specific HTTP client
     * @param endpoint Shaarli endpoint (like http://fabien.vauchelles.com/~fabien/shaarli)
     */
    public ShaarliClient( final HttpClient client ,
                          final String endpoint )
    {
        if ( client == null || endpoint == null )
        {
            throw new IllegalArgumentException();
        }

        this.cm = null;
        this.client = client;
        this.endpoint = cleanEnding( endpoint );
    }

    /**
     * Construct the DAO.
     *
     * @param endpoint Shaarli endpoint (like http://fabien.vauchelles.com/~fabien/shaarli)
     */
    public ShaarliClient( final String endpoint )
    {
        if ( endpoint == null )
        {
            throw new IllegalArgumentException();
        }

        this.endpoint = cleanEnding( endpoint );

        final HttpParams params = new BasicHttpParams();
        HttpProtocolParams.setUserAgent( params ,
                                         "Mozilla/5.0 (Windows NT 5.1; rv:15.0) Gecko/20100101 Firefox/15.0.1" );

        final SchemeRegistry sr = new SchemeRegistry();

        sr.register( new Scheme( "http" ,
                                 80 ,
                                 PlainSocketFactory.getSocketFactory() ) );

        this.cm = new PoolingClientConnectionManager( sr );
        this.cm.setMaxTotal( 1000 );

        final DefaultHttpClient lClient = new DefaultHttpClient( cm ,
                                                                 params );
        lClient.setCookieStore( new BasicCookieStore() );

        this.client = lClient;
    }

    /**
     * Return Shaarli endpoint.
     *
     * @return endpoint
     */
    public String getEndpoint()
    {
        return endpoint;
    }

    /**
     * Login.
     *
     * @param login Login (must not be empty)
     * @param password Password (must not be empty)
     * @return true if logged or false otherwise
     */
    public boolean login( final String login ,
                          final String password )
    {
        if ( login == null || password == null )
        {
            throw new IllegalArgumentException();
        }

        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug( "[" + getClass().getSimpleName() + "] login() : login=" + login );
        }

        final String token;
        try
        {
            token = getToken( endpoint + "/?do=login" );
        }
        catch( final IOException ex )
        {
            LOGGER.error( "Cannot retrieve login token" ,
                          ex );
            return false;
        }

        try
        {
            loginImpl( login ,
                       password ,
                       token );
        }
        catch( final IOException ex )
        {
            LOGGER.error( "Cannot retrieve login token" ,
                          ex );
            return false;
        }

        setLinksByPage( MAX_LINKS_BY_PAGE );

        return true;
    }

    /**
     * Create a link.
     *
     * @param url Link's URL
     * @param title Link's title
     * @param description Link's description
     * @param tags tags (set, no duplicate please)
     * @param restricted Is the link private ?
     * @return generated id
     */
    public String createOrUpdateLink( final String url ,
                                      final String title ,
                                      final String description ,
                                      final Set tags ,
                                      final boolean restricted )
    {
        return createOrUpdateLink( null ,
                                   url ,
                                   title ,
                                   description ,
                                   tags ,
                                   restricted );
    }

    /**
     * Create or modify a link (to modify, don't forgot the ID !).
     *
     * @param id Link's ID. You can enforce one or let it be null (not the permalink id. Don't be confuse!)
     * @param url Link's URL
     * @param title Link's title
     * @param description Link's description
     * @param tags Links tags (set, no duplicate please)
     * @param restricted Is the link private ?
     * @return id Link's ID
     */
    public String createOrUpdateLink( final String id ,
                                      final String url ,
                                      final String title ,
                                      final String description ,
                                      final Set tags ,
                                      final boolean restricted )
    {
        if ( url == null || title == null )
        {
            throw new IllegalArgumentException();
        }

        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug(
                "[" + getClass().getSimpleName() + "] createOrUpdateLink() : id=" + id + " / url=" + url + " / title=" + title + " / description=" + description + " / restricted=" + restricted );
        }

        final String token;
        try
        {
            token = getToken( endpoint + "/?post" );
        }
        catch( final IOException ex )
        {
            LOGGER.error( "Cannot retrieve post token" ,
                          ex );
            return null;
        }

        HttpEntity responseEntity = null;
        try
        {
            // Exec request
            final HttpPost post = new HttpPost( endpoint + "/?post=" + URLEncoder.encode( url ,
                                                                                          "UTF-8" ) );

            final List nvps = new ArrayList<>();

            String returnID;
            if ( id == null )
            {
                returnID = new SimpleDateFormat( "yyyyMMdd_HHmmss" ,
                                                 Locale.ENGLISH ).format( new Date() );
            }
            else
            {
                returnID = id;
            }
            nvps.add( new BasicNameValuePair( "lf_linkdate" ,
                                              returnID ) );

            nvps.add( new BasicNameValuePair( "lf_url" ,
                                              url ) );

            nvps.add( new BasicNameValuePair( "lf_title" ,
                                              title ) );

            if ( description != null )
            {
                nvps.add( new BasicNameValuePair( "lf_description" ,
                                                  description ) );
            }

            if ( restricted )
            {
                nvps.add( new BasicNameValuePair( "lf_private" ,
                                                  "true" ) );
            }

            final StringBuilder sbTags = new StringBuilder();
            if ( tags != null )
            {
                for ( final String tag : tags )
                {
                    if ( sbTags.length() > 0 )
                    {
                        sbTags.append( ' ' );
                    }

                    sbTags.append( tag );
                }
            }

            if ( sbTags.length() > 0 )
            {
                nvps.add( new BasicNameValuePair( "lf_tags" ,
                                                  sbTags.toString() ) );
            }

            nvps.add( new BasicNameValuePair( "save_edit" ,
                                              "Save" ) );
            nvps.add( new BasicNameValuePair( "token" ,
                                              token ) );
            nvps.add( new BasicNameValuePair( "returnurl" ,
                                              endpoint ) );

            post.setEntity( new UrlEncodedFormEntity( nvps ,
                                                      "UTF-8" ) );

            final HttpResponse response = client.execute( post );
            responseEntity = response.getEntity();

            final StatusLine sl = response.getStatusLine();
            if ( sl.getStatusCode() != 302 )
            {
                try( final InputStream is = responseEntity.getContent() )
                {
                    throw new IOException( IOUtils.toString( is ) );
                }
            }

            return returnID;
        }
        catch( final IOException ex )
        {
            LOGGER.error( "Cannot post" ,
                          ex );
            return null;
        }
        finally
        {
            if ( responseEntity != null )
            {
                try
                {
                    EntityUtils.consume( responseEntity );
                }
                catch( final IOException ex )
                {
                    throw new RuntimeException( ex );
                }
            }
        }
    }

    /**
     * Delete a link.
     *
     * @param id Link's id (not the permalink id. Don't be confuse!)
     * @return deleted or not
     */
    public boolean delete( final String id )
    {
        if ( id == null )
        {
            throw new IllegalArgumentException();
        }

        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug(
                "[" + getClass().getSimpleName() + "] delete() : id=" + id );
        }

        final String token;
        try
        {
            token = getToken( endpoint + "/?post" );
        }
        catch( final IOException ex )
        {
            LOGGER.error( "Cannot retrieve post token" ,
                          ex );
            return false;
        }

        HttpEntity responseEntity = null;
        try
        {
            // Exec request
            final HttpPost post = new HttpPost( endpoint + "/?post" );

            final List nvps = new ArrayList<>();

            nvps.add( new BasicNameValuePair( "lf_linkdate" ,
                                              id ) );

            nvps.add( new BasicNameValuePair( "delete_link" ,
                                              "" ) );

            nvps.add( new BasicNameValuePair( "token" ,
                                              token ) );

            post.setEntity( new UrlEncodedFormEntity( nvps ,
                                                      "UTF-8" ) );

            final HttpResponse response = client.execute( post );
            responseEntity = response.getEntity();

            final StatusLine sl = response.getStatusLine();
            if ( sl.getStatusCode() != 302 )
            {
                try( final InputStream is = responseEntity.getContent() )
                {
                    throw new IOException( IOUtils.toString( is ) );
                }
            }

            return true;
        }
        catch( final IOException ex )
        {
            LOGGER.error( "Cannot delete" ,
                          ex );
            return false;
        }
        finally
        {
            if ( responseEntity != null )
            {
                try
                {
                    EntityUtils.consume( responseEntity );
                }
                catch( final IOException ex )
                {
                    throw new RuntimeException( ex );
                }
            }
        }
    }

    /**
     * Get all used tags.
     *
     * @return key/value with tag name and tag count
     */
    public Map getTags()
    {
        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug(
                "[" + getClass().getSimpleName() + "] getTags()" );
        }

        HttpEntity responseEntity = null;
        try
        {
            // Exec request
            final String execURL = endpoint + "/?do=tagcloud";
            final HttpGet get = new HttpGet( execURL );

            final HttpResponse response = client.execute( get );
            responseEntity = response.getEntity();

            final StatusLine sl = response.getStatusLine();
            if ( sl.getStatusCode() == 200 )
            {
                try( final InputStream is = responseEntity.getContent() )
                {
                    final Map tags = new TreeMap<>();

                    final Document doc = Jsoup.parse( is ,
                                                      "UTF-8" ,
                                                      execURL );

                    final Elements elts = doc.select( "#cloudtag *" );
                    final Iterator itElts = elts.iterator();
                    while ( itElts.hasNext() )
                    {
                        final int count = Integer.parseInt( itElts.next().text() );
                        final String name = itElts.next().text();

                        tags.put( name ,
                                  count );
                    }

                    return tags;
                }
            }
            else
            {
                throw new IOException( sl.getReasonPhrase() );
            }
        }
        catch( final IOException ex )
        {
            LOGGER.error( "Cannot retrieve tags" ,
                          ex );
            return null;
        }
        finally
        {
            if ( responseEntity != null )
            {
                try
                {
                    EntityUtils.consume( responseEntity );
                }
                catch( final IOException ex )
                {
                    throw new RuntimeException( ex );
                }
            }
        }
    }

    /**
     * Iterator to search all links in shaarli.
     *
     * @return the iterator
     */
    public Iterator searchAllIterator()
    {
        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug(
                "[" + getClass().getSimpleName() + "] searchAllIterator()" );
        }

        return iterator( null );
    }

    /**
     * Get all page's links.
     *
     * @param page Page number (>=1)
     * @return List of links
     */
    public List searchAll( final int page )
    {
        if ( page < 1 )
        {
            throw new IllegalArgumentException( "page must be greater or equals to 1" );
        }

        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug(
                "[" + getClass().getSimpleName() + "] searchAll() : page=" + page );
        }

        final String execURL = endpoint + "/?page=" + page;

        return parseLinks( execURL );
    }

    /**
     * Iterator to search links, filter by a term.
     *
     * @param term Term (must not be null)
     * @return an iterator
     */
    public Iterator searchTermIterator( final String term )
    {
        if ( term == null )
        {
            throw new IllegalArgumentException();
        }

        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug(
                "[" + getClass().getSimpleName() + "] searchTermIterator() : term=" + term );
        }

        try
        {
            return iterator( "searchterm=" + URLEncoder.encode( term ,
                                                                "UTF-8" ) );
        }
        catch( final UnsupportedEncodingException ex )
        {
            throw new RuntimeException( ex );
        }
    }

    /**
     * Get all page's links, filter by a term.
     *
     * @param page Page number (>=1)
     * @param term Tags array
     * @return List of links
     */
    public List searchTerm( final int page ,
                                         final String term )
    {
        if ( term == null )
        {
            throw new IllegalArgumentException();
        }

        if ( page < 1 )
        {
            throw new IllegalArgumentException( "page must be greater or equals to 1" );
        }

        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug(
                "[" + getClass().getSimpleName() + "] searchTerm() : page=" + page + " / term=" + term );
        }

        try
        {
            final String execURL = endpoint + "/?page=" + page + "&searchterm=" + URLEncoder.encode( term ,
                                                                                                     "UTF-8" );

            return parseLinks( execURL );
        }
        catch( final UnsupportedEncodingException ex )
        {
            throw new RuntimeException( ex );
        }
    }

    /**
     * Iterator to search links, filter by tags.
     *
     * @param tags Tags array
     * @return an iterator
     */
    public Iterator searchTagsIterator( final String... tags )
    {
        if ( tags == null || tags.length <= 0 )
        {
            throw new IllegalArgumentException();
        }

        final StringBuilder sb = new StringBuilder();
        for ( final String tag : tags )
        {
            if ( sb.length() > 0 )
            {
                sb.append( ' ' );
            }

            sb.append( tag );
        }

        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug(
                "[" + getClass().getSimpleName() + "] searchTagsIterator() : tags=" + sb.toString() );
        }

        try
        {
            return iterator( "searchtags=" + URLEncoder.encode( sb.toString() ,
                                                                "UTF-8" ) );
        }
        catch( final UnsupportedEncodingException ex )
        {
            throw new RuntimeException( ex );
        }
    }

    /**
     * Get all page's links, filter by tags.
     *
     * @param page Page number (>=1)
     * @param tags Tags array
     * @return List of links
     */
    public List searchTags( final int page ,
                                         final String... tags )
    {
        if ( page < 1 )
        {
            throw new IllegalArgumentException( "page must be greater or equals to 1" );
        }

        final StringBuilder sb = new StringBuilder();
        for ( final String tag : tags )
        {
            if ( sb.length() > 0 )
            {
                sb.append( ' ' );
            }

            sb.append( tag );
        }

        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug(
                "[" + getClass().getSimpleName() + "] searchTags() : page=" + page + " / tags=" + sb.toString() );
        }

        try
        {
            final String execURL = endpoint + "/?page=" + page + "searchtags=" + URLEncoder.encode( sb.toString() ,
                                                                                                    "UTF-8" );

            return parseLinks( execURL );
        }
        catch( final UnsupportedEncodingException ex )
        {
            throw new RuntimeException( ex );
        }
    }

    /**
     * Set the number of links by page.
     *
     * @param count Number of links
     */
    public void setLinksByPage( final int count )
    {
        if ( count <= 0 )
        {
            throw new IllegalArgumentException();
        }

        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug(
                "[" + getClass().getSimpleName() + "] setLinksByPage() : count=" + count );
        }

        HttpEntity responseEntity = null;
        try
        {
            // Exec request
            final String execURL = endpoint + "/?linksperpage=" + count;
            final HttpGet get = new HttpGet( execURL );

            final HttpResponse response = client.execute( get );
            responseEntity = response.getEntity();

            final StatusLine sl = response.getStatusLine();
            if ( sl.getStatusCode() != 200 )
            {
                throw new IOException( sl.getReasonPhrase() );
            }
        }
        catch( final IOException ex )
        {
            LOGGER.error( "Cannot set links per page" ,
                          ex );
        }
        finally
        {
            if ( responseEntity != null )
            {
                try
                {
                    EntityUtils.consume( responseEntity );
                }
                catch( final IOException ex )
                {
                    throw new RuntimeException( ex );
                }
            }
        }
    }

    /**
     * Close the Shaarli connection.
     */
    @Override
    public void close()
    {
        if ( cm != null )
        {
            cm.shutdown();
        }
    }
    // PRIVATE
    private static final int MAX_LINKS_BY_PAGE = 100;
    private static final Logger LOGGER = LoggerFactory.getLogger( ShaarliClient.class );
    private final HttpClient client;
    private final PoolingClientConnectionManager cm;
    private final String endpoint;

    private String getToken( final String execURL )
        throws IOException
    {
        HttpEntity responseEntity = null;
        try
        {
            // Exec request
            final HttpGet get = new HttpGet( execURL );

            final HttpResponse response = client.execute( get );
            responseEntity = response.getEntity();

            final StatusLine sl = response.getStatusLine();
            if ( sl.getStatusCode() != 200 )
            {
                throw new IOException( sl.getReasonPhrase() );
            }

            try( final InputStream is = responseEntity.getContent() )
            {
                final Document doc = Jsoup.parse( is ,
                                                  "UTF-8" ,
                                                  execURL );

                final Elements elts = doc.select( "input[name=token]" );
                if ( elts == null || elts.size() <= 0 )
                {
                    return null;
                }
                else
                {
                    return elts.get( 0 ).attr( "value" );
                }
            }
        }
        finally
        {
            if ( responseEntity != null )
            {
                EntityUtils.consume( responseEntity );
            }
        }
    }

    private void loginImpl( final String login ,
                            final String password ,
                            final String token )
        throws IOException
    {
        HttpEntity responseEntity = null;
        try
        {
            // Exec request
            final HttpPost post = new HttpPost( endpoint + "/?do=login" );

            final List nvps = new ArrayList<>();
            nvps.add( new BasicNameValuePair( "login" ,
                                              login ) );
            nvps.add( new BasicNameValuePair( "password" ,
                                              password ) );
            nvps.add( new BasicNameValuePair( "token" ,
                                              token ) );
            nvps.add( new BasicNameValuePair( "returnurl" ,
                                              endpoint ) );
            post.setEntity( new UrlEncodedFormEntity( nvps ,
                                                      "UTF-8" ) );

            final HttpResponse response = client.execute( post );
            responseEntity = response.getEntity();

            final StatusLine sl = response.getStatusLine();
            if ( sl.getStatusCode() != 302 )
            {
                try( final InputStream is = responseEntity.getContent() )
                {
                    throw new IOException( IOUtils.toString( is ) );
                }
            }
        }
        finally
        {
            if ( responseEntity != null )
            {
                EntityUtils.consume( responseEntity );
            }
        }
    }

    private List parseLinks( final String execURL )
    {
        if ( execURL == null )
        {
            throw new IllegalArgumentException();
        }

        if ( LOGGER.isDebugEnabled() )
        {
            LOGGER.debug(
                "[" + getClass().getSimpleName() + "] parseLinks() : execURL=" + execURL );
        }

        final List links = new ArrayList<>();

        HttpEntity responseEntity = null;
        try
        {
            // Exec request
            final HttpGet get = new HttpGet( execURL );

            final HttpResponse response = client.execute( get );
            responseEntity = response.getEntity();

            final StatusLine sl = response.getStatusLine();
            if ( sl.getStatusCode() == 200 )
            {
                try( final InputStream is = responseEntity.getContent() )
                {
                    final Document doc = Jsoup.parse( is ,
                                                      "UTF-8" ,
                                                      execURL );

                    final Elements elts = doc.select( "ul li" );
                    if ( elts != null )
                    {
                        for ( final Element elt : elts )
                        {
                            final boolean restricted;
                            final String cssClass = elt.attr( "class" );
                            if ( "private".equals( cssClass ) )
                            {
                                restricted = true;
                            }
                            else
                            {
                                restricted = false;
                            }

                            final String ID = elt.select( "input[name=lf_linkdate" ).attr( "value" );
                            final String permaID = elt.select( "a[name]" ).attr( "id" );
                            final String title = elt.select( "span[class=linktitle]" ).text();
                            final String description = elt.select( "div[class=linkdescription]" ).text();
                            final String url = elt.select( "span[class=linkurl]" ).text();

                            final ShaarliLink link = new ShaarliLink( ID ,
                                                                      permaID ,
                                                                      title ,
                                                                      description ,
                                                                      url ,
                                                                      restricted );

                            final Elements eltsTag = elt.select( "div[class=linktaglist] a" );
                            if ( eltsTag != null )
                            {
                                for ( final Element eltTag : eltsTag )
                                {
                                    final String tag = eltTag.text();

                                    link.addTag( tag );
                                }
                            }

                            links.add( link );
                        }
                    }

                    return links;
                }
            }
            else
            {
                throw new IOException( sl.getReasonPhrase() );
            }
        }
        catch( final IOException ex )
        {
            LOGGER.error( "Cannot links" ,
                          ex );
            return links;
        }
        finally
        {
            if ( responseEntity != null )
            {
                try
                {
                    EntityUtils.consume( responseEntity );
                }
                catch( final IOException ex )
                {
                    throw new RuntimeException( ex );
                }
            }
        }
    }

    private Iterator iterator( final String query )
    {
        return new Iterator()
        {
            // PUBLIC
            @Override
            public boolean hasNext()
            {
                if ( bufferCursor < buffer.size() )
                {
                    return true;
                }
                else
                {
                    buffer.clear();
                    bufferCursor = 0;

                    final List links;
                    if ( query != null && query.length() > 0 )
                    {
                        links = parseLinks( endpoint + "/?page=" + ( page++ ) + "&" + query );
                    }
                    else
                    {
                        links = parseLinks( endpoint + "/?page=" + ( page++ ) );
                    }

                    if ( links.isEmpty() )
                    {
                        return false;
                    }
                    else
                    {
                        final String linksLastID = links.get( links.size() - 1 ).getID();
                        if ( lastID != null && lastID.equals( linksLastID ) )
                        {
                            return false;
                        }
                        else
                        {
                            lastID = linksLastID;

                            buffer.addAll( links );

                            return true;
                        }
                    }
                }
            }

            @Override
            public ShaarliLink next()
            {
                return buffer.get( bufferCursor++ );
            }

            @Override
            public void remove()
            {
                throw new UnsupportedOperationException();
            }

            // PRIVATE
            private final List buffer = new ArrayList<>();
            private int bufferCursor;
            private int page = 1;
            private String lastID;
        };
    }

    private static String cleanEnding( final String url )
    {
        if ( url.endsWith( "/" ) )
        {
            return url.substring( 0 ,
                                  url.length() - 1 );
        }
        else
        {
            return url;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy