001    package edu.nrao.sss.util;
002    
003    import java.io.StringReader;
004    import java.net.URL;
005    import java.util.List;
006    import java.util.ArrayList;
007    import java.rmi.RemoteException;
008    import javax.xml.bind.JAXBElement;
009    import javax.xml.namespace.QName;
010    
011    import org.apache.log4j.Logger;
012    import org.apache.axis.client.Call;
013    import org.apache.axis.client.Service;
014    
015    import edu.nrao.sss.astronomy.sesame.Resolver;
016    import edu.nrao.sss.astronomy.sesame.Sesame;
017    import edu.nrao.sss.astronomy.sesame.Target;
018    
019    /**
020     * The SourceNameResolver encapsulates the networking and XML envolved in
021     * making a source name lookup at the CDS and provides quick javabeans methods
022     * for retreiving a particular bit of information about a source. 
023     *
024     * @author btruitt
025     *
026     */
027    public class SourceNameResolver
028    {
029      private static final Logger log = Logger.getLogger(SourceNameResolver.class);
030    
031      private Call   call = null;
032      private Sesame data = null;
033    
034      private static final String[] serviceUrls = {
035        "http://cdsws.u-strasbg.fr/axis/services/Sesame",
036        "http://vizier.hia.nrc.ca:8080/axis/services/Sesame",
037        "http://vizier.nao.ac.jp:8080/axis/services/Sesame",
038      };
039    
040      private static int CURRENT_RESOURCE = 0;
041    
042      public SourceNameResolver()
043      {
044        try
045        {
046          //Create a service to do the work.
047          Service service = new Service();
048    
049          this.call = (Call)service.createCall();
050    
051          //set the address, method, and parameters of the call. Also specify their types.
052          this.call.setTargetEndpointAddress(new URL(serviceUrls[CURRENT_RESOURCE]));
053    
054          this.call.setOperationName(new QName("sesame"));
055    
056          this.call.addParameter("name", org.apache.axis.Constants.XSD_STRING, javax.xml.rpc.ParameterMode.IN);
057          this.call.addParameter("resultType", org.apache.axis.Constants.XSD_STRING, javax.xml.rpc.ParameterMode.IN);
058          this.call.addParameter("all", org.apache.axis.Constants.XSD_STRING, javax.xml.rpc.ParameterMode.IN);
059          this.call.addParameter("service", org.apache.axis.Constants.XSD_STRING, javax.xml.rpc.ParameterMode.IN);
060    
061          this.call.setReturnType(org.apache.axis.Constants.XSD_STRING);
062    
063          //Shorten the timeout to something more reasonable.(in milliseconds)
064          this.call.setTimeout(new Integer(15000));
065        }
066    
067        //Any other exception we'll just have to barf on and assume we can't
068        //get the source because something else is wrong.
069        catch (Exception e)
070        {
071          throw new IllegalStateException(e);
072        }
073      }
074    
075      /**
076       * Does a name lookup at one of the sites in {@link #serviceUrls} and
077       * returns an object that has convenient java bean methods for accessing
078       * properties of a source. The urls in the serviceUrls array are tried in
079       * order (in the event that the service is down at the first site, the 2nd
080       * is tried, etc.).
081       *
082       * @throws SourceNotFoundException if <code>target</code> could not be found.
083       */
084      public SourceNameResolver(String target) throws SourceNotFoundException
085      {
086        this();
087        lookup(target);
088      }
089    
090      /** Does a circular increment of CURRENT_RESOURCE */
091      private void incrementCurrentResourceCounter()
092      {
093        CURRENT_RESOURCE = ((CURRENT_RESOURCE + 1) < serviceUrls.length)? CURRENT_RESOURCE + 1 : 0;
094      }
095    
096      /**
097       * @return a list of errors found trying to process our request.
098       */
099      public List<String> getErrors()
100      {
101        List<String> errs = new ArrayList<String>();
102    
103        List<Target> targets = this.data.getTarget();
104        if (targets != null)
105        {
106          for (Target t : targets)
107          {
108            List<String> err = t.getERROR();
109            if (err != null)
110              errs.addAll(err);
111    
112            List<String> info = t.getINFO();
113            if (info != null)
114              for (String i : info)
115                if (i.startsWith("!***") || i.startsWith("***"))
116                  errs.add(i);
117          }
118        }
119    
120        return errs;
121      }
122    
123      //XXX temporary!
124      public Sesame getData()
125      {
126        return this.data;
127      }
128    
129      //TODO Method comments
130      public Sesame lookup(String target) throws SourceNotFoundException
131      {
132        //Try all addresses if we have to, but then give up.
133        for (int tries = 0; tries < serviceUrls.length; tries++)
134        {
135          try
136          {
137            //make the call, passing in the arguments as an array of objects. Note
138            //that this cast is safe because I explicitly set the return type
139            //to string.
140            //Note on arguments: first arg is the target name, the second tells
141            //Sesame to return xml, the 3rd to return all aliases of the target,
142            //and the 4th tells it to query Simbad, and then, if not found in
143            //Simbad, query NED.
144            String xml = (String)call.invoke(new Object[] {target, "x", true, "SN"});
145    
146            log.debug("Returned XML:\n" + xml);
147    
148            //Create a Sesame object from the xml
149            JaxbUtility xmlUtil = new JaxbUtility();
150            xmlUtil.setFailIfDefaultSchemaNotFound(false);
151            xmlUtil.setLookForDefaultSchema(false);
152            this.data = xmlUtil.readObjectAsXmlFrom(new StringReader(xml), Sesame.class, null);
153    
154            List<String> errs = getErrors();
155    
156            //if this worked and we haven't thrown an exception, we can break
157            //out of the loop.
158            if (errs == null || errs.isEmpty())
159              break;
160    
161            else
162            {
163              StringBuilder errors = new StringBuilder("Errors found while attempting to resolve '");
164              errors.append(target);
165              errors.append("' @ ");
166              errors.append(serviceUrls[CURRENT_RESOURCE]);
167              errors.append(":\n\t");
168    
169              for (String err : errs)
170              {
171                errors.append(err);
172                errors.append("\n\t");
173              }
174    
175              log.warn(errors.toString());
176              incrementCurrentResourceCounter();
177            }
178          }
179    
180          //Service is broken, try a different one.
181          catch (RemoteException re)
182          {
183            log.warn("Axis service is down @ " + serviceUrls[CURRENT_RESOURCE], re);
184    
185            //increment our place in our round robin attempts to find a working service.
186            incrementCurrentResourceCounter();
187    
188            log.warn("Trying next service @ " + serviceUrls[CURRENT_RESOURCE]);
189          }
190    
191          //Any other exception we'll just have to barf on and assume we can't
192          //get the source because something else is wrong.
193          catch (Exception e)
194          {
195            throw new SourceNotFoundException(e);
196          }
197        }
198    
199    
200        // Only pay attention to the first target as I don't think we can return
201        // more than one target with the way we're using the call.
202        if (this.data != null)
203        {
204          List<Target> targets = this.data.getTarget();
205          if (targets != null && !targets.isEmpty())
206          {
207            List<Resolver> resolvers = targets.get(0).getResolver();
208            if (resolvers != null)
209            {
210              for (Resolver r : resolvers)
211              {
212                // If there's a position, then return our data, we found it!
213                if (hasPosition(r))
214                  return this.data;
215              }
216            }
217          }
218        }
219    
220        //If we didn't get any position information back, there was an error
221        //so we throw a SourceNotFoundException.
222        throw new SourceNotFoundException("Could not find source: " + target);
223      }
224    
225      private boolean hasPosition(Resolver r)
226      {
227        if (r != null)
228        {
229          List<JAXBElement<?>> info = r.getINFOOrERROROrOtype();
230    
231          if (info != null)
232          {
233            for (JAXBElement<?> e : info)
234            {
235              String name = e.getName().getLocalPart();
236              if ("jradeg".equalsIgnoreCase(name))
237                return true;
238            }
239          }
240        }
241    
242        return false;
243      }
244    }