View Javadoc

1   /*$Id: DefaultMetadataService.java,v 1.13 2006/03/17 17:50:58 clq2 Exp $
2    * Created on 21-May-2004
3    *
4    * Copyright (C) AstroGrid. All rights reserved.
5    *
6    * This software is published under the terms of the AstroGrid 
7    * Software License version 1.2, a copy of which has been included 
8    * with this distribution in the LICENSE.txt file.  
9    *
10   **/
11  package org.astrogrid.applications.manager;
12  
13  import org.astrogrid.applications.CeaException;
14  import org.astrogrid.applications.beans.v1.ApplicationBase;
15  import org.astrogrid.applications.beans.v1.ApplicationList;
16  import org.astrogrid.applications.contracts.Configuration;
17  import org.astrogrid.applications.description.ApplicationDescription;
18  import org.astrogrid.applications.description.ApplicationDescriptionLibrary;
19  import org.astrogrid.applications.description.DescriptionUtils;
20  import org.astrogrid.applications.description.exception.ApplicationDescriptionNotFoundException;
21  import org.astrogrid.applications.description.registry.IvornUtil;
22  import org.astrogrid.common.bean.v1.Namespaces;
23  import org.astrogrid.component.descriptor.ComponentDescriptor;
24  import org.astrogrid.registry.beans.v10.cea.ApplicationDefinition;
25  import org.astrogrid.registry.beans.v10.cea.CeaApplicationType;
26  import org.astrogrid.registry.beans.v10.cea.CeaServiceType;
27  import org.astrogrid.registry.beans.v10.cea.ManagedApplications;
28  import org.astrogrid.registry.beans.v10.cea.Parameters;
29  import org.astrogrid.registry.beans.v10.resource.AccessURL;
30  import org.astrogrid.registry.beans.v10.wsinterface.VOResources;
31  import org.astrogrid.test.AstrogridAssert;
32  import org.astrogrid.test.schema.SchemaMap;
33  
34  import org.apache.axis.utils.XMLUtils;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.exolab.castor.xml.CastorException;
38  import org.exolab.castor.xml.MarshalException;
39  import org.exolab.castor.xml.Marshaller;
40  import org.exolab.castor.xml.Unmarshaller;
41  import org.exolab.castor.xml.ValidationException;
42  import org.w3c.dom.Document;
43  import org.xml.sax.InputSource;
44  
45  import java.io.IOException;
46  import java.io.InputStream;
47  import java.io.StringReader;
48  import java.io.StringWriter;
49  import java.net.URL;
50  
51  import javax.xml.parsers.DocumentBuilder;
52  import javax.xml.parsers.DocumentBuilderFactory;
53  import javax.xml.parsers.FactoryConfigurationError;
54  import javax.xml.parsers.ParserConfigurationException;
55  import javax.xml.transform.Result;
56  import javax.xml.transform.Source;
57  import javax.xml.transform.Templates;
58  import javax.xml.transform.Transformer;
59  import javax.xml.transform.TransformerConfigurationException;
60  import javax.xml.transform.TransformerException;
61  import javax.xml.transform.TransformerFactory;
62  import javax.xml.transform.TransformerFactoryConfigurationError;
63  import javax.xml.transform.dom.DOMResult;
64  import javax.xml.transform.dom.DOMSource;
65  import javax.xml.transform.stream.StreamResult;
66  import javax.xml.transform.stream.StreamSource;
67  
68  import junit.framework.Test;
69  import junit.framework.TestCase;
70  
71  /***
72   * Standard implementation of the
73   * {@link org.astrogrid.applications.manager.MetadataService}component.
74   * 
75   * @author Noel Winstanley nw@jb.man.ac.uk 21-May-2004
76   * @author pharriso@eso.org 02-Jun-2005
77   * @TODO - might want some more thought wrt overriding in other cards.
78    */
79  public class DefaultMetadataService implements MetadataService,
80        ComponentDescriptor {
81     private static final Log logger = LogFactory
82           .getLog(DefaultMetadataService.class);
83  
84     private static final String FORMATTER_XSL = "registryFormatter.xsl";
85  
86     /*** configuration settings */
87     private Configuration configuration;
88  
89     /*** library to generate description for */
90     private final ApplicationDescriptionLibrary lib;
91  
92  
93     /***
94      * Construct a new DefaultMetadataService
95      * 
96      * @param lib
97      *           The library of descriptions for which to build a registry entry.
98      * @param urls
99      *           URLs needed for configuration.
100     */
101    public DefaultMetadataService(ApplicationDescriptionLibrary lib,
102                                  Configuration configuration) {
103      this.lib = lib;
104      this.configuration = configuration;
105    }
106 
107    /***
108     * Create the registry entry....
109     * 
110     * @return a VOResources.
111     */
112    public VOResources makeEntry() 
113        throws ApplicationDescriptionNotFoundException,
114               MarshalException,
115               ValidationException, 
116               ParserConfigurationException,
117               FactoryConfigurationError, 
118               TransformerException, 
119               IOException {
120      
121       // Make a new template, parsing the input file. This picks up any
122       // recent changes to the template file.
123       VOResources template = this.makeTemplate();
124       
125       VOResources vodesc = new VOResources();
126       CeaApplicationType applicationTemplate = (CeaApplicationType) template
127             .getResource(0);
128       CeaServiceType serviceTemplate = (CeaServiceType) template.getResource(1);
129 
130       CeaServiceType service = cloneTemplate(serviceTemplate);
131       ManagedApplications managedApplications = new ManagedApplications();
132       service.setManagedApplications(managedApplications);
133       ApplicationList applist = makeApplist(lib);
134       //add each of the application definitions.
135       for (int i = 0; i < applist.getApplicationDefnCount(); i++) {
136 
137          ApplicationBase theapp = applist.getApplicationDefn(i);
138          ApplicationDescription theAppDesc = lib.getDescription(theapp
139                .getName());
140 
141          if (theapp.getName() != null) { //TODO this test is only here to get
142                                          // round a bug in the container, where
143                                          // a null application seems to be
144                                          // instantiated.
145             CeaApplicationType appentry = makeApplicationEntry(
146                   applicationTemplate, theapp);
147 
148             appentry.getContent()
149                   .setDescription(theAppDesc.getAppDescription());
150             appentry.getContent().setReferenceURL(theAppDesc.getReferenceURL());
151             appentry.setTitle(theAppDesc.getUIName());
152             //TODO getting short name from ui name - probably not appropriate
153             String shortname = theAppDesc.getUIName();
154             if(shortname.length() > 16)
155             {
156                logger.warn("truncating "+shortname+"to 16 characters to fit in VO shortname");
157                shortname = shortname.substring(0, 15);
158             }
159             appentry.setShortName(shortname);
160             vodesc.addResource(appentry);
161             //add this application to the list of managed applications.
162             managedApplications.addApplicationReference(appentry
163                   .getIdentifier());
164          }
165 
166       }
167       //add the service description
168       AccessURL accessurl = new AccessURL();
169       accessurl.setContent(this.configuration.getServiceEndpoint().toString());
170       service.get_interface(0).setAccessURL(accessurl);
171       vodesc.addResource(service);
172       return vodesc;
173 
174    }
175 
176    /***
177     * Create and populate a new application entry.
178     * 
179     * @param template
180     *           The template on which the general application information in the
181     *           entry is based.
182     * @param app
183     *           Specific application information to be added to the entry.
184     * @return
185     * @throws MarshalException
186     * @throws ValidationException
187     */
188    private CeaApplicationType makeApplicationEntry(CeaApplicationType template,
189          ApplicationBase app) throws MarshalException, ValidationException {
190       
191       CeaApplicationType entry = cloneTemplate(template);
192       //FIXME the identifier needs to be rationalized...
193       entry.setIdentifier(makeIvorn(IvornUtil.extractAuthorityFragment(app
194             .getName()), IvornUtil.extractIDFragment(app.getName())));
195       //TODO need to get the long description in here too
196       ApplicationDefinition applicationDefinition = new ApplicationDefinition();
197       // set the interfaces - easy it is the same type...
198       applicationDefinition.setInterfaces(app.getInterfaces());
199 
200       //parameters not quite so nice REFACTORME - perhaps the schema could be
201       // refactored....
202       Parameters regpar = new Parameters();
203       regpar.setParameterDefinition(app.getParameters().getParameter());
204       applicationDefinition.setParameters(regpar);
205 
206       entry.setApplicationDefinition(applicationDefinition);
207       return entry;
208    }
209 
210    /***
211     * Create a clone of the given object. Does this by using the castor
212     * marshalling/unmarshalling on the object.
213     * 
214     * @param app
215     * @return
216     * @throws MarshalException
217     * @throws ValidationException
218     */
219    private CeaApplicationType cloneTemplate(CeaApplicationType app)
220          throws MarshalException, ValidationException {
221       StringWriter sw = new StringWriter();
222       CeaApplicationType newapp = null;
223       app.marshal(sw);
224       StringReader sr = new StringReader(sw.toString());
225       InputSource is = new InputSource(sr);
226       Unmarshaller um = new Unmarshaller(CeaApplicationType.class);
227       newapp = (CeaApplicationType) um.unmarshal(is);
228 
229       return newapp;
230 
231    }
232 
233    /***
234     * Create a clone of the given object. Does this by using the castor
235     * marshalling/unmarshalling on the object.
236     * 
237     * @param serv
238     * @return
239     * @throws MarshalException
240     * @throws ValidationException
241     */
242    private CeaServiceType cloneTemplate(CeaServiceType serv)
243          throws MarshalException, ValidationException {
244       CeaServiceType newserv = null;
245       StringWriter sw = new StringWriter();
246       serv.marshal(sw);
247       //need to put in the castor hack to make the interface a web service type
248       String sb = sw
249             .toString()
250             .replaceAll(
251                   "xsi:type=\"WebService\"",
252                   "xsi:type=\"java:org.astrogrid.registry.beans.v10.resource.dataservice.WebService\"");
253 
254       StringReader sr = new StringReader(sb);
255       InputSource is = new InputSource(sr);
256       Unmarshaller um = new Unmarshaller(CeaServiceType.class);
257       newserv = (CeaServiceType) um.unmarshal(is);
258 
259       return newserv;
260    }
261 
262    /***
263     * Make an ApplicationList from a full configuration. This is a deep copy -
264     * new instances of the ApplicationBase objecst are created.
265     * 
266     * @param clecConfig
267     *           a configuration for a command line execution controller
268     * @return
269     * @TODO might be better to refactor the original schema so that there was a
270     *       base type for the common execution contoller configs...
271     */
272    private ApplicationList makeApplist(ApplicationDescriptionLibrary lib)
273          throws ApplicationDescriptionNotFoundException {
274       ApplicationList result = new ApplicationList();
275       String names[] = lib.getApplicationNames();
276       for (int i = 0; i < names.length; i++) {
277          ApplicationDescription descr = lib.getDescription(names[i]);
278          ApplicationBase base = DescriptionUtils
279                .applicationDescription2ApplicationBase(descr);
280          result.addApplicationDefn(base);
281       }
282       return result;
283    }
284    
285    /***
286     * Gets a URL leading to the current registration-template. The
287     * location of the template is set during construction.
288     */
289    public URL getRegistrationTemplate() {
290      return this.configuration.getRegistryTemplate();
291    }
292    
293    /***
294     * This should potentially be overriden by subclasses.
295     * @see org.astrogrid.applications.component.ProvidesVODescription#getDescription()
296     * @todo could cache the result.
297     */
298    public VOResources getVODescription() throws Exception {
299       return makeEntry();
300    }
301 
302    /***
303     * loads template fron url, builds objects from it. Has to "hack" the
304     * template to change it from being valid to allow castor to actually read
305     * it.
306     * 
307     * @throws IOException
308     * @throws TransformerException
309     * @throws FactoryConfigurationError
310     * @throws ParserConfigurationException
311     */
312    private VOResources makeTemplate() 
313        throws MarshalException,
314               ValidationException, 
315               ParserConfigurationException,
316               FactoryConfigurationError, 
317               TransformerException, 
318               IOException {
319       logger.info("Registry template is read from " + 
320                   this.configuration.getRegistryTemplate());
321       String hackedtemplate
322           = transformTemplateForCastor(this.configuration.getRegistryTemplate().openStream(), 
323                                        this.getClass().getResourceAsStream("/CastorHacker.xsl"));
324 
325       Unmarshaller um = new Unmarshaller(VOResources.class);
326       um.setIgnoreExtraAttributes(true);
327       um.setIgnoreExtraElements(true);
328       StringReader sr = new StringReader(hackedtemplate);
329       InputSource is = new InputSource(sr);
330       VOResources temp = (VOResources) um.unmarshal(is);
331       return temp;
332    }
333 
334    private String makeIvorn(String auth, String id) {
335       StringBuffer sb = new StringBuffer("ivo://");
336       sb.append(auth);
337       sb.append("/");
338       sb.append(id);
339       return sb.toString();
340    }
341 
342    /***
343     * takes an xml valid template and "hacks" to form that castor can read. This
344     * involves setting xsi:type=java:class for each derived class of Resource,
345     * and is achieved via an xsl translation in an external file.
346     * 
347     * @param in
348     * @param inxsl
349     * @return
350     * @throws ParserConfigurationException
351     * @throws FactoryConfigurationError
352     * @throws TransformerException
353     */
354    private String transformTemplateForCastor(InputStream in, InputStream inxsl)
355          throws ParserConfigurationException, FactoryConfigurationError,
356          TransformerException {
357       // Create transformer factory
358       TransformerFactory factory = TransformerFactory.newInstance();
359 
360       // Use the factory to create a template containing the xsl file
361       Templates template = factory.newTemplates(new StreamSource(inxsl));
362 
363       // Use the template to create a transformer
364       Transformer xformer = template.newTransformer();
365 
366       // Prepare the input file
367       Source source = new StreamSource(in);
368 
369       //
370       StringWriter sw = new StringWriter();
371 
372       // Create a new document to hold the results
373       Result result = new StreamResult(sw);
374 
375       // Apply the xsl file to the source file and create the DOM tree
376       xformer.transform(source, result);
377       return sw.toString();
378    }
379 
380 
381    /***
382     * This is final because the intention is that all differences between implementations are expressed by overriding @link DefaultMetadataService#getVODescription()
383     * @see org.astrogrid.applications.manager.MetadataService#returnRegistryEntry()
384     */
385    public final Document returnRegistryEntry() throws CeaException {
386 
387       InputStream formatterXSL = null;
388       Document finalDoc = null;
389 
390       try {
391         
392          // Serialize the registration metadata into an XML document using
393          // Castor. Note that the metadata are actually accessed on the very
394          // last call of this block, via getVODescription(); everything else
395          // in the block is setting up Castor. Leave the serialized XML in 
396          // the StringWriter sw.
397          DocumentBuilder builder = DocumentBuilderFactory.newInstance()
398                .newDocumentBuilder();
399          StringWriter sw = new StringWriter(1000);
400          Marshaller marshaller = new Marshaller(sw);
401          marshaller.setDebug(true);
402          marshaller.setMarshalExtendedType(true);
403          marshaller.setSuppressXSIType(false);
404          marshaller.setMarshalAsDocument(true);
405          //     TODO write a castor wiki page about this.... it is useful to stop
406          // castor putting namespace declarations all over the place, and
407          // essential to get the correct declarations inserted for some derived
408          // types, which castor does not do properly.
409          marshaller.setNamespaceMapping("cea", Namespaces.VOCEA);
410          marshaller.setNamespaceMapping("vr", Namespaces.VORESOURCE);
411          marshaller.setNamespaceMapping("ceapd", Namespaces.CEAPD);
412          marshaller.setNamespaceMapping("ceab", Namespaces.CEAB);
413          marshaller.setNamespaceMapping("vs", Namespaces.VODATASERVICE);
414          marshaller.marshal(this.getVODescription());
415 
416          // Transform the contents of sw to make a cleaned-up registration.
417          // Extract the result as a DOM.
418          TransformerFactory fac = TransformerFactory.newInstance();
419          String xslpath = DefaultMetadataService.class.getPackage() + FORMATTER_XSL;
420          formatterXSL = DefaultMetadataService.class.getResourceAsStream(FORMATTER_XSL);
421          Source formatter = new StreamSource(formatterXSL);
422          Templates xsltTemplate = fac.newTemplates(formatter);
423          Transformer xformer = xsltTemplate.newTransformer();
424          StringReader sr = new StringReader(sw.toString());
425          Source source = new StreamSource(sr);
426          finalDoc = builder.newDocument();
427          Result result = new DOMResult(finalDoc);
428          xformer.transform(source, result);
429       } catch (Exception e) {
430          logger.error("could not marshal VODescription", e);
431          throw new CeaException("could not marshal VODescription", e);
432       }
433 
434       return finalDoc;
435    }
436 
437    /***
438     * @see org.astrogrid.component.descriptor.ComponentDescriptor#getName()
439     */
440    public String getName() {
441       return "Standard CEA Server Description";
442    }
443 
444    /***
445     * @see org.astrogrid.component.descriptor.ComponentDescriptor#getDescription()
446     */
447    public String getDescription() {
448       StringBuffer sb = new StringBuffer("Standard implementation of the service description component`n");
449       try {
450          Document doc = this.returnRegistryEntry();
451          StringWriter sw = new StringWriter();
452          XMLUtils.PrettyDocumentToWriter(doc, sw);
453          sb.append("VODescription \n" + XMLUtils.xmlEncodeString(sw.toString()));
454       } catch (Exception e) {
455          sb.append( "Could not display description: " + e.getMessage());
456       }
457 
458       return sb.toString();
459    }
460 
461    /***
462     * @see org.astrogrid.component.descriptor.ComponentDescriptor#getInstallationTest()
463     */
464    public Test getInstallationTest() {
465       return new InstallationTest("testGetRegistryEntry");
466    }
467 
468    public class InstallationTest extends TestCase {
469 
470       public InstallationTest(String arg0) {
471          super(arg0);
472       }
473 
474       public void testGetRegistryEntry() throws Exception {
475          Document entry = returnRegistryEntry();
476          assertNotNull(entry);
477          AstrogridAssert.assertSchemaValid(entry, "VOResources", SchemaMap.ALL);
478       }
479 
480    }
481 }
482 
483 /*
484  * $Log: DefaultMetadataService.java,v $
485  * Revision 1.13  2006/03/17 17:50:58  clq2
486  * gtr_1489_cea correted version
487  *
488  * Revision 1.11  2006/03/07 21:45:26  clq2
489  * gtr_1489_cea
490  *
491  * Revision 1.8.38.5  2006/01/26 13:19:04  gtr
492  * Refactored.
493  *
494  * Revision 1.8.38.4  2006/01/26 11:03:10  gtr
495  * The URLs for configuring the metadata services are now supplied by the configuration service.
496  *
497  * Revision 1.8.38.3  2005/12/22 10:13:26  gtr
498  * I removed unused methods.
499  *
500  * Revision 1.8.38.2  2005/12/21 17:45:42  gtr
501  * I changed the dataflow so that the template document is reloaded each time a registration document is produced.
502  *
503  * Revision 1.8.38.1  2005/12/21 14:44:35  gtr
504  * Changed to make the registration template available through the InitServlet.
505  *
506  * Revision 1.8  2005/07/05 08:27:00  clq2
507  * paul's 559b and 559c for wo/apps and jes
508  *
509  * Revision 1.7.84.2  2005/06/14 09:49:32  pah
510  * make the http cec only register itself as a ceaservice - do not try to reregister any cea applications that it finds
511  *
512  * Revision 1.7.84.1  2005/06/09 08:47:32  pah
513  * result of merging branch cea_pah_559b into HEAD
514  *
515  * Revision 1.7.70.4  2005/06/08 22:10:45  pah
516  * make http applications v10 compliant
517  *
518  * Revision 1.7.70.3  2005/06/03 16:01:48  pah
519  * first try at getting commandline execution log bz#1058
520  *
521  * Revision 1.7.70.2  2005/06/02 14:57:28  pah
522  * merge the ProvidesVODescription interface into the MetadataService interface
523  * Revision 1.7.70.1 2005/05/31 12:58:24
524  * pah moved to v10 schema interpretation - this means that the authorityID is
525  * read directly with the applicaion "name"
526  * 
527  * Revision 1.7 2004/10/08 20:01:51 pah make the registry entry more "registry
528  * ready" - make sure namespaces are ok
529  * 
530  * Revision 1.6 2004/09/07 13:29:46 pah made sure that the vr namespace is
531  * declared
532  * 
533  * Revision 1.5 2004/08/28 07:17:34 pah commandline parameter passing - unit
534  * tests ok
535  * 
536  * Revision 1.4 2004/08/17 15:09:20 nw minor improvement on logging
537  * 
538  * Revision 1.3 2004/07/26 12:07:38 nw renamed indirect package to protocol,
539  * renamed classes and methods within protocol package javadocs
540  * 
541  * Revision 1.2 2004/07/01 11:16:22 nw merged in branch
542  * nww-itn06-componentization
543  * 
544  * Revision 1.1.2.2 2004/07/01 01:42:46 nw final version, before merge
545  * 
546  * Revision 1.1.2.1 2004/06/17 09:21:23 nw finished all major functionality
547  * additions to core
548  * 
549  * Revision 1.1.2.1 2004/06/14 08:56:58 nw factored applications into
550  * sub-projects, got packaging of wars to work again
551  * 
552  * Revision 1.1.2.1 2004/05/28 10:23:10 nw checked in early, broken version -
553  * but it builds and tests (fail)
554  *  
555  */