001/*
002 *  $URL: svn://svn.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/tools/MusicbrainzRestRequester.java $
003 *  $Author: tgutwin $
004 *  $Revision: 1219 $
005 *  $Date: 2018-03-02 22:12:45 -0800 (Fri, 02 Mar 2018) $
006 */
007/*
008 *
009 *  Written by Tom Gutwin - WebARTS Design.
010 *  Copyright (C) 2014-2016 WebARTS Design, North Vancouver Canada
011 *  http://www.webarts.ca
012 *
013 *  This program is free software; you can redistribute it and/or modify
014 *  it under the terms of the GNU General Public License as published by
015 *  the Free Software Foundation; version 3 of the License, or
016 *  (at your option) any later version.
017 *
018 *  This program is distributed in the hope that it will be useful,
019 *  but WITHOUT ANY WARRANTY; without_ even the implied warranty of
020 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
021 *  GNU General Public License for more details.
022 *
023 *  You should have received a copy of the GNU General Public License
024 *  along with this program; if not, write to the Free Software
025 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
026 */
027
028package ca.bc.webarts.tools.musicbrainz;
029
030import java.io.IOException;
031import java.io.File;
032import java.io.FileNotFoundException;
033import java.lang.Integer;
034import java.net.HttpURLConnection;
035import java.net.MalformedURLException;
036import java.net.URL;
037import java.net.URLEncoder;
038import java.util.Arrays;
039import java.util.Comparator;
040import java.util.Hashtable;
041import java.util.Set;
042import java.util.Vector;
043
044
045import ca.bc.webarts.tools.RestRequester;
046import ca.bc.webarts.widgets.Util;
047import ca.bc.webarts.widgets.Quick;
048
049import org.apache.commons.codec.binary.Base64;
050
051import nu.xom.Attribute;
052import nu.xom.Builder;
053import nu.xom.Document;
054import nu.xom.Element;
055import nu.xom.Elements;
056import nu.xom.Node;
057import nu.xom.ParsingException;
058import nu.xom.ValidityException;
059import nu.xom.Serializer;
060import nu.xom.XPathException;
061
062
063/**
064  * This class wraps the communication to the <a href="https://wiki.musicbrainz.org/Development">REST interface to Musicbrainz</a>.
065  * The web service root URL is https://musicbrainz.org/ws/2/<br>
066  * There are 12 resources on our web service which represent core entities in our database:
067  * <ul><li>area,</li><li>artist,</li><li>event,</li><li>instrument,</li><li>label,</li><li>place,</li><li>recording,</li><li>release,</li><li>release-group,</li><li>series,</li><li>work,</li><li>url</li></ul>
068  * We also provide a web service interface for the following non-core resources:
069  * <ul><li>rating,</li><li>tag,</li><li>collection</li></ul>
070  * You can perform lookups based on other unique identifiers with these resources:
071  *  <code>discid, isrc, iswc</code><br>
072  * On each entity resource, you can perform three different GET requests:
073  * <ul>
074  *  <li>lookup:   /&lt;ENTITY&gt;/&lt;MBID&gt;?inc=&lt;INC&gt;</li>
075  *  <li>browse:   /&lt;ENTITY&gt;?&lt;ENTITY&gt;=&lt;MBID&gt;&limit=&lt;LIMIT&gt;&offset=&lt;OFFSET&gt;&inc=&lt;INC&gt;</li>
076  *  <li>search:   /&lt;ENTITY&gt;?query=&lt;QUERY&gt;&limit=&lt;LIMIT&gt;&offset=&lt;OFFSET&gt;</li>
077  * ... except that search is not implemented for URL entities at this time.</ul><br />
078    * The request looks like this...<br>
079    *  <a href="http://musicbrainz.org/ws/2/release/?query=release:Schneider%20AND%20Shake">http://musicbrainz.org/ws/2/release/?query=release:Schneider%20AND%20Shake</a><br>
080    * or this... <a href="http://musicbrainz.org/ws/2/release/?query=country:US">http://musicbrainz.org/ws/2/release/?query=release:Schneider%20AND%20Shake%20AND%20country:US</a><br>
081    * or this... <a href="http://musicbrainz.org/ws/2/release/?query=release:X%20AND%20country:CA%20AND%20artist:Inxs">http://musicbrainz.org/ws/2/release/?query=release:X%20AND%20country:CA%20AND%20artist:Inxs</a><br>
082    *<br>
083  *
084  *  Written by Tom Gutwin - WebARTS Design.<br />
085  *  Copyright &copy; 2018 WebARTS Design, North Vancouver Canada<br />
086  *  <a href="http://www.webarts.ca">http://www.webarts.ca</a>
087  *
088  * @author  Tom B. Gutwin
089  **/
090public class MusicbrainzRestRequester extends ca.bc.webarts.tools.RestRequester
091{
092  protected static final String CLASSNAME = "ca.bc.webarts.tools.musicbrainz.MusicbrainzRestRequester"; //ca.bc.webarts.widgets.Util.getCurrentClassName();
093  public static final String LOG_TAG = "\n"+CLASSNAME; //+"."+ca.bc.webarts.android.Util.getCurrentClassName();
094
095  /** DEFAULT MUSICBRAINZ IP address to use: 10.0.0.207 .**/
096  protected static final String DEFAULT_MUSICBRAINZ_IP = "musicbrainz.org";
097  /** DEFAULT CoverArt Archive server used by MUSICBRAINZ .**/
098  protected static final String DEFAULT_COVERARTARCHIVE_IP = "coverartarchive.org";
099  /** DEFAULT MUSICBRAINZ username to use: admin .**/
100  protected static final String DEFAULT_MUSICBRAINZ_USERNAME = "admin";
101  /** DEFAULT MUSICBRAINZ password to use: admin .**/
102  protected static final String DEFAULT_MUSICBRAINZ_PASSWORD = "admin";
103  /** DEFAULT MUSICBRAINZ rest URL to start the URL path: /rest .**/
104  protected static final String DEFAULT_MUSICBRAINZ_REST_URL_PATHSTR = "/ws/2";
105  /** DEFAULT CoverArt Archive rest URL to start the URL path: /rest .**/
106  protected static final String DEFAULT_COVERARTARCHIVE_REST_URL_PATHSTR = "release"; // /release/{mbid}/front
107  /** A default Release MRID to use for testing. Barenaked Ladies, Gordon. **/
108  protected static final String DEFAULT_MUSICBRAINZ_TEST_RELEASE_MRID = "38e73fee-7420-4f3a-805a-6fdfac011ac2";
109  protected static final String DEFAULT_COVERARTARCHIVE_TEST_IMAGE_ID = "15241341638";
110  protected static final String DEFAULT_MUSICBRAINZ_TEST_ARTIST_MRID = "86e736b4-93e2-40ff-9e1c-fb7c63fef5f6";
111
112
113  protected static StringBuilder helpMsg_ = new StringBuilder(SYSTEM_LINE_SEPERATOR);
114  protected static boolean debugOut_ = false;
115  protected static boolean doWrites_ = true;
116
117  protected String metaDataDirFilename_ = ".";
118  protected String metaDataFilename_ = "mbData.props";
119
120  /** The start path to use in therest URL. Over-ride this if you extend this class. **/
121  protected String restUrlPath_ = DEFAULT_MUSICBRAINZ_REST_URL_PATHSTR;
122
123  /**
124     The URL for the CoverArt for any MRID looked up release. Various Coverart API calls are
125     available to get different images:
126     see <a href="https://musicbrainz.org/doc/Cover_Art_Archive/API">https://musicbrainz.org/doc/Cover_Art_Archive/API</a>
127  **/
128  protected String coverartImageUrl_="http://"+DEFAULT_COVERARTARCHIVE_IP+"/"
129                                    +DEFAULT_COVERARTARCHIVE_REST_URL_PATHSTR+"/"
130                                    +DEFAULT_MUSICBRAINZ_TEST_RELEASE_MRID+"/front";
131
132  Builder xmlBuilder_ = new Builder();
133
134  /**
135    * Default constructor .
136    *
137    * @see #DEFAULT_MUSICBRAINZ_IP
138    * @see #DEFAULT_MUSICBRAINZ_USERNAME
139    * @see #DEFAULT_MUSICBRAINZ_PASSWORD
140    **/
141  public MusicbrainzRestRequester()
142  {
143    authenticating_=false;
144    setBaseUrl( "https://"+DEFAULT_MUSICBRAINZ_IP+restUrlPath_);
145    setUsername( DEFAULT_MUSICBRAINZ_USERNAME);
146    setPassword( DEFAULT_MUSICBRAINZ_PASSWORD);
147  }
148
149
150  /**
151    * Constructor to customize all connection settings.
152    *
153    **/
154  public MusicbrainzRestRequester(String server, String user, String pass)
155  {
156    setBaseUrl( "https://"+server+restUrlPath_);
157    authenticating_=true;
158    setUsername(user);
159    setPassword( pass);
160  }
161
162  /**
163    * Set Method for class field 'restUrlPath_'.
164    *
165    * @param restUrlPath is the value to set this class field to.
166    *
167    **/
168  public  void setRestUrlPath(String restUrlPath)
169  {
170    restUrlPath_ = restUrlPath;
171  }  // setRestUrlPath Method
172
173
174  /**
175    * Get Method for class field 'restUrlPath_'.
176    *
177    * @return String - The value the class field 'restUrlPath_'.
178    *
179    **/
180  public String getRestUrlPath()
181  {
182    return restUrlPath_;
183  }  // getRestUrlPath Method
184
185
186  public String getCoverArtImageUrlStrByRelease(String artName, String albName)
187  {
188    StringBuilder releaseResults =  searchRelease(artName, albName);
189    nu.xom.Document releaseDoc = parseXMLResponse(releaseResults.toString());
190    String mrid = parseSearchResultsForMRID(releaseDoc,0);
191
192    return "http://"+DEFAULT_COVERARTARCHIVE_IP+"/"
193                    +DEFAULT_COVERARTARCHIVE_REST_URL_PATHSTR+"/"
194                    +mrid+"/front";
195  }
196
197
198  public String getCoverArtImageUrlStrByMbid(String mrid)
199  {
200    return "http://"+DEFAULT_COVERARTARCHIVE_IP+"/"
201                    +DEFAULT_COVERARTARCHIVE_REST_URL_PATHSTR+"/"
202                    +mrid+"/front";
203  }
204
205
206  /** Check connectivity to the MusicBrainz URL specified by the class parms.
207    * @return true or false
208    **/
209  public boolean canConnect()
210  {
211    if(debugOut_) System.out.println(LOG_TAG+".canConnect("+getBaseUrl()+", "+getUsername()+", "+getPassword()+")");
212
213    boolean retVal = false;
214    if(isInit())
215    {
216      try
217      {
218        if(debugOut_) System.out.println(LOG_TAG+".init = true");
219        String usrlStr = (baseUrl_+"/release/"+DEFAULT_MUSICBRAINZ_TEST_RELEASE_MRID).replace(" " ,"%20");
220        URL url = new URL(usrlStr);
221        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
222        conn.setRequestMethod("GET");
223        if(acceptJSON_)
224          conn.setRequestProperty("Accept", "application/json");
225        else
226          conn.setRequestProperty("Accept", "application/xml");
227
228         conn.setRequestProperty("User-Agent", USER_AGENT+" ( tgutwin@webarts.ca )");
229
230        if (authenticating_)
231        {
232          //BASE64Encoder enc = new sun.misc.BASE64Encoder();
233          String userpassword = username_ + ":" + password_;
234          //String encodedAuthorization = android.util.Base64.encodeToString( userpassword.getBytes(), android.util.Base64.DEFAULT );
235          String encodedAuthorization = new String(Base64.encodeBase64( (userpassword.getBytes()) ));
236          conn.setRequestProperty("Authorization", "Basic "+ encodedAuthorization);
237        }
238
239        if (conn.getResponseCode() == 200)
240        {
241           retVal=true;
242        } // valid http response code
243        conn.disconnect();
244      }
245      catch (MalformedURLException e)
246      {
247       e.printStackTrace();
248      }
249      catch (IOException e)
250      {
251         e.printStackTrace();
252      }
253    }
254    return retVal;
255  }
256
257
258  /** returns the status for all the nodes. **/
259  public StringBuilder getStatus()
260  {
261    return serviceGet("/status");
262  }
263
264
265  /** queries all the nodes. **/
266  public StringBuilder getQuery()
267  {
268    return serviceGet("/query");
269  }
270
271
272
273
274  /** returns config. **/
275  public StringBuilder getConfig()
276  {
277    return serviceGet("/config");
278  }
279
280
281
282  /**
283   * Class main commandLine entry method that has a test command and some convienience commands, as well as a pure rest command.
284   *
285   * <br>Commandline:<br>
286   *    java ca.bc.webarts.tools.musicbrainz.MusicbrainzRestRequester command or {restCommand}<br>
287   * <br>
288   * Available commands:<br>
289   *     test<br>
290   *
291   *     parseDir             Creates MusicBrainz Meta-data files for all subdirectories.<br>
292   * <br>
293   * Available restCommands:<br>
294   *     see: <a href="https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2">
295   *          https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2</a><br>
296   *   Example: java ca.bc.webarts.tools.musicbrainz.MusicbrainzRestRequester test <br>
297   *
298   **/
299  public static void main(String [] args)
300  {
301    final String methodName = CLASSNAME + ": main()";
302    int cmdStart = 0;
303    String cmdArgs = "";
304
305    MusicbrainzRestRequester instance = null;
306
307    if (args.length>4) /* Possible User/Pass added on the commandline */
308    {
309      if ( (args[cmdStart].equalsIgnoreCase("-u") && args[cmdStart+2].equalsIgnoreCase("-p") ) ||
310           (args[cmdStart+2].equalsIgnoreCase("-u") && args[cmdStart].equalsIgnoreCase("-p") )
311         )
312      {
313        instance = new MusicbrainzRestRequester(DEFAULT_MUSICBRAINZ_IP,
314                                                (args[cmdStart].equalsIgnoreCase("-u")?args[cmdStart+1]:args[cmdStart+3]),
315                                                (args[cmdStart].equalsIgnoreCase("-p")?args[cmdStart+1]:args[cmdStart+3])
316                                               );
317        cmdStart += 4;
318      }
319    }
320
321    if (instance==null)
322      instance = new MusicbrainzRestRequester();
323
324    instance.setAcceptJSON(false);
325
326    /* Simple way af parsing the args */
327    if (args ==null || args.length<1)
328      System.out.println(getHelpMsgStr());
329    /* *************************************** */
330    else
331    {
332      if (args[cmdStart].equalsIgnoreCase("-xml"))
333      {
334        instance.setAcceptJSON(false); // false means get XML
335        cmdStart++;
336      }
337      if (args[cmdStart].equalsIgnoreCase("-json"))
338      {
339        instance.setAcceptJSON(true); // true means get JSON
340        cmdStart++;
341      }
342
343      if (args[cmdStart].equalsIgnoreCase("test"))
344      {
345        instance.debugOut_=false;
346
347        System.out.println("Can Connect? "+(instance.canConnect()?"Yes":"No"));
348        System.out.println("\n**************************\nLookup Barenaked Ladies - Gordon ");
349        String [] incs = {"artists"};
350        String releaseResults =  instance.lookupRelease(DEFAULT_MUSICBRAINZ_TEST_RELEASE_MRID, incs).toString();
351        String artistResults =  instance.lookupArtist(DEFAULT_MUSICBRAINZ_TEST_ARTIST_MRID).toString();
352        String searchResults = instance.searchRelease("Barenaked Ladies", "Gordon").toString();
353        try
354        {
355          System.out.println("\n\n"+"Release\n~~~~~~~~~~~~~~~~");
356          System.out.println(Util.xmlToPrettyString(releaseResults, 2));
357          nu.xom.Document releaseDoc = instance.parseXMLResponse(releaseResults);
358          System.out.println("\nRelease Date: "+instance.parseFirstReleaseDate(releaseDoc));
359          System.out.println("\nRelease Year: "+instance.parseFirstReleaseYear(releaseDoc));
360
361          System.out.println("\n\n"+"Artist\n~~~~~~~~~~~~~~~~");
362          System.out.println(Util.xmlToPrettyString(artistResults, 2));
363          nu.xom.Document artistDoc = instance.parseXMLResponse(artistResults);
364
365          System.out.println("\n\n"+"Search\n~~~~~~~~~~~~~~~~");
366          System.out.println(Util.xmlToPrettyString(searchResults, 2));
367          nu.xom.Document searchDoc = instance.parseXMLResponse(searchResults);
368        }
369        catch (Exception ex)
370        {
371          ex.printStackTrace();
372          System.out.println("\nParsing error - raw results:");
373          System.out.println(releaseResults);
374        }
375      }
376      /* *************************************** */
377      else if (args[cmdStart].equalsIgnoreCase("searchRelease"))
378      {
379        for (int i=cmdStart+2; i<args.length; i++ ) cmdArgs+=" "+args[i];
380        cmdArgs = cmdArgs.trim();
381
382        String releaseResults =  instance.searchRelease(args[cmdStart+1], cmdArgs).toString();
383        try
384        {
385          System.out.println("\n\n"+"Release\n~~~~~~~~~~~~~~~~");
386          if(debugOut_) System.out.println(Util.xmlToPrettyString(releaseResults, 2));
387          nu.xom.Document releaseDoc = instance.parseXMLResponse(releaseResults);
388          System.out.println("\nRelease MRID: "+instance.parseSearchResultsForMRID(releaseDoc,0));
389          System.out.println("\nRelease Date: "+instance.parseFirstReleaseDate(releaseDoc));
390          System.out.println("\nRelease Year: "+instance.parseFirstReleaseYear(releaseDoc));
391
392        }
393        catch (Exception ex)
394        {
395          ex.printStackTrace();
396          System.out.println("\nParsing error - raw results:");
397          System.out.println(releaseResults);
398        }
399      }
400      /* *************************************** */
401      else if (args[cmdStart].equalsIgnoreCase("searchArtist"))
402      {
403        for (int i=cmdStart+1; i<args.length; i++ ) cmdArgs+=" "+args[i];
404        cmdArgs = cmdArgs.trim();
405
406        String artistResults =  instance.searchArtist(cmdArgs).toString();
407        try
408        {
409          System.out.println("\n\n"+"Artist\n~~~~~~~~~~~~~~~~");
410          System.out.println(Util.xmlToPrettyString(artistResults, 2));
411          nu.xom.Document artistDoc = instance.parseXMLResponse(artistResults);
412
413          /* use the artistDoc to find the artist with attribute ns2:score="100" */
414
415        }
416        catch (Exception ex)
417        {
418          ex.printStackTrace();
419          System.out.println("\nParsing error - raw results:");
420          System.out.println(artistResults);
421        }
422      }
423      /* *************************************** */
424      // Looks up the MRID
425      else if (args[cmdStart].equalsIgnoreCase("lookupArtist")) // expecting an MRID
426      {
427
428        String searchResults = "";
429        try
430        {
431          instance.debugOut_=true;
432
433          instance.setAcceptJSON(false); // false means get XML
434          System.out.println("Can Connect? "+(instance.canConnect()?"Yes":"No"));
435          StringBuilder sb = instance.lookupArtist(args[cmdStart+1]);
436          if(sb!=null)
437          {
438            searchResults = sb.toString();
439            System.out.println(Util.xmlToPrettyString(searchResults, 2));
440            nu.xom.Document searchDoc = instance.parseXMLResponse(searchResults);
441          }
442        }
443        catch (Exception ex)
444        {
445          ex.printStackTrace();
446          System.out.println("\nParsing error - raw results:");
447          System.out.println(searchResults);
448        }
449      }
450      /* *************************************** */
451      else if (args[cmdStart].equalsIgnoreCase("lookupRelease")) // expecting an MRID
452      {
453        String searchResults = "";
454        try
455        {
456          instance.debugOut_=false;
457
458          instance.setAcceptJSON(false); // false means get XML
459          System.out.println("Can Connect? "+(instance.canConnect()?"Yes":"No"));
460          searchResults = instance.lookupRelease(args[cmdStart+1]).toString();
461          System.out.println(Util.xmlToPrettyString(searchResults, 2));
462          nu.xom.Document searchDoc = instance.parseXMLResponse(searchResults);
463        }
464        catch (Exception ex)
465        {
466          ex.printStackTrace();
467          System.out.println("\nParsing error - raw results:");
468          System.out.println(searchResults);
469        }
470      }
471      /* *************************************** */
472      else if (args[cmdStart].equalsIgnoreCase("parseDir"))
473      {
474        System.out.println("    Parsing current dir AND creating meta-data files for all subDir Artists.");
475        instance.parseTunesDir(".");
476      }
477      /* *************************************** */
478      else if (args[cmdStart].equalsIgnoreCase("parseArtistDir"))
479      {
480        instance.debugOut_=true;
481
482        System.out.println("    Parsing current dir AND creating meta-data files for all subDir Albums.");
483        instance.parseArtistDir(".");
484      }
485      /* *************************************** */
486      else
487      {
488        instance.restCMD(args);
489      }
490    }
491  } // main
492
493
494
495  /** Parses the tunes dir and reads in all the artists. **/
496  public void parseArtistDir(String artistDirPath)
497  {
498    final String methodName = CLASSNAME + ": parseArtistDir(String )";
499
500    if(debugOut_) System.out.println("    >> "+methodName);
501      File artistDirFile = new File( artistDirPath);
502      //java.util.List<Track> pagedLovedTracks = (java.util.List<Track>)lastFmLovedTracks_.getPageResults();
503      if(artistDirFile!=null)
504      {
505        try
506        {
507          String artName = artistDirFile.getCanonicalPath().substring(artistDirFile.getCanonicalPath().lastIndexOf("/")+1);
508          if(artName.equals("54•40") ) artName="54-40";
509          // check if the artist dir has ANY album dirs, then add
510          File[] albumFiles = (new File(artistDirFile.getAbsolutePath())).listFiles();
511          boolean valid = false;
512          String artNameSpaced = "";
513          for(int i=0; !valid&&i<albumFiles.length; i++)
514          {
515            // check if there are ANY album dirs
516            if(albumFiles[i].isDirectory()
517               && albumFiles[i].canRead()
518               && albumFiles[i].listFiles().length>0
519              ) valid= true;
520          }
521          if(valid)
522          {
523            artNameSpaced = Util.capsToSpacesInString(artName);
524            artNameSpaced = Util.tokenReplace(artNameSpaced,"_"," ");
525            if(debugOut_) System.out.println("    >>"+methodName+"\n                    Creating a MusicbrainzArtist("+artNameSpaced+")");
526            MusicbrainzArtist art = new MusicbrainzArtist(artNameSpaced,
527                                                          artistDirFile.getAbsolutePath(),
528                                                          false); // last false means DONT use cache only ie. query MB
529
530            //art.writeMetaData();
531            art.parseAlbumDirs();
532          }
533        }
534        catch ( Exception ex)
535        {
536          System.out.println(" *!What The Fudge (parseArtistDir()) : "+ex.getMessage());
537          ex.printStackTrace();
538        }
539      }
540  }
541
542
543  /** Parses the tunes dir and reads in all the artists. **/
544  public void parseTunesDir(String tunesDirPath)
545  {
546    final String methodName = CLASSNAME + ": parseTunesDir(String )";
547
548    if(debugOut_) System.out.println("    >>"+methodName);
549      File[] artistDirFiles = (new File( tunesDirPath)).listFiles();
550      //java.util.List<Track> pagedLovedTracks = (java.util.List<Track>)lastFmLovedTracks_.getPageResults();
551      if(artistDirFiles!=null)
552      {
553        Vector <MusicbrainzArtist> artists = new Vector <MusicbrainzArtist>();
554        try
555        {
556          for(File currAristDirFile : artistDirFiles)
557            if(currAristDirFile.isDirectory()
558               && currAristDirFile.canRead() )
559            {
560              String artName = currAristDirFile.getAbsolutePath().substring(currAristDirFile.getAbsolutePath().lastIndexOf("/")+1);
561              if(artName.equals("54•40") ) artName="54-40";
562              // check if the artist dir has ANY album dirs, then add
563              File[] albumFiles = (new File(currAristDirFile.getAbsolutePath())).listFiles();
564              boolean valid = false;
565              String artNameSpaced = "";
566              try
567              {
568                for(int i=0; !valid&&i<albumFiles.length; i++)
569                {
570                  // check if there are ANY album dirs
571                  if(albumFiles[i].isDirectory()
572                     && albumFiles[i].canRead()
573                     && albumFiles[i].listFiles().length>0
574                    ) valid= true;
575                }
576                if(valid)
577                {
578                  artNameSpaced = Util.capsToSpacesInString(artName);
579                  artNameSpaced = Util.tokenReplace(artNameSpaced,"_"," ");
580                  if(debugOut_) System.out.println("    >>"+methodName+"\n                    Creating a MusicbrainzArtist("+artNameSpaced+")");
581                  MusicbrainzArtist art = new MusicbrainzArtist(artNameSpaced,
582                                                                currAristDirFile.getAbsolutePath(),
583                                                                false); // last false means DONT use cache only ie. query MB
584
585                  artists.add(art);
586                  //art.writeMetaData();
587                  art.parseAlbumDirs();
588                }
589              }
590              catch ( Exception ex)
591              {
592                System.out.println(" *!What The Fudge (parseTunesDir()) : "+ex.getMessage());
593                ex.printStackTrace();
594              }
595            }
596        }
597        catch(Exception ex) { /* ignore error dirs */ }
598        artists.sort(MusicbrainzArtist.ArtistComparatorIgnoreCase );
599      }
600  }
601
602
603  /**
604   * commandLine command executor method for the test Command.
605   * @param args the array of commandLine args that got passed in
606   **/
607  protected void testCMD(String [] args)
608  {
609    final String methodName = CLASSNAME + ": testCMD(String [])";
610
611    System.out.println("Testing MusicBrainz Rest Service: "+ "/sys");
612    StringBuilder resp =  serviceGet("/sys");
613    System.out.println(resp.toString()); System.out.println();
614  }
615
616
617  protected StringBuilder lookupRelease(String mrid){return lookupRelease(mrid, null);}
618  /**
619   * commandLine command executor method for the test Command.
620   * @param mrid the MusicBrainz Release ID to lookup
621   * @param incs the optional array of associated includes to add to the lookup, null to not add any extra includes
622   **/
623  public StringBuilder lookupRelease(String mrid, String [] incs)
624  {
625    final String methodName = CLASSNAME + ": lookupRelease(String )";
626
627    if(debugOut_) System.out.println(" MusicBrainz Rest Service: "+ "lookup a RELEASE");
628    String inc = "";
629    if(incs!=null && incs.length >0 && !"".equals(incs[0]))
630    {
631      int cnt = 0;
632      inc = "?inc=";
633      for (String i : incs)
634      {
635        if(cnt++>0) inc+="+";
636        inc+=i;
637      }
638      if(debugOut_) System.out.println("  with includes="+inc);
639    }
640    StringBuilder resp =  serviceGet("/release/"+mrid+inc.replace(" " ,"+"));
641    //if(debugOut_) System.out.println(resp.toString()); if(debugOut_) System.out.println();
642    return resp;
643  }
644
645
646  public StringBuilder searchRelease(String artist, String release)
647  {
648    return searchRelease( artist,  release,1);
649  }
650
651
652  /**
653   * Wraps the MusicBrainz search, specifically for a release ; example:  https://musicbrainz.org/ws/2/release/?query=release:Gordon%20AND%20artist:Barenaked%20Ladies .
654   * if you want only area=Canada : ID=71bbafaa-e825-3e15-8ca9-017dcad1748b<br>
655   * try https://musicbrainz.org/ws/2/release/?query=release:Gordon%20AND%20country:CA%20AND%20artist:Barenaked+Ladies
656   * @param artist the artistName of teh release
657   * @param release the name of the release
658   * @param limit the maximum number of returned results
659   * @return the search results in MMD - MusicBrainz XML Metadata Format (https://musicbrainz.org/doc/MusicBrainz_XML_Meta_Data)
660   **/
661  public StringBuilder searchRelease(String artist, String release, int limit)
662  {
663    final String methodName = CLASSNAME + ": searchRelease(String , String)";
664    StringBuilder resp = null;
665    //debugOut_=true;
666
667    String limitStr="&limit="+limit;
668
669    if(debugOut_) System.out.println("  MusicBrainz Rest Service: "+ "search for a RELEASE");
670    if(artist!=null && release!=null
671       && !"".equals(artist) && !"".equals(release) )
672    {
673      try
674      {
675        String searchRequest = "/release/?query=release:"+
676                               URLEncoder.encode(release, "UTF-8")+
677                               "%20AND%20artist:"+
678                               URLEncoder.encode(artist, "UTF-8")+
679                               limitStr;
680        if(debugOut_) System.out.println("     searchRequest= "+searchRequest);
681        resp =  serviceGet(searchRequest);
682
683      }
684      catch(java.io.UnsupportedEncodingException ueEx)
685      {
686        ueEx.printStackTrace();
687      }
688    }
689    //if(debugOut_) System.out.println(resp.toString()); if(debugOut_) System.out.println();
690    //debugOut_=false;
691    return resp;
692  }
693
694
695  public StringBuilder searchArtist(String artist)
696  {
697    return searchArtist( artist,  1);
698  }
699
700
701  /**
702   * Wraps the MusicBrainz search, specifically for an Artist ; example:  https://musicbrainz.org/ws/2/release/?query=release:Gordon%20AND%20artist:Barenaked%20Ladies .
703   * if you want only area=Canada : ID=71bbafaa-e825-3e15-8ca9-017dcad1748b<br>
704   * try https://musicbrainz.org/ws/2/release/?query=release:Gordon%20AND%20country:CA%20AND%20artist:Barenaked+Ladies
705   * @param artist the artistName of teh release
706   * @param limit the maximum number of returned results
707   * @return the search results in MMD - MusicBrainz XML Metadata Format (https://musicbrainz.org/doc/MusicBrainz_XML_Meta_Data)
708   **/
709  public StringBuilder searchArtist(String artist, int limit)
710  {
711    final String methodName = CLASSNAME + ": searchArtist("+artist+")";
712    StringBuilder resp = null;
713
714    String limitStr="&limit="+limit;
715
716    if(debugOut_) System.out.println("  MusicBrainz Rest Service: "+ "search for an ARTIST");
717    if(artist!=null
718       && !"".equals(artist) )
719    {
720      try
721      {
722        String searchRequest = "/artist/?query=artist:"+
723                               URLEncoder.encode(artist, "UTF-8")+
724                               limitStr;
725        if(debugOut_) System.out.println("     searchRequest= "+searchRequest);
726        resp =  serviceGet(searchRequest);
727
728      }
729      catch(java.io.UnsupportedEncodingException ueEx)
730      {
731        ueEx.printStackTrace();
732      }
733    }
734    //if(debugOut_) System.out.println(resp.toString()); if(debugOut_) System.out.println();
735    //debugOut_=false;
736    return resp;
737  }
738
739
740  protected StringBuilder lookupArtist(String mrid){return lookupArtist(mrid, null);}
741  /**
742   * commandLine command executor method for the test Command.
743   * @param mrid the MusicBrainz Release ID to lookup
744   * @param incs the optional array of associated includes to add to the lookup, null to not add any extra includes
745   **/
746  public StringBuilder lookupArtist(String mrid, String [] incs)
747  {
748    final String methodName = CLASSNAME + ": lookupArtist(String )";
749
750    if(debugOut_) System.out.println(" MusicBrainz Rest Service: "+ "lookup an ARTIST");
751    String inc = "";
752    if(incs!=null && incs.length >0 && !"".equals(incs[0]))
753    {
754      int cnt = 0;
755      inc = "?inc=";
756      for (String i : incs)
757      {
758        if(cnt++>0) inc+="+";
759        inc+=i;
760      }
761    }
762    StringBuilder resp =  serviceGet("/artist/"+mrid+inc.replace(" " ,"+"));
763    //if(debugOut_) System.out.println(resp.toString()); if(debugOut_) System.out.println();
764    return resp;
765  }
766
767
768  public nu.xom.Document parseXMLResponse(String respXmlStr)
769  {
770    //String methodName = className_+"."+Util.getCurrentMethodName();
771    //log_.startMethod(methodName);
772    if(debugOut_) System.out.println("  parseXMLResponse(String) ");
773
774    nu.xom.Document respDoc = null;
775    nu.xom.Element rootElem = null;
776    try
777    {
778      respDoc = xmlBuilder_.build(respXmlStr,null);
779    }
780    catch (nu.xom.ParsingException xomEx)
781    {
782      xomEx.printStackTrace();
783      System.out.println("MAJor Error: parsing respXmlStr");
784      System.out.println(respXmlStr);
785    }
786    catch (IOException ioEx)
787    {
788      ioEx.printStackTrace();
789      System.out.println("MAJor Error: IOException respXmlStr");
790      System.out.println(respXmlStr);
791    }
792    return respDoc;
793  }
794
795
796  public java.util.Date parseFirstReleaseDate(nu.xom.Document respDoc){return parseReleaseDate(respDoc, 0);}
797  public java.util.Date parseReleaseDate(nu.xom.Document respDoc, int releaseIndex)
798  {
799    java.util.Date retVal = null;
800    nu.xom.Element rootElem = null;
801    nu.xom.Element dateXpathElem = null;
802    String dateStr = "";
803
804    if(debugOut_) System.out.println(" > parseReleaseDate not null?"+(respDoc!=null));
805    if (respDoc!=null)
806    {
807      rootElem = respDoc.getRootElement();
808
809      nu.xom.Elements releaseListElems = parseReleaseListElems(respDoc);
810      nu.xom.Element releaseListElem = null;
811      nu.xom.Element dateElem = null;
812      int numReleases = 0;
813      if(debugOut_) System.out.println("     > releaseListElems not null?"+(releaseListElems!=null));
814      if(debugOut_) System.out.println(  "       > Searching For Index "+releaseIndex +" of " +releaseListElems.size());
815      for (int i=0; i< releaseListElems.size(); i++)
816      {
817        releaseListElem = releaseListElems.get(i);  //release-event
818        if(releaseListElem.getLocalName().equalsIgnoreCase("date") )
819        {
820          dateElem = releaseListElem;
821        }
822      }
823      if (dateElem!=null)
824      {
825        if(debugOut_) System.out.print(" .");
826        dateStr = dateElem.getValue();
827        if(debugOut_) System.out.print(" "+dateStr);
828        if(debugOut_) System.out.println("  "+Integer.parseInt(dateStr.substring(0,4))+" "+
829                                Integer.parseInt(dateStr.substring(5,7))+" "+
830                                Integer.parseInt(dateStr.substring(8,10)));
831        if (dateElem!=null )
832        {
833          if(dateStr.length()>4)
834            retVal = new java.util.Date(Integer.parseInt(dateStr.substring(0,4))-1900,
835                                                         Integer.parseInt(dateStr.substring(5,7)),
836                                                         Integer.parseInt(dateStr.substring(8,10)));
837          else if(dateStr.length()>0)
838            retVal = new java.util.Date(Integer.parseInt(dateStr.substring(0,4))-1900,
839                                                         1,1);
840        }
841      }
842      else
843      {
844          System.out.println("        ERROR> parseReleaseDate numReleases =  "+numReleases);
845          System.out.println("                                releaseEventElems.size = "+releaseListElems.size());
846      }
847    }
848    if(debugOut_) System.out.println();
849    return retVal;
850  }
851
852
853  public int parseFirstReleaseYear(nu.xom.Document respDoc){return parseReleaseYear(respDoc, 0);}
854  public int parseReleaseYear(nu.xom.Document respDoc, int releaseIndex)
855  {
856     int retVal = 0;
857
858     java.util.Date relDate = parseReleaseDate(respDoc, releaseIndex);
859     if (relDate!=null)  retVal = Integer.parseInt(right(relDate.toString().trim(),4)); // dow mon dd hh:mm:ss zzz yyyy
860    if(debugOut_) System.out.println(retVal);
861    return retVal;
862  }
863
864
865  public  String right(String value, int length)
866  {
867    // To get right characters from a string, change the begin index.
868    return value.substring(value.length() - length);
869  }
870
871
872  public nu.xom.Elements parseReleaseEventElems(nu.xom.Document respDoc)
873  {
874    nu.xom.Elements retVal = null;
875    nu.xom.Element rootElem = null;
876
877    if(debugOut_) System.out.println(" > parseReleaseEventElems not null?"+(respDoc!=null));
878    if (respDoc!=null)
879    {
880      rootElem = respDoc.getRootElement();
881      // load the data
882      if (rootElem != null && rootElem instanceof Element && rootElem.getLocalName().equals("metadata"))
883      {
884        if(debugOut_) System.out.print(" .");
885        nu.xom.Elements releaseElems = rootElem.getChildElements();
886        nu.xom.Element releaseElem = releaseElems.get(0);
887        if (releaseElem!=null )
888        {
889          if(debugOut_) System.out.print(" .");
890          nu.xom.Elements releaseEventListElems = releaseElem.getChildElements();
891          nu.xom.Element releaseEventListElem = null;
892          for (int i=0; i< releaseEventListElems.size(); i++)
893          {
894            releaseEventListElem = releaseEventListElems.get(i);  //release-event-list
895            if(releaseEventListElem.getLocalName().equalsIgnoreCase("release-event-list"))
896              i=releaseEventListElems.size();
897            //releaseEventListElem = releaseElem.getFirstChildElement("release-event-list");
898          }
899          if (releaseEventListElem!=null )
900          {
901            if(debugOut_) System.out.print(" ." );
902            retVal = releaseEventListElem.getChildElements();
903          }
904        }
905      }
906    }
907    return retVal;
908  }
909
910
911  public nu.xom.Elements parseReleaseListElems(nu.xom.Document respDoc)
912  {
913    nu.xom.Elements retVal = null;
914    nu.xom.Element rootElem = null;
915
916    if(debugOut_) System.out.println(" > parseReleaseListElems not null?"+(respDoc!=null));
917    if (respDoc!=null)
918    {
919      rootElem = respDoc.getRootElement();
920      if (rootElem != null && rootElem instanceof Element && rootElem.getLocalName().equals("metadata"))
921      {
922        if(debugOut_) System.out.print(" .");
923        nu.xom.Elements releaseElems = rootElem.getChildElements();
924        nu.xom.Element releaseElem = releaseElems.get(0);
925        if (releaseElem!=null )
926        {
927          if(debugOut_) System.out.print(" .");
928          nu.xom.Elements releaseEventListElems = releaseElem.getChildElements();
929          nu.xom.Element releaseEventListElem = null;
930          for (int i=0; i< releaseEventListElems.size(); i++)
931          {
932            releaseEventListElem = releaseEventListElems.get(i);  //release-list
933            if(releaseEventListElem.getLocalName().equalsIgnoreCase("release-list"))
934              i=releaseEventListElems.size();
935            //releaseEventListElem = releaseElem.getFirstChildElement("release-event-list");
936          }
937          if (releaseEventListElem!=null )
938          {
939            if(debugOut_) System.out.print(" ." );
940            retVal = releaseEventListElem.getChildElements();
941          }
942        }
943      }
944    }
945    return retVal;
946  }
947
948
949  public nu.xom.Element parseFirstReleaseEventElem(nu.xom.Document respDoc){return parseReleaseEventElem(respDoc, 0);}
950  public nu.xom.Element parseReleaseEventElem(nu.xom.Document respDoc, int releaseIndex)
951  {
952    nu.xom.Element retVal = null;
953    nu.xom.Element rootElem = null;
954
955    if(debugOut_) System.out.println(" > parseReleaseEventElem not null?"+(respDoc!=null));
956    if (respDoc!=null)
957    {
958      rootElem = respDoc.getRootElement();
959
960      nu.xom.Elements releaseEventElems = parseReleaseEventElems(respDoc);
961      nu.xom.Element releaseEventElem = null;  //("release-event");
962      int numReleases = 0;
963      for (int i=0; i< releaseEventElems.size(); i++)
964      {
965        releaseEventElem = releaseEventElems.get(i);  //release-event
966        if(releaseEventElem.getLocalName().equalsIgnoreCase("release-event") )
967        {
968          if(numReleases==releaseIndex)
969          {
970            i=releaseEventElems.size();
971            retVal = releaseEventElem;
972          }
973          numReleases++;
974        }
975      }
976    }
977
978    return retVal;
979  }
980
981
982  /** parses through a returned responseResult doc and looks for the 1st  "release" element.
983    * see <a href="https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2">https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2</a>
984    *
985    * @param respDoc is the MB responseResult XML document from a release query request
986    * @return the release Element located at index releaseIndex
987    **/
988  public nu.xom.Element parseFirstReleaseListElem(nu.xom.Document respDoc){return parseReleaseListElem(respDoc, 0);}
989
990
991
992  /** parses through a returned responseResult doc and looks for the releaseIndex "release" element.
993    * see <a href="https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2">https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2</a>
994    *
995    * @param respDoc is the MB responseResult XML document from a release query request
996    * @param releaseIndex is the zero based release element to return
997    * @return the release Element located at index releaseIndex
998    **/
999  public nu.xom.Element parseReleaseListElem(nu.xom.Document respDoc, int releaseIndex)
1000  {
1001    nu.xom.Element retVal = null;
1002    nu.xom.Element rootElem = null;
1003
1004    if(debugOut_) System.out.println(" > parseReleaseListElem not null?"+(respDoc!=null));
1005    if (respDoc!=null)
1006    {
1007      rootElem = respDoc.getRootElement();
1008
1009      nu.xom.Elements releaseListElems = parseReleaseListElems(respDoc);
1010      nu.xom.Element releaseListElem = null;  //("release-event");
1011      int numReleases = 0;
1012      for (int i=0; i< releaseListElems.size(); i++)
1013      {
1014        releaseListElem = releaseListElems.get(i);  //release-event
1015        if(releaseListElem.getLocalName().equalsIgnoreCase("release") )
1016        {
1017          if(numReleases==releaseIndex)
1018          {
1019            i=releaseListElems.size();
1020            retVal = releaseListElem;
1021          }
1022          numReleases++;
1023        }
1024      }
1025    }
1026
1027    return retVal;
1028  }
1029
1030
1031  public int parseSearchResultsForReleaseYear(nu.xom.Document respDoc)
1032  {
1033    int retVal = 1900;
1034    nu.xom.Element rootElem = null;
1035
1036    if(debugOut_) System.out.println(" > parseReleaseEventElems not null?"+(respDoc!=null));
1037    if (respDoc!=null)
1038    {
1039      rootElem = respDoc.getRootElement();
1040      // load the data
1041      if (rootElem != null && rootElem instanceof Element && rootElem.getLocalName().equals("metadata"))
1042      {
1043        if(debugOut_) System.out.print(" .");
1044        nu.xom.Elements releaseListElems = rootElem.getChildElements();
1045        nu.xom.Element releaseListElem = releaseListElems.get(0);
1046        if (releaseListElem!=null )
1047        {
1048          if(debugOut_) System.out.print(" .");
1049          nu.xom.Elements releaseElems = releaseListElem.getChildElements();
1050          nu.xom.Element releaseElem = null;
1051          //for (int i=0; i< releaseElems.size(); i++)
1052          //{
1053          releaseElem = releaseElems.get(0);  //release-event-list
1054          if (releaseElem!=null )
1055          {
1056            if(debugOut_) System.out.print(" .");
1057            if(debugOut_) System.out.print(" ("+releaseElem.getLocalName()+")");
1058            nu.xom.Elements releaseSubElems = releaseElem.getChildElements();
1059            nu.xom.Element dateElem = null;
1060            boolean foundIt = false;
1061            for (int i=0; i< releaseSubElems.size(); i++)
1062            {
1063              dateElem = releaseSubElems.get(i);
1064              if(dateElem.getLocalName().equalsIgnoreCase("date"))
1065              {
1066                foundIt = true;
1067                i=releaseSubElems.size();
1068              }
1069            }
1070            if (foundIt)
1071            {
1072              if(debugOut_) System.out.print(" ." );
1073              String dateStr = dateElem.getValue();
1074              if(debugOut_) System.out.print(" "+dateStr);
1075              retVal = Integer.parseInt(dateStr.substring(0,4));
1076            }
1077          }
1078        }
1079      }
1080    }
1081    if(debugOut_) System.out.println("\n  >>>>>>>>>>>>>>>>>>>>>>>>>>");
1082   return retVal;
1083  }
1084
1085
1086  /** loops through a returned responseResult doc and looks for the 1st result.
1087    * @return null if not found, else it returns the release element
1088  **/
1089  public nu.xom.Element parseSearchResultsForFirstReleaseElem(nu.xom.Document respDoc)
1090  {
1091    return parseSearchResultsForReleaseElem(respDoc, 0);
1092  }
1093
1094
1095  /** loops through a returned responseResult doc and looks for the 'releaseIndex' result.
1096    * responses from a release query have multiple releases in them.
1097    * see <a href="https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2#Release">https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2#Release</a>
1098    *
1099    * @param respDoc is the MB responseResult XML document from a release query request
1100    * @param releaseIndex is the zero based release element to return
1101    * @return the relese located at index releaseIndex
1102    **/
1103  public nu.xom.Element parseSearchResultsForReleaseElem(nu.xom.Document respDoc, int releaseIndex)
1104  {
1105    nu.xom.Element retVal = null;
1106    nu.xom.Element rootElem = null;
1107
1108    if(debugOut_) System.out.println(" > parseSearchResultsForReleaseElem["+releaseIndex+"] not null?"+(respDoc!=null));
1109    if (respDoc!=null)
1110    {
1111      rootElem = respDoc.getRootElement();
1112      // load the data
1113      if (rootElem != null && rootElem instanceof Element && rootElem.getLocalName().equals("metadata"))
1114      {
1115        if(debugOut_) System.out.print(" .");
1116        try
1117        {
1118          nu.xom.Elements releaseListElems = rootElem.getChildElements();
1119          nu.xom.Element releaseListElem = releaseListElems.get(0);
1120          if (releaseListElem!=null )
1121          {
1122            if(debugOut_) System.out.print(" .");
1123            nu.xom.Elements releaseElems = releaseListElem.getChildElements();
1124            nu.xom.Element releaseElem = null;
1125            releaseElem = releaseElems.get(releaseIndex);  //release
1126
1127            if(debugOut_) System.out.print(" . found: "+releaseElem.getLocalName());
1128            retVal = releaseElem;
1129          }
1130        }
1131        catch (Exception ex)
1132        {
1133          System.out.print(" NOT found: ["+releaseIndex+"]");
1134          retVal = null;
1135          ex.printStackTrace();
1136        }
1137      }
1138    }
1139    if(debugOut_) System.out.println("\n  >>>>>>>>>>>>>>>>>>>>>>>>>>");
1140   return retVal;
1141  }
1142
1143
1144  /**
1145    * Parses through the Release search results doc, gets the 1st release
1146    * and looks for the status subElement.
1147    *
1148    * @return null if not found, else t returns the elementName's value
1149    **/
1150  public String parseSearchResultsForFirstReleaseStatus(nu.xom.Document respDoc)
1151  {
1152    return parseSearchResultsForReleaseStatus(respDoc, 0);
1153  }
1154
1155
1156  /**
1157    * Parses through the Release search results doc, gets the 'releaseIndex' release element
1158    * and looks for the status subElement.
1159    * see <a href="https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search#Release">https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search#Release</a>
1160    *
1161    * @param respDoc is the MB responseResult XML document from a release query request
1162    * @param releaseIndex is the zero based release element' status sub-element to return
1163    * @return null if not found, else t returns the elementName's value
1164    **/
1165  public String parseSearchResultsForReleaseStatus(nu.xom.Document respDoc, int releaseIndex)
1166  {
1167    return parseSearchResultsForReleaseSubElement(respDoc, releaseIndex, "status");
1168  }
1169
1170
1171  /**
1172    * Parses through the Release search results doc, gets the 'releaseIndex' release element
1173    * and looks for its MRID.
1174    * see <a href="https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search">https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search</a>
1175    *
1176    * @param respDoc is the MB responseResult XML document from a release query request
1177    * @param releaseIndex is the zero based release to return
1178    * @return null if not found, else t returns the elementName's value
1179    **/
1180  public String parseSearchResultsForMRID(nu.xom.Document respDoc, int releaseIndex)
1181  {
1182    return parseSearchResultsForReleaseElem(respDoc, releaseIndex).getAttributeValue("id");
1183  }
1184
1185
1186  /**
1187    * Parses through the Release search results doc, gets the 1st release element
1188    * and looks for the subElement named elemntName passed into this method.
1189    * see <a href="https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search">https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search</a>
1190    *
1191    * @return null if not found, else t returns the elementName's value
1192    **/
1193  public String parseSearchResultsForFirstReleaseSubElement(nu.xom.Document respDoc, String elementName)
1194  {
1195    return parseSearchResultsForReleaseSubElement(respDoc, 0, elementName);
1196  }
1197
1198
1199  /**
1200    * Parses through the search results doc, gets the releaseIndex specified release
1201    * and looks for the subElement named elemntName passed into this method.
1202    * The request looks like this...<br>
1203    *  <a href="http://musicbrainz.org/ws/2/release/?query=release:Schneider%20AND%20Shake">http://musicbrainz.org/ws/2/release/?query=release:Schneider%20AND%20Shake</a><br>
1204    * or this... <a href="http://musicbrainz.org/ws/2/release/?query=country:US">http://musicbrainz.org/ws/2/release/?query=release:Schneider%20AND%20Shake%20AND%20country:US</a><br>
1205    * or this... <a href="http://musicbrainz.org/ws/2/release/?query=release:X%20AND%20country:CA%20AND%20artist:Inxs">http://musicbrainz.org/ws/2/release/?query=release:X%20AND%20country:CA%20AND%20artist:Inxs</a><br>
1206    *<br>
1207    * see <a href="https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search#Release">https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search#Release</a>
1208    *
1209    * <ul><li>title</li><li>status</li><li>packaging</li><li>text-representation</li><li>artist-credit</li><li>release-group</li>
1210    * <li>date</li><li>country</li><li>release-event-list</li><li>barcode</li><li>label-info-list</li><li>medium-list</li>
1211    * <li> more at <a href="https://wiki.musicbrainz.org/Development/XML_Web_Service/Version_2/Search#Release"> Musicbrainz search relese page</a></li>
1212    * </ul>
1213    *
1214    * @param respDoc is the MB responseResult XML document from a release query request
1215    * @param releaseIndex is the zero based release to return
1216    * @param elementName the sub-element name to look for
1217    * @return null if not found, else t returns the elementName's value
1218    **/
1219  public String parseSearchResultsForReleaseSubElement(nu.xom.Document respDoc, int releaseIndex, String elementName)
1220  {
1221    String retVal = null;
1222    nu.xom.Element rootElem = null;
1223
1224    if(debugOut_) System.out.println(" > parseSearchResultsForReleaseSubElement not null?"+(respDoc!=null));
1225    if (respDoc!=null)
1226    {
1227      rootElem = respDoc.getRootElement();
1228      // load the data
1229      if (rootElem != null && rootElem instanceof Element && rootElem.getLocalName().equals("metadata"))
1230      {
1231        if(debugOut_) System.out.print(" .");
1232        nu.xom.Elements releaseListElems = rootElem.getChildElements();
1233        nu.xom.Element releaseListElem = releaseListElems.get(0);
1234        if (releaseListElem!=null )
1235        {
1236          if(debugOut_) System.out.print(" .");
1237          nu.xom.Elements releaseElems = releaseListElem.getChildElements();
1238          nu.xom.Element releaseElem = null;
1239          //for (int i=0; i< releaseElems.size(); i++)
1240          //{
1241          releaseElem = releaseElems.get(releaseIndex);  //release-event-list
1242          if (releaseElem!=null )
1243          {
1244            if(debugOut_) System.out.print(" .");
1245            if(debugOut_) System.out.print(" ( "+releaseElem.getLocalName());
1246            nu.xom.Elements releaseSubElems = releaseElem.getChildElements();
1247            nu.xom.Element subElem = null;
1248            boolean foundIt = false;
1249            for (int i=0; i< releaseSubElems.size(); i++)
1250            {
1251              subElem = releaseSubElems.get(i);
1252              if(subElem.getLocalName().equalsIgnoreCase(elementName))
1253              {
1254                foundIt = true;
1255                i=releaseSubElems.size();
1256              }
1257            }
1258            if (foundIt)
1259            {
1260              if(debugOut_) System.out.print("/"+elementName+" ) - " );
1261              String elemValueStr = subElem.getValue();
1262              if(debugOut_) System.out.print(" "+elemValueStr);
1263              retVal = elemValueStr;
1264            }
1265          }
1266        }
1267      }
1268    }
1269    if(debugOut_) System.out.println("\n  >>>>>>>>>>>>>>>>>>>>>>>>>>");
1270   return retVal;
1271  }
1272
1273
1274  /**
1275   * commandLine command executor method for the default rest Command.
1276   * It treats each arg as a part of a single rest command and passes it along to the ISY.
1277   * @param args the array of commandLine args that got passed in
1278   **/
1279  protected void restCMD(String [] args)
1280  {
1281    final String methodName = CLASSNAME + ": restCMD(String [])";
1282    // Parse the command
1283    String allcommands = args[0];
1284    for (int i=1;i< args.length;i++) allcommands+=" "+args[i];
1285    if (debugOut_) System.out.print("Sending MusicBrainz Rest Service: "+allcommands);
1286    String passedCommand = (allcommands.startsWith(restUrlPath_+"/")?allcommands.substring(restUrlPath_.length()):allcommands);
1287    if(debugOut_) System.out.println(" ("+passedCommand+")");
1288    passedCommand = (passedCommand.startsWith("/")?passedCommand:"/"+passedCommand);
1289    StringBuilder resp =  serviceGet(passedCommand);
1290    if (resp!=null)
1291    {
1292      System.out.println(responseIndenter(resp).toString());
1293      System.out.println();
1294    }
1295    else
1296    {
1297      System.out.println("Response Error");
1298      System.out.println();
1299    }
1300
1301  }
1302
1303
1304  /**
1305   * Template method for future commandLine command executor methods.
1306   * @param args the array of commandLine args that got passed in
1307   **/
1308  protected void templateCMD(String [] args)
1309  {
1310    final String methodName = CLASSNAME + ": testCMD(String [])";
1311
1312  }
1313
1314
1315    /** gets the help as a String.
1316   * @return the helpMsg in String form
1317   **/
1318  protected static String getHelpMsgStr() {return getHelpMsg().toString();}
1319
1320
1321  /** Makes the JSON string pretty with indenting. **/
1322  public static String prettyJson(String jsonStr)
1323  {
1324    String retVal = jsonStr;
1325    retVal = retVal.replace("[", " [\n");
1326    retVal = retVal.replace("]", "  ]" + SYSTEM_LINE_SEPERATOR);
1327    retVal = retVal.replace("]\n\"", "  ]\"" + SYSTEM_LINE_SEPERATOR);
1328    retVal = retVal.replace("{", "  {" + SYSTEM_LINE_SEPERATOR + "      ");
1329    retVal = retVal.replace("}", "}" + SYSTEM_LINE_SEPERATOR);
1330    retVal = retVal.replace(",", "," + SYSTEM_LINE_SEPERATOR + "      ");
1331    retVal = retVal.replace("}" + SYSTEM_LINE_SEPERATOR + "," + SYSTEM_LINE_SEPERATOR, "    }," + SYSTEM_LINE_SEPERATOR);
1332    retVal = retVal.replace("[\n", "  [ ");
1333    retVal = retVal.replace("        {", "    {");
1334    retVal = retVal.replace("[   {", "[\n   {");
1335    retVal = retVal.replace("\n}", "\n    }");
1336    return retVal;
1337  }
1338
1339
1340
1341  /** initializes and gets the helpMsg_
1342  class var.
1343   * @return the class var helpMsg_
1344   **/
1345  protected static StringBuilder getHelpMsg()
1346  {
1347    helpMsg_ = new StringBuilder(SYSTEM_LINE_SEPERATOR);
1348    helpMsg_.append("---  WebARTS "+CLASSNAME+" Class  -----------------------------------------------------");
1349    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1350    helpMsg_.append("--- + $Revision: 1219 $ $Date: 2020-03-02 22:12:45 -0800 (Fri, 02 Mar 2020) $ ---");
1351    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1352    helpMsg_.append("-------------------------------------------------------------------------------");
1353    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1354    helpMsg_.append("WebARTS ca.bc.webarts.tools.musicbrainz.MusicbrainzRestRequester Class");
1355    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1356    helpMsg_.append("SYNTAX:");
1357    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1358    helpMsg_.append("   java ");
1359    helpMsg_.append(CLASSNAME);
1360    helpMsg_.append(" [-xml or -json]");
1361    helpMsg_.append(" [-u MBuserid -p passwd]");
1362    helpMsg_.append(" command or {restCommand}");
1363    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1364    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1365    helpMsg_.append("Available commands:");
1366    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1367    helpMsg_.append("    test");
1368    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1369    helpMsg_.append("           runs a few default test commands");
1370    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1371    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1372    helpMsg_.append("    parseDir");
1373    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1374    helpMsg_.append("           Uses current dir as starting point and");
1375    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1376    helpMsg_.append("           Creates MusicBrainz Meta-data files for all Artist subdirectories.");
1377    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1378    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1379    helpMsg_.append("    parseArtistDir");
1380    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1381    helpMsg_.append("           Uses current dir as starting point and");
1382    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1383    helpMsg_.append("           Creates MusicBrainz Meta-data files for all Album subdirectories.");
1384    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1385    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1386    helpMsg_.append("    searchArtist ArtistName");
1387    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1388    helpMsg_.append("           duh");
1389    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1390    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1391    helpMsg_.append("    searchRelease \"ArtistName\" ReleaseName");
1392    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1393    helpMsg_.append("           duh");
1394    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1395    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1396    helpMsg_.append("    lookupRelease MBID");
1397    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1398    helpMsg_.append("           looks-up the release with Musicbrain ID = MBID");
1399    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1400    helpMsg_.append("           example:   lookupRelease "+DEFAULT_MUSICBRAINZ_TEST_RELEASE_MRID);
1401    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1402    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1403    helpMsg_.append("    lookupArtist MBID");
1404    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1405    helpMsg_.append("           looks-up the release with Musicbrain ID = MBID");
1406    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1407    helpMsg_.append("           example:   lookupArtist "+DEFAULT_MUSICBRAINZ_TEST_ARTIST_MRID);
1408    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1409    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1410    helpMsg_.append("Available restCommands:");
1411    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1412    helpMsg_.append("    see: https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2");
1413    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1414    helpMsg_.append("  Example: java ca.bc.webarts.tools.musicbrainz.MusicbrainzRestRequester test ");
1415    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1416    helpMsg_.append("---------------------------------------------------------");
1417    helpMsg_.append("----------------------");
1418    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1419
1420    return helpMsg_;
1421  }
1422
1423} // MusicbrainzRestRequester