Accessing Photographs On Smugmug using Java

I store many of my photographs on smugmug (see http://pitonyak.smugmug.com/). They gave me a "coupon" to use that will save you $5 on your renewal (and if you use it, I save $10 on mine). The code is: Rl0h94ubYv13o

The upload client from Linux does not work well, so I am investigating accessing the website using restful web services. Step 1, obtain an account. Step 2, request a key that allows me to use the web services. Note that the key is free for the asking and they respond promptly.

For my first attempt, I connected directly from my web browser. Obviously, my login email address is not x@y.com, my password is not 1234, and my APIKey is not abcdefg, but, if it were, I can go to this URL to login.

https://api.smugmug.com/hack/rest/1.2.0/?method=smugmug.login.withPasswo...

I received the following reply:

  1. <rsp stat="ok">
  2.     <method>smugmug.login.withPassword</method>
  3.     <Login PasswordHash="xyzzy" AccountType="Power" FileSizeLimit="12582912" SmugVault="0">
  4.         <Session id="gfedcba"/>
  5.         <User id="94628" NickName="pitonyak" DisplayName="pitonyak"/>
  6.     </Login>
  7. </rsp>

After parsing the XML, I made another call to obtain information regarding all of my albums:

https://api.smugmug.com/hack/rest/1.2.0/?SessionID=gfedcba&Heavy=1&metho...

A representative reply showing only a single album is shown below:

  1. <rsp stat="ok">
  2.   <method>smugmug.albums.get</method>
  3.   <Albums>
  4.     <Album id="1" Key="2" Position="1" ImageCount="2" Title="Lib 1" Description="Whatever" Keywords="" Public="1" Password="" PasswordHint="" Printable="1" Filenames="0" Comments="1" External="1" Originals="1" EXIF="1" Share="1" SortMethod="Position" SortDirection="0" LastUpdated="2007-08-21 19:53:25" FamilyEdit="0" FriendEdit="0" HideOwner="0" CanRank="1" Clean="0" Geography="1" SmugSearchable="1" WorldSearchable="1" SquareThumbs="0" ColorCorrection="2" X2Larges="1" X3Larges="1" Header="0" Protected="0" UnsharpRadius="1" UnsharpSigma="1" UnsharpAmount="0.2" UnsharpThreshold="0.05">
  5.       <Highlight id="0"/>
  6.       <Community id="0"/>
  7.       <Template id="0"/>
  8.       <Category id="0" Name="Other"/>
  9.     </Album>
  10.   </Albums>
  11. </rsp>

Don't forget to logout

https://api.smugmug.com/hack/rest/1.2.0/?SessionID=gfedcba&method=smugmu...

My first attempt was using Java. The HashMap contained a list of key/value pairs, such as APIKey/abcdefg. The method is a string such as "smugmug.logout".

  1.   public String callMethod(HashMap<String, String> parameters, String method)
  2.   {
  3.     if (parameters == null)
  4.     {
  5.       parameters = new HashMap<String, String>();
  6.     }
  7.     if (method != null && method.length() > 0)
  8.     {
  9.       if (parameters.containsKey(SmugMugConstants.XmlMethod))
  10.       {
  11.         parameters.remove(SmugMugConstants.XmlMethod);
  12.       }
  13.       parameters.put(SmugMugConstants.XmlMethod, method);
  14.     }
  15.     if (!parameters.containsKey(SmugMugConstants.ArgAPIKey))
  16.     {
  17.       parameters.put(SmugMugConstants.ArgAPIKey, apiKey);
  18.     }
  19.     String response = null;
  20.     try
  21.     {
  22.       URL url = new URL(baseEndPoint + EncoderHelper.buildEncodedURL(parameters, "?", encodeArguments));
  23.       System.out.println("Using URL: " + url.toString());
  24.  
  25.       //make connection, use post mode, and send query
  26.       URLConnection urlc = url.openConnection();
  27.       urlc.setDoOutput(true);
  28.       urlc.setAllowUserInteraction(false);
  29.       PrintStream ps = new PrintStream(urlc.getOutputStream());
  30.       //ps.print(EncoderHelper.buildEncodedURL(parameters, "?", false));
  31.       ps.close();
  32.  
  33.       //retrieve result
  34.       BufferedReader br = new BufferedReader(new InputStreamReader(urlc.getInputStream()));
  35.       String str;
  36.       StringBuffer sb = new StringBuffer();
  37.       while ((str = br.readLine()) != null)
  38.       {
  39.         sb.append(str);
  40.         sb.append("\n");
  41.       }
  42.       br.close();
  43.       response = sb.toString();
  44.  
  45.       logger.info("Response = " + response);
  46.     }
  47.     catch (Exception e)
  48.     {
  49.       logger.severe("Failed REST service call.\n" + exceptionStacktraceToString(e));
  50.       response = null;
  51.     }
  52.     return response;
  53.   }

This is the encoder helper class.

  1. public class EncoderHelper
  2. {
  3.  
  4.   public static String Encoding = "UTF-8";
  5.  
  6.   /**
  7.    * Build the argument portion of a URL. Iterate through all entries in the hash table and
  8.    * build a string such as "?key1=value1&key2=value2&key3=value3".
  9.    * @param parameters Map of key/value pairs that will become the argument string.
  10.    * @param leadingString Usually a "?", this is the first character in the string.
  11.    * @param encodeValues Determines if values are URL encoded. 
  12.    * @return
  13.    * @throws java.io.UnsupportedEncodingException
  14.    */
  15.   public static String buildEncodedURL(HashMap<String, String> parameters, String leadingString, boolean encodeValues) throws UnsupportedEncodingException
  16.   {
  17.     StringBuilder sb = new StringBuilder();
  18.     if (parameters != null)
  19.     {
  20.       String value = null;
  21.       for (String key : parameters.keySet())
  22.       {
  23.         value = parameters.get(key);
  24.         if (key != null && key.length() > 0 && value != null)
  25.         {
  26.           if (sb.length() == 0)
  27.           {
  28.             if (leadingString != null)
  29.             {
  30.               sb.append(leadingString);
  31.             }
  32.           }
  33.           else
  34.           {
  35.             sb.append("&");
  36.           }
  37.           sb.append(key + "=");
  38.           if (encodeValues)
  39.           {
  40.             sb.append(URLEncoder.encode(value, Encoding));
  41.           }
  42.           else
  43.           {
  44.             sb.append(value);
  45.           }
  46.         }
  47.       }
  48.     }
  49.     return sb.toString();
  50.   }
  51. }

The driver code sets the arguments and makes the call to process the arguments:

  1.   public SmugMugLoginEntity login(String emailAddress, String password) throws XMLStreamException, IllegalAccessException
  2.   {
  3.     SmugMugLoginEntity entity = new SmugMugLoginEntity();
  4.     HashMap<String, String> parameters = new HashMap<String, String>();
  5.     setArgumentValue(parameters, SmugMugConstants.ArgEmailAddress, emailAddress, true);
  6.     setArgumentValue(parameters, SmugMugConstants.ArgPassword, password, true);
  7.     String result = callMethod(parameters, CmdLoginWithPassword);
  8.     if (!entity.processXML(result, CmdLoginWithPassword, true))
  9.     {
  10.       return null;
  11.     }
  12.     return entity;
  13.   }
  14.  
  15.   public SmugMugAlbumsEntity getAlbumsForAccount(String session, boolean verboseInformation) throws XMLStreamException, IllegalAccessException
  16.   {
  17.     SmugMugAlbumsEntity entity = new SmugMugAlbumsEntity();
  18.     HashMap<String, String> parameters = new HashMap<String, String>();
  19.     setArgumentValue(parameters, SmugMugConstants.ArgSessionID, session, true);
  20.     if (verboseInformation)
  21.     {
  22.       setArgumentValue(parameters, SmugMugConstants.ArgHeavy, "1", true);
  23.     }
  24.     String result = callMethod(parameters, CmdAlbumsGet);
  25.     if (!entity.processXML(result, CmdAlbumsGet, true))
  26.     {
  27.       return null;
  28.     }
  29.     return entity;
  30.   }
  31.  
  32.   public SmugMugImagesEntity getImagesForAlbum(String session, boolean verboseInformation, String sitePassword, SmugMugAlbumEntity entity) throws XMLStreamException, IllegalAccessException
  33.   {
  34.     String albumKey = entity.getAlbumKey();
  35.     String albumId = entity.getAlbumId();
  36.     String albumPassword = entity.getAlbumPassword();
  37.     return getImagesForAlbum(session, albumId, verboseInformation, albumPassword, sitePassword, albumKey);
  38.   }
  39.  
  40.   /**
  41.    * Retrieves a list of images for a given album.
  42.    * @param session
  43.    * @param albumId
  44.    * @param verboseInformation
  45.    * @param albumPassword
  46.    * @param sitePassword
  47.    * @param albumKey
  48.    * @return
  49.    * @throws javax.xml.stream.XMLStreamException
  50.    * @throws java.lang.IllegalAccessException
  51.    */
  52.   public SmugMugImagesEntity getImagesForAlbum(String session, String albumId, boolean verboseInformation, String albumPassword, String sitePassword, String albumKey) throws XMLStreamException, IllegalAccessException
  53.   {
  54.     SmugMugImagesEntity entity = new SmugMugImagesEntity();
  55.     HashMap<String, String> parameters = new HashMap<String, String>();
  56.     setArgumentValue(parameters, SmugMugConstants.ArgSessionID, session, true);
  57.     setArgumentValue(parameters, SmugMugConstants.ArgAlbumID, albumId, true);
  58.     setArgumentValue(parameters, SmugMugConstants.ArgAlbumKey, albumKey, true);
  59.     if (albumPassword != null)
  60.     {
  61.       setArgumentValue(parameters, SmugMugConstants.ArgPassword, albumPassword, true);
  62.     }
  63.     if (sitePassword != null)
  64.     {
  65.       setArgumentValue(parameters, SmugMugConstants.ArgSitePassword, sitePassword, true);
  66.     }
  67.  
  68.     if (verboseInformation)
  69.     {
  70.       setArgumentValue(parameters, SmugMugConstants.ArgHeavy, "1", true);
  71.     }
  72.     String result = callMethod(parameters, CmdImagesGet);
  73.     if (!entity.processXML(result, CmdImagesGet, true))
  74.     {
  75.       return null;
  76.     }
  77.     return entity;
  78.   }
  79.  
  80.  
  81.   /**
  82.    * This method will return camera and photograph details about the image
  83.    * specified by ImageID. The Album must be owned by the Session holder,
  84.    * or else be Public (if password-protected, a Password must be provided),
  85.    * to return results. Otherwise, an "invalid user" faultCode will result.
  86.    * Additionally, the album owner must have specified that EXIF data is
  87.    * allowed. Note that many photos have no EXIF data, so an empty or
  88.    * partially returned result is normal.
  89.    * @param session Current Session Id
  90.    * @param imageId Desired Image Id
  91.    * @param albumPassword Password for the album, or NULL of not required.
  92.    * @param sitePassword Password for the site, or NULL if not required.
  93.    * @param imageKey Key for the desired Image.
  94.    * @return Image EXIF information.
  95.    * @throws javax.xml.stream.XMLStreamException
  96.    * @throws java.lang.IllegalAccessException
  97.    */
  98.   public SmugMugImageEntity getImageExif(String session, String imageId, String albumPassword, String sitePassword, String imageKey) throws XMLStreamException, IllegalAccessException
  99.   {
  100.     SmugMugImageEntity entity = new SmugMugImageEntity();
  101.     HashMap<String, String> parameters = new HashMap<String, String>();
  102.     setArgumentValue(parameters, SmugMugConstants.ArgSessionID, session, true);
  103.     setArgumentValue(parameters, SmugMugConstants.ArgImageID, imageId, true);
  104.     setArgumentValue(parameters, SmugMugConstants.ArgImageKey, imageKey, true);
  105.     if (albumPassword != null)
  106.     {
  107.       setArgumentValue(parameters, SmugMugConstants.ArgPassword, albumPassword, true);
  108.     }
  109.     if (sitePassword != null)
  110.     {
  111.       setArgumentValue(parameters, SmugMugConstants.ArgSitePassword, sitePassword, true);
  112.     }
  113.  
  114.     String result = callMethod(parameters, CmdImagesGetExif);
  115.     if (!entity.processXML(result, CmdImagesGetExif, true))
  116.     {
  117.       return null;
  118.     }
  119.     return entity;
  120.   }
  121.  
  122.   /**
  123.    * This method will return details about the image specified by ImageID.
  124.    * The Album must be owned by the Session holder, or else be Public
  125.    * (if password-protected, a Password must be provided), to return results..
  126.    * Otherwise, an "invalid user" faultCode will result. Additionally,
  127.    * some fields are only returned to the Album owner.
  128.    * @param session Current Session Id
  129.    * @param imageId Desired Image Id
  130.    * @param albumPassword Password for the album, or NULL of not required.
  131.    * @param sitePassword Password for the site, or NULL if not required.
  132.    * @param imageKey Key for the desired Image.
  133.    * @return Information for a specific image.
  134.    * @throws javax.xml.stream.XMLStreamException
  135.    * @throws java.lang.IllegalAccessException
  136.    */
  137.   public SmugMugImageEntity getImageInfo(String session, String imageId, String albumPassword, String sitePassword, String imageKey) throws XMLStreamException, IllegalAccessException
  138.   {
  139.     SmugMugImageEntity entity = new SmugMugImageEntity();
  140.     HashMap<String, String> parameters = new HashMap<String, String>();
  141.     setArgumentValue(parameters, SmugMugConstants.ArgSessionID, session, true);
  142.     setArgumentValue(parameters, SmugMugConstants.ArgImageID, imageId, true);
  143.     setArgumentValue(parameters, SmugMugConstants.ArgImageKey, imageKey, true);
  144.     if (albumPassword != null)
  145.     {
  146.       setArgumentValue(parameters, SmugMugConstants.ArgPassword, albumPassword, true);
  147.     }
  148.     if (sitePassword != null)
  149.     {
  150.       setArgumentValue(parameters, SmugMugConstants.ArgSitePassword, sitePassword, true);
  151.     }
  152.  
  153.     String result = callMethod(parameters, CmdImagesGetInfo);
  154.     if (!entity.processXML(result, CmdImagesGetInfo, true))
  155.     {
  156.       return null;
  157.     }
  158.     return entity;
  159.   }
  160.  
  161.   /**
  162.    * Logout a session ID.
  163.    * @param session Current session ID.
  164.    * @throws javax.xml.stream.XMLStreamException
  165.    */
  166.   public void logout(String session) throws XMLStreamException
  167.   {
  168.     HashMap<String, String> parameters = new HashMap<String, String>();
  169.     setArgumentValue(parameters, SmugMugConstants.ArgSessionID, session, true);
  170.     String x = callMethod(parameters, CmdLogout);
  171.   }
  172.  
  173.   public SmugMugAlbumEntity getAlbumInfo(String session, String albumID, String albumKey, String albumPassword) throws XMLStreamException, IllegalAccessException
  174.   {
  175.     SmugMugAlbumEntity entity = new SmugMugAlbumEntity();
  176.     HashMap<String, String> parameters = new HashMap<String, String>();
  177.     setArgumentValue(parameters, SmugMugConstants.ArgSessionID, session, true);
  178.     setArgumentValue(parameters, SmugMugConstants.ArgAlbumKey, albumKey, false);
  179.     setArgumentValue(parameters, SmugMugConstants.ArgAlbumID, albumID, false);
  180.     setArgumentValue(parameters, SmugMugConstants.ArgPassword, albumPassword, false);
  181.     String result = callMethod(parameters, CmdAlbumsGetInfo);
  182.     if (!entity.processXML(result, CmdAlbumsGetInfo, true))
  183.     {
  184.       return null;
  185.     }
  186.     return entity;
  187.   }
  188.  
  189.   /**
  190.    * Set a key/value pair, overwriting any existing key/value pair.
  191.    * @param parameters key/value pairs
  192.    * @param key
  193.    * @param value
  194.    * @param forceValue If true, then value can not be null.
  195.    */
  196.   public static void setArgumentValue(HashMap<String, String> parameters, String key, String value, boolean forceValue)
  197.   {
  198.     if (parameters == null)
  199.     {
  200.       throw new NullPointerException("Parameters can not be null in setArgumentValue");
  201.     }
  202.     if (key == null)
  203.     {
  204.       throw new NullPointerException("Key can not be null in setArgumentValue");
  205.     }
  206.     if (value == null)
  207.     {
  208.       if (forceValue)
  209.       {
  210.         throw new NullPointerException("Value can not be null in setArgumentValue for key (" + key + ")");
  211.       }
  212.       return;
  213.     }
  214.     if (parameters.containsKey(key))
  215.     {
  216.       parameters.remove(key);
  217.     }
  218.     parameters.put(key, value);
  219.   }

Pizza's here, so I will post a follow-up later where I use C++ and QT rather than Java.