View Javadoc

1   /*
2    * $Id: VoDescriptionServer.java,v 1.18 2006/10/17 10:11:41 clq2 Exp $
3    *
4    * (C) Copyright Astrogrid...
5    */
6   
7   package org.astrogrid.dataservice.metadata;
8   import java.io.IOException;
9   import java.lang.reflect.Constructor;
10  import java.net.URISyntaxException;
11  import java.net.URL;
12  import org.apache.commons.logging.Log;
13  import org.apache.commons.logging.LogFactory;
14  import org.astrogrid.cfg.ConfigFactory;
15  import org.astrogrid.cfg.PropertyNotFoundException;
16  import org.astrogrid.dataservice.metadata.queryable.QueryableResourceReader;
17  import org.astrogrid.dataservice.metadata.v0_10.VoResourceSupport;
18  import org.astrogrid.dataservice.service.cea.CeaResources;
19  import org.astrogrid.dataservice.service.cone.ConeResources;
20  import org.astrogrid.registry.RegistryException;
21  import org.astrogrid.registry.client.RegistryDelegateFactory;
22  //import org.astrogrid.registry.client.DelegateProperties;
23  import org.astrogrid.registry.client.admin.RegistryAdminService;
24  import org.astrogrid.tableserver.test.SampleStarsPlugin;
25  import org.astrogrid.xml.DomHelper;
26  import org.w3c.dom.Document;
27  import org.w3c.dom.Element;
28  import org.w3c.dom.NodeList;
29  import org.xml.sax.SAXException;
30  import org.astrogrid.slinger.ivo.IVORN;
31  import org.astrogrid.test.AstrogridAssert;
32  import org.astrogrid.contracts.SchemaMap;
33  
34  
35  /***
36   * Assembles the various VoResource elements provided by the plugins, and
37   * serves them all up wrapped in a VoDescription element for submitting to registries
38   * @see VoResourceSupport for how resource elements are generated
39   * @see package documentation
40   * <p>
41   * @author M Hill
42   */
43  
44  public class VoDescriptionServer {
45     protected static Log log = LogFactory.getLog(VoDescriptionServer.class);
46     
47     private static Document cache = null;
48     
49     public static final String QUERYABLE_PLUGIN = "datacenter.queryable.plugin";
50     public final static String RESOURCE_PLUGIN_KEY = "datacenter.resource.plugin";
51  
52  /*   
53     public final static String VODESCRIPTION_ELEMENT =
54                 "<VOResources " +
55                     "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
56                     "xmlns:vor='http://www.ivoa.net/xml/VOResource/v0.10' " +
57                     "xmlns='http://www.ivoa.net/xml/VOResource/v0.10' " + //default namespace
58                     ">";
59   */
60     public final static String VODESCRIPTION_ELEMENT =
61           "<vor:VOResources xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"  xmlns:vor=\"http://www.ivoa.net/xml/RegistryInterface/v0.1\" xsi:schemaLocation=\"http://www.ivoa.net/xml/RegistryInterface/v0.1 http://software.astrogrid.org/schema/registry/RegistryInterface/v0.1/RegistryInterface.xsd\">\n";
62  
63     public final static String VODESCRIPTION_ELEMENT_END = "</vor:VOResources>";
64  
65     /***
66      * Returns the whole metadata file as a DOM document
67      */
68     public synchronized static Document getVoDescription() throws IOException {
69        if (cache == null) {
70           try {
71              cache = DomHelper.newDocument(makeVoDescription());
72              
73           }
74           catch (SAXException e) {
75              throw new MetadataException("XML error with Metadata: "+e,e);
76           }
77        }
78        return cache;
79     }
80     
81     /*** Checks that the given document is a valid vodescription, throwing an
82      * exception if not */
83     public static void validateDescription(String vod) throws SAXException, MetadataException {
84        Element root = null;
85        try {
86           root = DomHelper.newDocument(vod).getDocumentElement();
87        }
88        catch (IOException e) {
89           throw new RuntimeException(e);
90        }
91        
92        // Added by KEA: validate the VODescription against schema
93        String rootElement = root.getLocalName();
94        if(rootElement == null) {
95           rootElement = root.getNodeName();
96        }
97        try {
98           AstrogridAssert.assertSchemaValid(root,rootElement,SchemaMap.ALL);
99        }
100       catch (Throwable th) {
101          throw new MetadataException("Resource VODescription does not validate against its schema: "+th.getMessage(), th);
102       }
103 
104       // Perform some manual checks (KEA: remove these?)
105       NodeList children = root.getChildNodes();
106       
107       for (int i = 0; i < children.getLength(); i++) {
108          if (children.item(i) instanceof Element) {
109             Element resource = (Element) children.item(i);
110             
111             if (!resource.getLocalName().equals("Resource")) {
112                throw new MetadataException("VODescription Child "+i+" ("+resource.getNodeName()+") is not a Resource element");
113             }
114             
115             Element idNode = DomHelper.getSingleChildByTagName(resource, "identifier");
116             if (idNode == null) {
117                //no identifier - could add one but we don't know what resource key to give it
118                throw new MetadataException("Resource "+i+" (xsi:type="+resource.getAttribute("xsi:type")+") has no <identifier>");
119             }
120             
121             String configAuth = ConfigFactory.getCommonConfig().getString(VoResourceSupport.AUTHID_KEY);
122             String rawId = DomHelper.getValueOf(idNode).trim();
123             IVORN id = null;
124             try {
125                id = new IVORN(rawId);
126             }
127             catch (URISyntaxException e) {
128                throw new MetadataException("<identifier> '"+rawId+"' is not a valid IVORN: "+e);
129             }
130             
131             if (!id.getAuthority().startsWith(configAuth)) {
132                throw new MetadataException("<identifier> '"+id+"' does not start with configured authority "+configAuth);
133             }
134          }
135       }
136    }
137 
138    /*** Instantiates the class with the given name.  This is useful for things
139     * such as 'plugins', where a class name might be given in a configuration file.
140     * Rather messily throws Throwable because anything might have
141     * gone wrong in the constructor.
142     */
143    public static VoResourcePlugin createVoResourcePlugin(String pluginClassName) {
144       
145       Object plugin = null;
146       
147       try {
148          log.debug("Creating VoResourcePlugin '"+pluginClassName+"'");
149          
150          Class qClass = Class.forName(pluginClassName);
151        
152          /* NWW - interesting bug here.
153           original code used class.newInstance(); this method doesn't declare it throws InvocationTargetException,
154           however, this exception _is_ thrown if an exception is thrown by the constructor (as is often the case at the moment)
155           worse, as InvocatioinTargetException is a checked exception, the compiler rejects code with a catch clause for
156           invocationTargetExcetpion - as it thinks it cannot be thrown.
157           this means the exception boils out of the code, and is unstoppable - dodgy
158           work-around - use the equivalent methods on java.lang.reflect.Constructor - which do throw the correct exceptions */
159          
160          Constructor constr = qClass.getConstructor(new Class[] { });
161          plugin = constr.newInstance(new Object[] { } );
162          
163       }
164       catch (ClassNotFoundException cnfe) {
165          throw new RuntimeException("Could not find metadata plugin class "+pluginClassName);
166       }
167       catch (NoSuchMethodException nsme) {
168          throw new RuntimeException("Bad metadata plugin specified ("+pluginClassName+") - has no zero-argument constructor");
169       }
170       catch (Throwable th) {
171          throw new RuntimeException("Bad metadata plugin specified ("+pluginClassName+")",th);
172       }
173       
174       if (!(plugin instanceof VoResourcePlugin)) {
175          throw new RuntimeException("Bad metadata plugin specified ("+pluginClassName+") - does not implement VoResourcePlugin");
176       }
177       
178       return (VoResourcePlugin) plugin;
179       
180    }
181    
182    /*** Instantiates the class with the given name.  This is useful for things
183     * such as 'plugins', where a class name might be given in a configuration file.
184     * Rather messily throws Throwable because anything might have
185     * gone wrong in the constructor.
186     */
187    public static QueryableResourceReader createQueryablePlugin(String pluginClassName) {
188       
189       Object plugin = null;
190       
191       try {
192          log.debug("Creating Queryable Plugin '"+pluginClassName+"'");
193          
194          Class qClass = Class.forName(pluginClassName);
195        
196          /* NWW - interesting bug here.
197           original code used class.newInstance(); this method doesn't declare it throws InvocationTargetException,
198           however, this exception _is_ thrown if an exception is thrown by the constructor (as is often the case at the moment)
199           worse, as InvocatioinTargetException is a checked exception, the compiler rejects code with a catch clause for
200           invocationTargetExcetpion - as it thinks it cannot be thrown.
201           this means the exception boils out of the code, and is unstoppable - dodgy
202           work-around - use the equivalent methods on java.lang.reflect.Constructor - which do throw the correct exceptions */
203          
204          Constructor constr = qClass.getConstructor(new Class[] { });
205          plugin = constr.newInstance(new Object[] { } );
206          
207       }
208       catch (ClassNotFoundException cnfe) {
209          throw new RuntimeException("Could not find metadata plugin class "+pluginClassName);
210       }
211       catch (NoSuchMethodException nsme) {
212          throw new RuntimeException("Bad metadata plugin specified ("+pluginClassName+") - has no zero-argument constructor");
213       }
214       catch (Throwable th) {
215          throw new RuntimeException("Bad metadata plugin specified ("+pluginClassName+")",th);
216       }
217       
218       if (!(plugin instanceof VoResourcePlugin)) {
219          throw new RuntimeException("Bad metadata plugin specified ("+pluginClassName+") - does not implement VoResourcePlugin");
220       }
221       
222       return (QueryableResourceReader) plugin;
223       
224    }
225    /***
226     * Clears the cache - useful to call before doing a set of operations, forces
227     * metadata to be refreshed from disk.  Not threadsafe...
228     */
229    public static void clearCache() {
230       cache = null;
231    }
232    
233    /***
234     * Make a VODescription document out of all the voResourcePlugins, returning an
235     * unvalidated string.  This means we can view the made (finsihed) docuemnt
236     * separate from the validating process. */
237    public static String makeVoDescription() throws IOException, MetadataException {
238 
239       //get plugin list from config - need to add a default to the common method...
240       Object[] plugins  = null;
241       try {
242          plugins = ConfigFactory.getCommonConfig().getProperties(RESOURCE_PLUGIN_KEY);
243       } catch (PropertyNotFoundException pnfe)
244       {
245          log.warn("No config found for resource plugins, key="+RESOURCE_PLUGIN_KEY);
246          
247          //for backwards compatibility, look for old datacenter.metadata.plugin
248          String s = ConfigFactory.getCommonConfig().getString("datacenter.metadata.plugin",null);
249          if (s != null) {
250             plugins = new String[] { s };
251          }
252       }
253 
254       //start the vodescription document
255       StringBuffer vod = new StringBuffer();
256       vod.append(VODESCRIPTION_ELEMENT+"\n");
257       boolean ceaDone = false; //backwards compatiblity marker to see if CeaResource has been done
258       
259       //loop through plugins adding each one's list of resources
260       if (plugins != null) {
261          for (int p = 0; p < plugins.length; p++) {
262             log.debug("Including Resource plugin "+plugins[p].toString());
263 
264             //make plugin
265             VoResourcePlugin plugin = createVoResourcePlugin(plugins[p].toString());
266             
267             checkAndAppendResource(vod, plugin);
268             
269             if (plugin instanceof CeaResources) { ceaDone = true; }
270          }
271       }
272 
273       //add the standard ones - cea, cone etc
274       if (!ceaDone)
275           {
276           checkAndAppendResource(vod, new CeaResources());
277           }
278 
279       // Only add conesearch if the configuration file says to do so.
280       //
281       String s = ConfigFactory.getCommonConfig().getString(
282           "datacenter.implements.conesearch",null);
283       if ( (s != null) && ( s.equals("true") || s.equals("TRUE") ) ) {
284          checkAndAppendResource(vod, new ConeResources());
285       }
286 
287 //    addResources(vod, new SkyNodeResourceServer());
288       
289       //finish vod element
290       vod.append(VODESCRIPTION_ELEMENT_END);
291 
292       return vod.toString();
293    }
294 
295    public static void checkAndAppendResource(StringBuffer vod, VoResourcePlugin plugin) throws MetadataException, IOException {
296 
297       //get resources from plugin
298       String resources = plugin.getVoResource();
299 
300       try {
301          validateDescription(VODESCRIPTION_ELEMENT+resources+VODESCRIPTION_ELEMENT_END);
302       
303          vod.append(resources+"\n\n");
304       }
305       catch (SAXException e) {
306   //       throw new MetadataException("Plugin "+plugin.getClass()+" generated invalid XML ",e);
307     
308          log.error("Plugin "+plugin.getClass()+" generated invalid XML ",e);
309          vod.append(resources+"\n\n");
310       }
311    }
312 
313    
314    /***
315     * Returns the resource element of the given type eg 'AuthorityID'.
316     * Matches the given string against the attribute 'xsi:type' of the elements
317     * named 'Resource'
318     */
319    public static Element getResource(String type) throws IOException {
320       NodeList resources = getVoDescription().getElementsByTagName("Resource");
321       
322       for (int i = 0; i < resources.getLength(); i++) {
323          Element resource = (Element) resources.item(i);
324          if (resource.getAttribute("xsi:type").equals(type)) {
325             return resource;
326          }
327       }
328       return null; //not found
329    }
330 
331    /***
332     * Sends the voDescription to the registry, returning list of Registries that
333     * it was sent to
334     */
335    public static String[] pushToRegistry() throws IOException, RegistryException {
336       RegistryAdminService service = RegistryDelegateFactory.createAdmin();
337       service.update(getVoDescription());
338       //return new String[] { ConfigFactory.getCommonConfig().getString(DelegateProperties.ADMIN_URL_PROPERTY) };
339       return new String[] { ConfigFactory.getCommonConfig().getString(RegistryDelegateFactory.ADMIN_URL_PROPERTY) };
340    }
341 
342    /***
343     * Sends the voDescription to the given registry URL, returning list of Registries that
344     * it was sent to
345     */
346    public static void pushToRegistry(URL targetRegistry) throws IOException, RegistryException {
347       RegistryAdminService service = RegistryDelegateFactory.createAdmin(targetRegistry);
348       service.update(getVoDescription());
349    }
350 
351    /***
352     * for quick tests etc
353     */
354    public static void main(String[] args) throws RegistryException, IOException
355    {
356       SampleStarsPlugin.initConfig();
357 //    ConfigFactory.getCommonConfig().setProperty("datacenter.url","http://localhost:8080");
358       VoDescriptionServer.pushToRegistry(new URL("http://galahad.star.le.ac.uk:8080/galahad-registry/services/AdminService"));
359    }
360 }
361