Ignore:
Timestamp:
2011-08-12T09:57:26+12:00 (13 years ago)
Author:
sjm84
Message:

Adding in the server-side code for the Document Maker as well as several other enhancements

File:
1 edited

Legend:

Unmodified
Added
Removed
  • main/trunk/greenstone3/src/java/org/greenstone/gsdl3/collection/Collection.java

    r24361 r24393  
    2323import org.greenstone.gsdl3.service.*;
    2424
    25 
    2625// java XML classes we're using
    27 import org.w3c.dom.Document; 
    28 import org.w3c.dom.Node; 
    29 import org.w3c.dom.Element; 
    30 import org.w3c.dom.NodeList; 
     26import org.w3c.dom.Document;
     27import org.w3c.dom.Node;
     28import org.w3c.dom.Element;
     29import org.w3c.dom.NodeList;
    3130
    3231import java.io.*;
     
    4948
    5049/**
    51  * Represents a collection in Greenstone. A collection is an extension of
    52  * a ServiceCluster - it has local data that the services use.
    53  *
     50 * Represents a collection in Greenstone. A collection is an extension of a
     51 * ServiceCluster - it has local data that the services use.
     52 * 
    5453 * @author <a href="mailto:[email protected]">Katherine Don</a>
    5554 * @see ModuleInterface
    5655 */
    57 public class Collection
    58     extends ServiceCluster {
    59 
    60     static Logger logger = Logger.getLogger(org.greenstone.gsdl3.collection.Collection.class.getName());
    61 
    62     /** is this collection being tidied */
    63     protected boolean useBook = false;
    64     /** is this collection public or private */
    65     protected boolean is_public = true;
    66 
    67     /** does this collection provide the OAI service */
    68     protected boolean has_oai = true;
    69     /** time when this collection was built */
    70     protected long lastmodified = 0;
    71     /** earliestDatestamp of this collection. Necessary for OAI */
    72     protected long earliestDatestamp = 0;
    73 
    74 
    75     /** An element containing the serviceRackList element of buildConfig.xml, used to determine whether it contains
    76      *  the OAIPMH serviceRack
    77      */
    78     //protected Element service_rack_list = null;
    79    
    80     protected XMLTransformer transformer = null;
    81     /** same as setClusterName */
    82     public void setCollectionName(String name) {
    83     setClusterName(name);
    84     }
    85 
    86     public Collection() {
    87     super();
    88     this.description = this.doc.createElement(GSXML.COLLECTION_ELEM);
    89    
    90     }
    91    
    92     /**
    93      * Configures the collection.
    94      *
    95      * gsdlHome and collectionName must be set before configure is called.
    96      *
    97      * the file buildcfg.xml is located in gsdlHome/collect/collectionName
    98      * collection metadata is obtained, and services loaded.
    99      *
    100      * @return true/false on success/fail
    101      */
    102     public boolean configure() {
    103    
    104     if (this.site_home == null || this.cluster_name== null) {
    105         logger.error("Collection: site_home and collection_name must be set before configure called!");
    106         return false;
    107     }
    108    
    109     Element coll_config_xml = loadCollConfigFile();
    110     Element build_config_xml = loadBuildConfigFile();
    111    
    112     if (coll_config_xml==null||build_config_xml==null) {
    113         return false;
    114     }
    115    
    116     // get the collection type attribute
    117     Element search = (Element) GSXML.getChildByTagName(coll_config_xml, GSXML.SEARCH_ELEM);
    118     if(search!=null) {
    119       col_type = search.getAttribute(GSXML.TYPE_ATT);
    120     }
    121    
    122     // process the metadata and display items
    123     findAndLoadInfo(coll_config_xml, build_config_xml);
    124    
    125     // now do the services
    126     configureServiceRacks(coll_config_xml, build_config_xml);
    127 
    128     return true;
    129    
    130     }
    131 
    132     public boolean useBook() {
    133         return useBook;
    134     }
    135    
    136     public boolean isPublic() {
    137     return is_public;
    138     }
    139     // Not used anymore by the OAIReceptionist to find out the earliest datestamp
    140     // amongst all oai collections in the repository. May be useful generally.
    141     public long getLastmodified() {
    142       return lastmodified;
    143     }
    144     //used by the OAIReceptionist to find out the earliest datestamp amongst all oai collections in the repository
    145     public long getEarliestDatestamp() {
    146     return earliestDatestamp;
    147     }
    148 
    149     /** whether the service_map in ServiceCluster.java contains the service 'OAIPMH'
    150      *  11/06/2007 xiao
    151      */
    152     public boolean hasOAI() {
    153       return has_oai;
    154     }
    155     /**
    156      * load in the collection config file into a DOM Element
    157      */   
    158     protected Element loadCollConfigFile() {
    159 
    160     File coll_config_file = new File(GSFile.collectionConfigFile(this.site_home, this.cluster_name));
    161    
    162     if (!coll_config_file.exists()) {
    163         logger.error("Collection: couldn't configure collection: "+this.cluster_name+", "+coll_config_file+" does not exist");
    164         return null;
    165     }
    166     // get the xml for both files
    167     Document coll_config_doc = this.converter.getDOM(coll_config_file, CONFIG_ENCODING);
    168     Element coll_config_elem = null;
    169     if (coll_config_doc != null) {
    170         coll_config_elem = coll_config_doc.getDocumentElement();
    171     }
    172     return coll_config_elem;
    173 
    174     }
    175    
    176     /**
    177      * load in the collection build config file into a DOM Element
    178      */       
    179     protected Element loadBuildConfigFile() {
    180    
    181     File build_config_file = new File(GSFile.collectionBuildConfigFile(this.site_home, this.cluster_name));
    182     if (!build_config_file.exists()) {
    183         logger.error("Collection: couldn't configure collection: "+this.cluster_name+", "+build_config_file+" does not exist");
    184         return null;
    185     }
    186     Document build_config_doc = this.converter.getDOM(build_config_file, CONFIG_ENCODING);
    187     Element build_config_elem = null;
    188     if (build_config_doc != null) {
    189         build_config_elem = build_config_doc.getDocumentElement();
    190     }
    191 
    192     lastmodified = build_config_file.lastModified();
    193  
    194     return build_config_elem;
    195     }
    196 
    197     /**
    198      * find the metadata and display elems from the two config files and add it to the appropriate lists
    199      */
    200     protected boolean findAndLoadInfo(Element coll_config_xml,
    201                       Element build_config_xml){
    202              
    203       // metadata
    204       Element meta_list = (Element)GSXML.getChildByTagName(coll_config_xml, GSXML.METADATA_ELEM+GSXML.LIST_MODIFIER);
    205       addMetadata(meta_list);
    206       meta_list = (Element)GSXML.getChildByTagName(build_config_xml, GSXML.METADATA_ELEM+GSXML.LIST_MODIFIER);
    207       addMetadata(meta_list);
    208      
    209       meta_list = this.doc.createElement(GSXML.METADATA_ELEM + GSXML.LIST_MODIFIER);
    210       GSXML.addMetadata(this.doc, meta_list, "httpPath", this.site_http_address+"/collect/"+this.cluster_name);
    211       addMetadata(meta_list);
    212      
    213       // display stuff
    214       Element display_list = (Element)GSXML.getChildByTagName(coll_config_xml, GSXML.DISPLAY_TEXT_ELEM+GSXML.LIST_MODIFIER);
    215       if (display_list != null) {
    216         resolveMacros(display_list);
    217         addDisplayItems(display_list);
    218       }
    219      
    220       //check whether the html are tidy or not
    221       Element import_list = (Element)GSXML.getChildByTagName(coll_config_xml, GSXML.IMPORT_ELEM);
    222       if (import_list != null) {
    223         Element plugin_list = (Element)GSXML.getChildByTagName(import_list, GSXML.PLUGIN_ELEM+GSXML.LIST_MODIFIER);
    224         addPlugins(plugin_list);
    225         if (plugin_list != null){
    226           Element plugin_elem = (Element)GSXML.getNamedElement(plugin_list, GSXML.PLUGIN_ELEM, GSXML.NAME_ATT, "HTMLPlugin");
    227           if (plugin_elem != null) {
    228             //get the option
    229             Element option_elem = (Element)GSXML.getNamedElement(plugin_elem, GSXML.PARAM_OPTION_ELEM, GSXML.NAME_ATT, "-use_realistic_book");
    230             if (option_elem != null) {
    231               useBook = true;
    232             }
    233           }
    234         }
    235       }
    236       meta_list = this.doc.createElement(GSXML.METADATA_ELEM + GSXML.LIST_MODIFIER);
    237       if (useBook == true)
    238         GSXML.addMetadata(this.doc, meta_list, "tidyoption", "tidy");
    239       else
    240         GSXML.addMetadata(this.doc, meta_list, "tidyoption", "untidy");
    241       addMetadata(meta_list);
    242      
    243       // check whether we are public or not
    244       if (meta_list != null) {
    245         Element meta_elem = (Element) GSXML.getNamedElement(metadata_list, GSXML.METADATA_ELEM, GSXML.NAME_ATT, "public");
    246         if (meta_elem != null) {
    247           String value = GSXML.getValue(meta_elem).toLowerCase().trim();
    248           if (value.equals("false")) {
    249             is_public = false;
    250           }
    251         }
    252       }
    253       return true;
    254 
    255     }
    256 
    257   protected boolean configureServiceRacks(Element coll_config_xml,
    258                       Element build_config_xml){
    259     clearServices();
    260     Element service_list = (Element)GSXML.getChildByTagName(build_config_xml, GSXML.SERVICE_CLASS_ELEM+GSXML.LIST_MODIFIER);
    261     configureServiceRackList(service_list, coll_config_xml);
    262        
    263     // collection Config may also contain manually added service racks
    264     service_list = (Element)GSXML.getChildByTagName(coll_config_xml, GSXML.SERVICE_CLASS_ELEM+GSXML.LIST_MODIFIER);
    265     if (service_list != null) {
    266       configureServiceRackList(service_list, build_config_xml);
    267      
    268       // Check for oai
    269       Element oai_service_rack = GSXML.getNamedElement(service_list, GSXML.SERVICE_CLASS_ELEM, OAIXML.NAME, OAIXML.OAIPMH);
    270       if (oai_service_rack == null) {
    271     has_oai = false;
    272     logger.info("No oai for collection: " + this.cluster_name);
    273    
    274       } else {
    275     has_oai = true;
    276    
    277     // extract earliestDatestamp from the buildconfig.xml for OAI
    278     Element metadata_list = (Element)GSXML.getChildByTagName(build_config_xml, GSXML.METADATA_ELEM+GSXML.LIST_MODIFIER);
    279 
    280     if(metadata_list != null) {
    281         NodeList children = metadata_list.getElementsByTagName(GSXML.METADATA_ELEM);
    282                // can't do getChildNodes(), because whitespace, such as newlines, creates Text nodes
    283         for (int i = 0; i < children.getLength(); i++) {
    284         Element metadata = (Element)children.item(i);
    285         if(metadata.getAttribute(GSXML.NAME_ATT).equals(OAIXML.EARLIEST_DATESTAMP)) {
    286             String earliestDatestampStr = GSXML.getValue(metadata);
    287             if(!earliestDatestampStr.equals("")) {
    288             earliestDatestamp = Long.parseLong(earliestDatestampStr);
    289             }
    290             break; // found a metadata element with name=earliestDatestamp in buildconfig
    291         }
    292         }
    293     }
    294    
    295     // If at the end of this, there is no value for earliestDatestamp, print out a warning
    296     logger.warn("No earliestDatestamp in buildConfig.xml for collection: " + this.cluster_name + ". Defaulting to 0.");
    297 
    298       }
    299     } else { // no list of services (no ServiceRackList), so no oai_service_rack either
    300     // explicitly set has_oai to false here, since it's initialised to true by default
    301     has_oai = false;
    302     }
    303     return true;
    304   }
    305  
    306   protected boolean resolveMacros(Element display_list) {
    307     if (display_list==null) return false;
    308     NodeList displaynodes = display_list.getElementsByTagName(GSXML.DISPLAY_TEXT_ELEM);
    309     if (displaynodes.getLength()>0) {
    310         String http_site = this.site_http_address;
    311         String http_collection = this.site_http_address +"/collect/"+this.cluster_name;
    312         for(int k=0; k<displaynodes.getLength(); k++) {
    313         Element d = (Element) displaynodes.item(k);
    314         String text = GSXML.getNodeText(d);
    315         text = StringUtils.replace(text, "_httpsite_", http_site);
    316         text = StringUtils.replace(text, "_httpcollection_", http_collection);
    317         GSXML.setNodeText(d, text);
    318         }
    319     }
    320     return true;
    321     }
    322     /**
    323      * do a configure on only part of the collection
    324      */
    325     protected boolean configureSubset(String subset) {
    326 
    327     // need the coll config files
    328     Element coll_config_elem = loadCollConfigFile();
    329     Element build_config_elem = loadBuildConfigFile();
    330     if (coll_config_elem == null||build_config_elem == null) {
    331         // wont be able to do any of the requests
    332         return false;
    333     }   
    334    
    335     if (subset.equals(GSXML.SERVICE_ELEM+GSXML.LIST_MODIFIER)) {
    336       return configureServiceRacks(coll_config_elem, build_config_elem);
    337     }
    338 
    339     if (subset.equals(GSXML.METADATA_ELEM+GSXML.LIST_MODIFIER) || subset.equals(GSXML.DISPLAY_TEXT_ELEM+GSXML.LIST_MODIFIER) || subset.equals(GSXML.PLUGIN_ELEM+GSXML.LIST_MODIFIER)) {
    340         return findAndLoadInfo(coll_config_elem, build_config_elem);
    341        
    342     }
    343    
    344     logger.error("Collection: cant process system request, configure "+subset);
    345     return false;
    346     }
    347 
    348  /** handles requests made to the ServiceCluster itself
    349      *
    350      * @param req - the request Element- <request>
    351      * @return the result Element - should be <response>
    352      */
    353     protected Element processMessage(Element request) {
    354 
    355     Element response = this.doc.createElement(GSXML.RESPONSE_ELEM);
    356     response.setAttribute(GSXML.FROM_ATT, this.cluster_name);
    357     String type = request.getAttribute(GSXML.TYPE_ATT);
    358     String lang = request.getAttribute(GSXML.LANG_ATT);
    359     response.setAttribute(GSXML.TYPE_ATT, type);
    360 
    361     logger.debug("Collection received a message, attempting to process");
    362 
    363     if (type.equals(GSXML.REQUEST_TYPE_FORMAT_STRING)) {
    364         logger.error("Received format string request");
    365 
    366         String subaction = request.getAttribute("subaction");
    367         logger.error("Subaction is " + subaction);
    368 
    369         String service = request.getAttribute("service");
    370         logger.error("Service is " + service);
    371 
    372         String classifier = null;
    373         if(service.equals("ClassifierBrowse"))
    374         {
    375             classifier = request.getAttribute("classifier");
    376             logger.error("Classifier is " + classifier);
    377         }
    378 
    379         //logger.error("Format string: " + format_string);
    380         logger.error("Config file location = " + GSFile.collectionConfigFile(this.site_home, this.cluster_name));
    381 
    382         // check for version file
    383 
    384         String directory = new File(GSFile.collectionConfigFile(this.site_home, this.cluster_name)).getParent() + File.separator;
    385         logger.error("Directory is " + directory);
    386 
    387         String version_filename = "";
    388         if(service.equals("ClassifierBrowse"))
    389             version_filename = directory + "browse_"+classifier+"_format_statement_version.txt";
    390         else
    391             version_filename = directory + "query_format_statement_version.txt";
    392 
    393         File version_file = new File(version_filename);
    394         logger.error("Version filename is " + version_filename);
    395 
    396 
    397         if(subaction.equals("update"))
    398         {
    399             Element format_element = (Element) GSXML.getChildByTagName(request, GSXML.FORMAT_STRING_ELEM);
    400             //String format_string = GSXML.getNodeText(format_element);
    401             Element format_statement = (Element) format_element.getFirstChild();
    402 
    403 
    404             String version_number = "1";
    405             BufferedWriter writer;
    406 
    407             try{
    408 
    409                 if(version_file.exists())
    410                 {
    411                     // Read version
    412                     BufferedReader reader = new BufferedReader(new FileReader(version_filename));
    413                     version_number = reader.readLine();
    414                     int aInt = Integer.parseInt(version_number) + 1;
    415                     version_number = Integer.toString(aInt);
    416                     reader.close();
    417                 }
    418                 else{
    419                     // Create
    420                     version_file.createNewFile();
    421                     writer = new BufferedWriter(new FileWriter(version_filename));
    422                     writer.write(version_number);
    423                     writer.close();
    424                 }
    425 
    426                 // Write version file
    427                 String format_statement_filename = "";
    428 
    429                 if(service.equals("ClassifierBrowse"))
    430                     format_statement_filename = directory + "browse_"+classifier+"_format_statement_v" + version_number + ".txt";
    431                 else
    432                     format_statement_filename = directory + "query_format_statement_v" + version_number + ".txt";
    433 
    434                 logger.error("Format statement filename is " + format_statement_filename);
    435 
    436                 // Write format statement
    437                 String format_string = this.converter.getString(format_statement); //GSXML.xmlNodeToString(format_statement);
    438                 writer = new BufferedWriter(new FileWriter(format_statement_filename));
    439                 writer.write(format_string);
    440                 writer.close();
    441 
    442                 // Update version number
    443                 writer = new BufferedWriter(new FileWriter(version_filename));
    444                 writer.write(version_number);
    445                 writer.close();
    446 
    447             } catch (IOException e) {
    448                 logger.error("IO Exception "+e);
    449             }
    450         }
    451 
    452         if(subaction.equals("saveDocument"))
    453         {
    454             int k;
    455             Element format_element = (Element) GSXML.getChildByTagName(request, GSXML.FORMAT_STRING_ELEM);
    456             //String format_string = GSXML.getNodeText(format_element);
    457             // Get display tag
    458             Element display_format = (Element) format_element.getFirstChild();
    459 
    460             logger.error("I have received a save document request");
    461             String format_string = GSXML.xmlNodeToString(display_format);
    462             logger.error("Param="+format_string);
    463             String collection_config = directory + "collectionConfig.xml";
    464             Document config = this.converter.getDOM(new File(collection_config), "UTF-8");
    465 
    466             Node current_node = GSXML.getChildByTagName(config, "CollectionConfig");
    467 
    468             // Get display child
    469             if(GSXML.getChildByTagName(current_node, "display") == null)
    470             {
    471                 logger.error("ERROR: does not have a display child");
    472                 // well then create a format tag
    473                 Element display_tag = config.createElement("display");
    474                 current_node = (Node) current_node.appendChild(display_tag);
    475                 //current_node = (Node) format_tag;
    476             }
    477 
    478             else{
    479                 current_node = GSXML.getChildByTagName(current_node, "display");
    480             }
    481 
    482             if(GSXML.getChildByTagName(current_node, "format") == null)
    483             {
    484                 logger.error("ERROR: does not have a format child");
    485                 // well then create a format tag
    486                 Element format_tag = config.createElement("format");
    487                 current_node.appendChild(format_tag);
    488                 //current_node = (Node) format_tag;
    489             }
    490 
    491 
    492             current_node.replaceChild(config.importNode(display_format,true), GSXML.getChildByTagName(current_node, "format"));
    493 
    494             logger.error(GSXML.xmlNodeToString(current_node));
    495 
    496             logger.error("Convert config to string");
    497             String new_config = this.converter.getString(config);
    498 
    499             new_config = StringUtils.replace(new_config, "&lt;", "<");
    500             new_config = StringUtils.replace(new_config, "&gt;", ">");
    501             new_config = StringUtils.replace(new_config, "&quot;", "\"");
    502 
    503             try{
    504                 // Write to file (not original! for now)
    505                 BufferedWriter writer = new BufferedWriter(new FileWriter(collection_config+".new"));
    506                 writer.write(new_config);
    507                 writer.close();
    508                 logger.error("All is happy with collection saveDocument");
    509             } catch (IOException e) {
    510                 logger.error("IO Exception "+e);
    511             }
    512         }
    513 
    514         if(subaction.equals("save"))
    515         {
    516             logger.error("SAVE format statement");
    517 
    518             Element format_element = (Element) GSXML.getChildByTagName(request, GSXML.FORMAT_STRING_ELEM);
    519             //String format_string = GSXML.getNodeText(format_element);
    520             Element format_statement = (Element) format_element.getFirstChild();
    521 
    522             try{
    523 
    524                 // open collectionConfig.xml and read in to w3 Document
    525                 String collection_config = directory + "collectionConfig.xml";
    526                 Document config = this.converter.getDOM(new File(collection_config), "UTF-8");
    527            
    528                 //String tag_name = "";
    529                 int k;
    530                 int index;
    531                 Element elem;
    532                 // Try importing entire tree to this.doc so we can add and remove children at ease
    533                 //Node current_node = this.doc.importNode(GSXML.getChildByTagName(config, "CollectionConfig"),true);
    534                 Node current_node = GSXML.getChildByTagName(config, "CollectionConfig");
    535                 NodeList current_node_list;
    536    
    537                 logger.error("Service is "+service);
    538 
    539                 if(service.equals("ClassifierBrowse"))
    540                 {
    541                     //tag_name = "browse";
    542                     // if CLX then need to look in <classifier> X then <format>
    543                     // default is <browse><format>
    544 
    545                     logger.error("Looking for browse");
    546                     current_node = GSXML.getChildByTagName(current_node, "browse");
    547 
    548                     // find CLX
    549                     if(classifier != null)
    550                     {
    551                         logger.error("Classifier is not null");
    552                         logger.error("Classifier is "+classifier);
    553                         current_node_list = GSXML.getChildrenByTagName(current_node, "classifier");
    554                         index = Integer.parseInt(classifier.substring(2)) - 1;
    555                         logger.error("classifier index is "+index);
    556                         // index should be given by X-1
    557                         current_node = current_node_list.item(index);
    558                         // what if classifier does not have a format tag?
    559                         if(GSXML.getChildByTagName(current_node, "format") == null)
    560                         {
    561                             logger.error("ERROR: valid classifier but does not have a format child");
    562                             // well then create a format tag
    563                             Element format_tag = config.createElement("format");
    564                             current_node.appendChild(format_tag);
    565                             //current_node = (Node) format_tag;
    566                         }
    567                     }
    568                     else{
    569                         logger.error("Classifier is null");
    570                         // To support all classifiers, set classifier to null?  There is the chance here that the format tag does not exist
    571                         if(GSXML.getChildByTagName(current_node, "format") == null)
    572                         {
    573                             logger.error("ERROR: classifier does not have a format child");
    574                             // well then create a format tag
    575                             Element format_tag = config.createElement("format");
    576                             current_node.appendChild(format_tag);
    577                             //current_node = (Node) format_tag;
    578                         }
    579                     }
    580                 }
    581                 else if(service.equals("AllClassifierBrowse"))
    582                 {
    583                     logger.error("Looking for browse");
    584                     current_node = GSXML.getChildByTagName(current_node, "browse");
    585                     if(GSXML.getChildByTagName(current_node, "format") == null)
    586                     {
    587                         logger.error("ERROR AllClassifierBrowse: all classifiers do not have a format child");
    588                         // well then create a format tag
    589                         Element format_tag = config.createElement("format");
    590                         current_node.appendChild(format_tag);
    591                         //current_node = (Node) format_tag;
    592                     }
    593                 }
    594                 else
    595                 {
    596                     // look in <format> with no attributes
    597                     logger.error("I presume this is search");
    598            
    599                     current_node_list = GSXML.getChildrenByTagName(current_node, "search");
    600                     for(k=0; k<current_node_list.getLength(); k++)
    601                     {
    602                         current_node = current_node_list.item(k);
    603                         // if current_node has no attributes then break
    604                         elem = (Element) current_node;
    605                         if(elem.hasAttribute("name")==false)
    606                             break;
    607                     }
    608                 }
    609 
    610                 current_node.replaceChild(config.importNode(format_statement,true), GSXML.getChildByTagName(current_node, "format"));
    611 
    612                 // Now convert config document to string for writing to file
    613                 logger.error("Convert config to string");
    614                 String new_config = this.converter.getString(config);
    615 
    616                 new_config = StringUtils.replace(new_config, "&lt;", "<");
    617                 new_config = StringUtils.replace(new_config, "&gt;", ">");
    618                 new_config = StringUtils.replace(new_config, "&quot;", "\"");
    619    
    620                 // Write to file (not original! for now)
    621                 BufferedWriter writer = new BufferedWriter(new FileWriter(collection_config+".new"));
    622                 writer.write(new_config);
    623                 writer.close();
    624                 logger.error("All is happy with collection");
    625 
    626              } catch( Exception ex ) {
    627                 logger.error("There was an exception "+ex);
    628 
    629                 StringWriter sw = new StringWriter();
    630                 PrintWriter pw = new PrintWriter(sw, true);
    631                 ex.printStackTrace(pw);
    632                 pw.flush();
    633                 sw.flush();
    634                 logger.error(sw.toString());
    635             }
    636 
    637         }
    638     }
    639     else { // unknown type
    640         return super.processMessage(request);
    641 
    642     }
    643     return response;
    644     }
     56public class Collection extends ServiceCluster
     57{
     58
     59    static Logger logger = Logger.getLogger(org.greenstone.gsdl3.collection.Collection.class.getName());
     60
     61    /** is this collection being tidied */
     62    protected boolean useBook = false;
     63    /** is this collection public or private */
     64    protected boolean is_public = true;
     65
     66    /** does this collection provide the OAI service */
     67    protected boolean has_oai = true;
     68    /** time when this collection was built */
     69    protected long lastmodified = 0;
     70    /** earliestDatestamp of this collection. Necessary for OAI */
     71    protected long earliestDatestamp = 0;
     72
     73    /**
     74     * An element containing the serviceRackList element of buildConfig.xml,
     75     * used to determine whether it contains the OAIPMH serviceRack
     76     */
     77    //protected Element service_rack_list = null;
     78
     79    protected XMLTransformer transformer = null;
     80
     81    /** same as setClusterName */
     82    public void setCollectionName(String name)
     83    {
     84        setClusterName(name);
     85    }
     86
     87    public Collection()
     88    {
     89        super();
     90        this.description = this.doc.createElement(GSXML.COLLECTION_ELEM);
     91
     92    }
     93
     94    /**
     95     * Configures the collection.
     96     *
     97     * gsdlHome and collectionName must be set before configure is called.
     98     *
     99     * the file buildcfg.xml is located in gsdlHome/collect/collectionName
     100     * collection metadata is obtained, and services loaded.
     101     *
     102     * @return true/false on success/fail
     103     */
     104    public boolean configure()
     105    {
     106
     107        if (this.site_home == null || this.cluster_name == null)
     108        {
     109            logger.error("Collection: site_home and collection_name must be set before configure called!");
     110            return false;
     111        }
     112
     113        Element coll_config_xml = loadCollConfigFile();
     114        Element build_config_xml = loadBuildConfigFile();
     115
     116        if (coll_config_xml == null || build_config_xml == null)
     117        {
     118            return false;
     119        }
     120
     121        // get the collection type attribute
     122        Element search = (Element) GSXML.getChildByTagName(coll_config_xml, GSXML.SEARCH_ELEM);
     123        if (search != null)
     124        {
     125            col_type = search.getAttribute(GSXML.TYPE_ATT);
     126        }
     127       
     128        Element browse = (Element) GSXML.getChildByTagName(coll_config_xml, GSXML.INFODB_ELEM);
     129        if (browse != null)
     130        {
     131            db_type = browse.getAttribute(GSXML.TYPE_ATT);
     132        }
     133        else
     134        {
     135            db_type = "gdbm"; //Default database type
     136        }
     137
     138        // process the metadata and display items
     139        findAndLoadInfo(coll_config_xml, build_config_xml);
     140
     141        // now do the services
     142        configureServiceRacks(coll_config_xml, build_config_xml);
     143
     144        return true;
     145
     146    }
     147
     148    public boolean useBook()
     149    {
     150        return useBook;
     151    }
     152
     153    public boolean isPublic()
     154    {
     155        return is_public;
     156    }
     157
     158    // Not used anymore by the OAIReceptionist to find out the earliest datestamp
     159    // amongst all oai collections in the repository. May be useful generally.
     160    public long getLastmodified()
     161    {
     162        return lastmodified;
     163    }
     164
     165    //used by the OAIReceptionist to find out the earliest datestamp amongst all oai collections in the repository
     166    public long getEarliestDatestamp()
     167    {
     168        return earliestDatestamp;
     169    }
     170
     171    /**
     172     * whether the service_map in ServiceCluster.java contains the service
     173     * 'OAIPMH' 11/06/2007 xiao
     174     */
     175    public boolean hasOAI()
     176    {
     177        return has_oai;
     178    }
     179
     180    /**
     181     * load in the collection config file into a DOM Element
     182     */
     183    protected Element loadCollConfigFile()
     184    {
     185
     186        File coll_config_file = new File(GSFile.collectionConfigFile(this.site_home, this.cluster_name));
     187
     188        if (!coll_config_file.exists())
     189        {
     190            logger.error("Collection: couldn't configure collection: " + this.cluster_name + ", " + coll_config_file + " does not exist");
     191            return null;
     192        }
     193        // get the xml for both files
     194        Document coll_config_doc = this.converter.getDOM(coll_config_file, CONFIG_ENCODING);
     195        Element coll_config_elem = null;
     196        if (coll_config_doc != null)
     197        {
     198            coll_config_elem = coll_config_doc.getDocumentElement();
     199        }
     200        return coll_config_elem;
     201
     202    }
     203
     204    /**
     205     * load in the collection build config file into a DOM Element
     206     */
     207    protected Element loadBuildConfigFile()
     208    {
     209
     210        File build_config_file = new File(GSFile.collectionBuildConfigFile(this.site_home, this.cluster_name));
     211        if (!build_config_file.exists())
     212        {
     213            logger.error("Collection: couldn't configure collection: " + this.cluster_name + ", " + build_config_file + " does not exist");
     214            return null;
     215        }
     216        Document build_config_doc = this.converter.getDOM(build_config_file, CONFIG_ENCODING);
     217        Element build_config_elem = null;
     218        if (build_config_doc != null)
     219        {
     220            build_config_elem = build_config_doc.getDocumentElement();
     221        }
     222
     223        lastmodified = build_config_file.lastModified();
     224
     225        return build_config_elem;
     226    }
     227
     228    /**
     229     * find the metadata and display elems from the two config files and add it
     230     * to the appropriate lists
     231     */
     232    protected boolean findAndLoadInfo(Element coll_config_xml, Element build_config_xml)
     233    {
     234
     235        // metadata
     236        Element meta_list = (Element) GSXML.getChildByTagName(coll_config_xml, GSXML.METADATA_ELEM + GSXML.LIST_MODIFIER);
     237        addMetadata(meta_list);
     238        meta_list = (Element) GSXML.getChildByTagName(build_config_xml, GSXML.METADATA_ELEM + GSXML.LIST_MODIFIER);
     239        addMetadata(meta_list);
     240
     241        meta_list = this.doc.createElement(GSXML.METADATA_ELEM + GSXML.LIST_MODIFIER);
     242        GSXML.addMetadata(this.doc, meta_list, "httpPath", this.site_http_address + "/collect/" + this.cluster_name);
     243        addMetadata(meta_list);
     244
     245        // display stuff
     246        Element display_list = (Element) GSXML.getChildByTagName(coll_config_xml, GSXML.DISPLAY_TEXT_ELEM + GSXML.LIST_MODIFIER);
     247        if (display_list != null)
     248        {
     249            resolveMacros(display_list);
     250            addDisplayItems(display_list);
     251        }
     252
     253        //check whether the html are tidy or not
     254        Element import_list = (Element) GSXML.getChildByTagName(coll_config_xml, GSXML.IMPORT_ELEM);
     255        if (import_list != null)
     256        {
     257            Element plugin_list = (Element) GSXML.getChildByTagName(import_list, GSXML.PLUGIN_ELEM + GSXML.LIST_MODIFIER);
     258            addPlugins(plugin_list);
     259            if (plugin_list != null)
     260            {
     261                Element plugin_elem = (Element) GSXML.getNamedElement(plugin_list, GSXML.PLUGIN_ELEM, GSXML.NAME_ATT, "HTMLPlugin");
     262                if (plugin_elem != null)
     263                {
     264                    //get the option
     265                    Element option_elem = (Element) GSXML.getNamedElement(plugin_elem, GSXML.PARAM_OPTION_ELEM, GSXML.NAME_ATT, "-use_realistic_book");
     266                    if (option_elem != null)
     267                    {
     268                        useBook = true;
     269                    }
     270                }
     271            }
     272        }
     273        meta_list = this.doc.createElement(GSXML.METADATA_ELEM + GSXML.LIST_MODIFIER);
     274        if (useBook == true)
     275            GSXML.addMetadata(this.doc, meta_list, "tidyoption", "tidy");
     276        else
     277            GSXML.addMetadata(this.doc, meta_list, "tidyoption", "untidy");
     278        addMetadata(meta_list);
     279
     280        // check whether we are public or not
     281        if (meta_list != null)
     282        {
     283            Element meta_elem = (Element) GSXML.getNamedElement(metadata_list, GSXML.METADATA_ELEM, GSXML.NAME_ATT, "public");
     284            if (meta_elem != null)
     285            {
     286                String value = GSXML.getValue(meta_elem).toLowerCase().trim();
     287                if (value.equals("false"))
     288                {
     289                    is_public = false;
     290                }
     291            }
     292        }
     293        return true;
     294
     295    }
     296
     297    protected boolean configureServiceRacks(Element coll_config_xml, Element build_config_xml)
     298    {
     299        clearServices();
     300        Element service_list = (Element) GSXML.getChildByTagName(build_config_xml, GSXML.SERVICE_CLASS_ELEM + GSXML.LIST_MODIFIER);
     301        configureServiceRackList(service_list, coll_config_xml);
     302
     303        // collection Config may also contain manually added service racks
     304        service_list = (Element) GSXML.getChildByTagName(coll_config_xml, GSXML.SERVICE_CLASS_ELEM + GSXML.LIST_MODIFIER);
     305        if (service_list != null)
     306        {
     307            configureServiceRackList(service_list, build_config_xml);
     308
     309            // Check for oai
     310            Element oai_service_rack = GSXML.getNamedElement(service_list, GSXML.SERVICE_CLASS_ELEM, OAIXML.NAME, OAIXML.OAIPMH);
     311            if (oai_service_rack == null)
     312            {
     313                has_oai = false;
     314                logger.info("No oai for collection: " + this.cluster_name);
     315
     316            }
     317            else
     318            {
     319                has_oai = true;
     320
     321                // extract earliestDatestamp from the buildconfig.xml for OAI
     322                Element metadata_list = (Element) GSXML.getChildByTagName(build_config_xml, GSXML.METADATA_ELEM + GSXML.LIST_MODIFIER);
     323
     324                if (metadata_list != null)
     325                {
     326                    NodeList children = metadata_list.getElementsByTagName(GSXML.METADATA_ELEM);
     327                    // can't do getChildNodes(), because whitespace, such as newlines, creates Text nodes
     328                    for (int i = 0; i < children.getLength(); i++)
     329                    {
     330                        Element metadata = (Element) children.item(i);
     331                        if (metadata.getAttribute(GSXML.NAME_ATT).equals(OAIXML.EARLIEST_DATESTAMP))
     332                        {
     333                            String earliestDatestampStr = GSXML.getValue(metadata);
     334                            if (!earliestDatestampStr.equals(""))
     335                            {
     336                                earliestDatestamp = Long.parseLong(earliestDatestampStr);
     337                            }
     338                            break; // found a metadata element with name=earliestDatestamp in buildconfig
     339                        }
     340                    }
     341                }
     342
     343                // If at the end of this, there is no value for earliestDatestamp, print out a warning
     344                logger.warn("No earliestDatestamp in buildConfig.xml for collection: " + this.cluster_name + ". Defaulting to 0.");
     345
     346            }
     347        }
     348        else
     349        { // no list of services (no ServiceRackList), so no oai_service_rack either
     350            // explicitly set has_oai to false here, since it's initialised to true by default
     351            has_oai = false;
     352        }
     353        return true;
     354    }
     355
     356    protected boolean resolveMacros(Element display_list)
     357    {
     358        if (display_list == null)
     359            return false;
     360        NodeList displaynodes = display_list.getElementsByTagName(GSXML.DISPLAY_TEXT_ELEM);
     361        if (displaynodes.getLength() > 0)
     362        {
     363            String http_site = this.site_http_address;
     364            String http_collection = this.site_http_address + "/collect/" + this.cluster_name;
     365            for (int k = 0; k < displaynodes.getLength(); k++)
     366            {
     367                Element d = (Element) displaynodes.item(k);
     368                String text = GSXML.getNodeText(d);
     369                text = StringUtils.replace(text, "_httpsite_", http_site);
     370                text = StringUtils.replace(text, "_httpcollection_", http_collection);
     371                GSXML.setNodeText(d, text);
     372            }
     373        }
     374        return true;
     375    }
     376
     377    /**
     378     * do a configure on only part of the collection
     379     */
     380    protected boolean configureSubset(String subset)
     381    {
     382
     383        // need the coll config files
     384        Element coll_config_elem = loadCollConfigFile();
     385        Element build_config_elem = loadBuildConfigFile();
     386        if (coll_config_elem == null || build_config_elem == null)
     387        {
     388            // wont be able to do any of the requests
     389            return false;
     390        }
     391
     392        if (subset.equals(GSXML.SERVICE_ELEM + GSXML.LIST_MODIFIER))
     393        {
     394            return configureServiceRacks(coll_config_elem, build_config_elem);
     395        }
     396
     397        if (subset.equals(GSXML.METADATA_ELEM + GSXML.LIST_MODIFIER) || subset.equals(GSXML.DISPLAY_TEXT_ELEM + GSXML.LIST_MODIFIER) || subset.equals(GSXML.PLUGIN_ELEM + GSXML.LIST_MODIFIER))
     398        {
     399            return findAndLoadInfo(coll_config_elem, build_config_elem);
     400
     401        }
     402
     403        logger.error("Collection: cant process system request, configure " + subset);
     404        return false;
     405    }
     406
     407    /**
     408     * handles requests made to the ServiceCluster itself
     409     *
     410     * @param req
     411     *            - the request Element- <request>
     412     * @return the result Element - should be <response>
     413     */
     414    protected Element processMessage(Element request)
     415    {
     416
     417        Element response = this.doc.createElement(GSXML.RESPONSE_ELEM);
     418        response.setAttribute(GSXML.FROM_ATT, this.cluster_name);
     419        String type = request.getAttribute(GSXML.TYPE_ATT);
     420        String lang = request.getAttribute(GSXML.LANG_ATT);
     421        response.setAttribute(GSXML.TYPE_ATT, type);
     422
     423        logger.error("Collection received a message, attempting to process");
     424
     425        if (type.equals(GSXML.REQUEST_TYPE_FORMAT_STRING))
     426        {
     427            logger.error("Received format string request");
     428
     429            String subaction = request.getAttribute("subaction");
     430            logger.error("Subaction is " + subaction);
     431
     432            String service = request.getAttribute("service");
     433            logger.error("Service is " + service);
     434
     435            String classifier = null;
     436            if (service.equals("ClassifierBrowse"))
     437            {
     438                classifier = request.getAttribute("classifier");
     439                logger.error("Classifier is " + classifier);
     440            }
     441
     442            //logger.error("Format string: " + format_string);
     443            logger.error("Config file location = " + GSFile.collectionConfigFile(this.site_home, this.cluster_name));
     444
     445            // check for version file
     446
     447            String directory = new File(GSFile.collectionConfigFile(this.site_home, this.cluster_name)).getParent() + File.separator;
     448            logger.error("Directory is " + directory);
     449
     450            String version_filename = "";
     451            if (service.equals("ClassifierBrowse"))
     452                version_filename = directory + "browse_" + classifier + "_format_statement_version.txt";
     453            else
     454                version_filename = directory + "query_format_statement_version.txt";
     455
     456            File version_file = new File(version_filename);
     457            logger.error("Version filename is " + version_filename);
     458
     459            if (subaction.equals("update"))
     460            {
     461                Element format_element = (Element) GSXML.getChildByTagName(request, GSXML.FORMAT_STRING_ELEM);
     462                //String format_string = GSXML.getNodeText(format_element);
     463                Element format_statement = (Element) format_element.getFirstChild();
     464
     465                String version_number = "1";
     466                BufferedWriter writer;
     467
     468                try
     469                {
     470
     471                    if (version_file.exists())
     472                    {
     473                        // Read version
     474                        BufferedReader reader = new BufferedReader(new FileReader(version_filename));
     475                        version_number = reader.readLine();
     476                        int aInt = Integer.parseInt(version_number) + 1;
     477                        version_number = Integer.toString(aInt);
     478                        reader.close();
     479                    }
     480                    else
     481                    {
     482                        // Create
     483                        version_file.createNewFile();
     484                        writer = new BufferedWriter(new FileWriter(version_filename));
     485                        writer.write(version_number);
     486                        writer.close();
     487                    }
     488
     489                    // Write version file
     490                    String format_statement_filename = "";
     491
     492                    if (service.equals("ClassifierBrowse"))
     493                        format_statement_filename = directory + "browse_" + classifier + "_format_statement_v" + version_number + ".txt";
     494                    else
     495                        format_statement_filename = directory + "query_format_statement_v" + version_number + ".txt";
     496
     497                    logger.error("Format statement filename is " + format_statement_filename);
     498
     499                    // Write format statement
     500                    String format_string = this.converter.getString(format_statement); //GSXML.xmlNodeToString(format_statement);
     501                    writer = new BufferedWriter(new FileWriter(format_statement_filename));
     502                    writer.write(format_string);
     503                    writer.close();
     504
     505                    // Update version number
     506                    writer = new BufferedWriter(new FileWriter(version_filename));
     507                    writer.write(version_number);
     508                    writer.close();
     509
     510                }
     511                catch (IOException e)
     512                {
     513                    logger.error("IO Exception " + e);
     514                }
     515            }
     516
     517            if (subaction.equals("saveDocument"))
     518            {
     519                int k;
     520                Element format_element = (Element) GSXML.getChildByTagName(request, GSXML.FORMAT_STRING_ELEM);
     521                //String format_string = GSXML.getNodeText(format_element);
     522                // Get display tag
     523                Element display_format = (Element) format_element.getFirstChild();
     524
     525                logger.error("I have received a save document request");
     526                String format_string = GSXML.xmlNodeToString(display_format);
     527                logger.error("Param=" + format_string);
     528                String collection_config = directory + "collectionConfig.xml";
     529                Document config = this.converter.getDOM(new File(collection_config), "UTF-8");
     530
     531                Node current_node = GSXML.getChildByTagName(config, "CollectionConfig");
     532
     533                // Get display child
     534                if (GSXML.getChildByTagName(current_node, "display") == null)
     535                {
     536                    logger.error("ERROR: does not have a display child");
     537                    // well then create a format tag
     538                    Element display_tag = config.createElement("display");
     539                    current_node = (Node) current_node.appendChild(display_tag);
     540                    //current_node = (Node) format_tag;
     541                }
     542
     543                else
     544                {
     545                    current_node = GSXML.getChildByTagName(current_node, "display");
     546                }
     547
     548                if (GSXML.getChildByTagName(current_node, "format") == null)
     549                {
     550                    logger.error("ERROR: does not have a format child");
     551                    // well then create a format tag
     552                    Element format_tag = config.createElement("format");
     553                    current_node.appendChild(format_tag);
     554                    //current_node = (Node) format_tag;
     555                }
     556
     557                current_node.replaceChild(config.importNode(display_format, true), GSXML.getChildByTagName(current_node, "format"));
     558
     559                logger.error(GSXML.xmlNodeToString(current_node));
     560
     561                logger.error("Convert config to string");
     562                String new_config = this.converter.getString(config);
     563
     564                new_config = StringUtils.replace(new_config, "&lt;", "<");
     565                new_config = StringUtils.replace(new_config, "&gt;", ">");
     566                new_config = StringUtils.replace(new_config, "&quot;", "\"");
     567
     568                try
     569                {
     570                    // Write to file (not original! for now)
     571                    BufferedWriter writer = new BufferedWriter(new FileWriter(collection_config + ".new"));
     572                    writer.write(new_config);
     573                    writer.close();
     574                    logger.error("All is happy with collection saveDocument");
     575                }
     576                catch (IOException e)
     577                {
     578                    logger.error("IO Exception " + e);
     579                }
     580            }
     581
     582            if (subaction.equals("save"))
     583            {
     584                logger.error("SAVE format statement");
     585
     586                Element format_element = (Element) GSXML.getChildByTagName(request, GSXML.FORMAT_STRING_ELEM);
     587                //String format_string = GSXML.getNodeText(format_element);
     588                Element format_statement = (Element) format_element.getFirstChild();
     589
     590                try
     591                {
     592
     593                    // open collectionConfig.xml and read in to w3 Document
     594                    String collection_config = directory + "collectionConfig.xml";
     595                    Document config = this.converter.getDOM(new File(collection_config), "UTF-8");
     596
     597                    //String tag_name = "";
     598                    int k;
     599                    int index;
     600                    Element elem;
     601                    // Try importing entire tree to this.doc so we can add and remove children at ease
     602                    //Node current_node = this.doc.importNode(GSXML.getChildByTagName(config, "CollectionConfig"),true);
     603                    Node current_node = GSXML.getChildByTagName(config, "CollectionConfig");
     604                    NodeList current_node_list;
     605
     606                    logger.error("Service is " + service);
     607
     608                    if (service.equals("ClassifierBrowse"))
     609                    {
     610                        //tag_name = "browse";
     611                        // if CLX then need to look in <classifier> X then <format>
     612                        // default is <browse><format>
     613
     614                        logger.error("Looking for browse");
     615                        current_node = GSXML.getChildByTagName(current_node, "browse");
     616
     617                        // find CLX
     618                        if (classifier != null)
     619                        {
     620                            logger.error("Classifier is not null");
     621                            logger.error("Classifier is " + classifier);
     622                            current_node_list = GSXML.getChildrenByTagName(current_node, "classifier");
     623                            index = Integer.parseInt(classifier.substring(2)) - 1;
     624                            logger.error("classifier index is " + index);
     625                            // index should be given by X-1
     626                            current_node = current_node_list.item(index);
     627                            // what if classifier does not have a format tag?
     628                            if (GSXML.getChildByTagName(current_node, "format") == null)
     629                            {
     630                                logger.error("ERROR: valid classifier but does not have a format child");
     631                                // well then create a format tag
     632                                Element format_tag = config.createElement("format");
     633                                current_node.appendChild(format_tag);
     634                                //current_node = (Node) format_tag;
     635                            }
     636                        }
     637                        else
     638                        {
     639                            logger.error("Classifier is null");
     640                            // To support all classifiers, set classifier to null?  There is the chance here that the format tag does not exist
     641                            if (GSXML.getChildByTagName(current_node, "format") == null)
     642                            {
     643                                logger.error("ERROR: classifier does not have a format child");
     644                                // well then create a format tag
     645                                Element format_tag = config.createElement("format");
     646                                current_node.appendChild(format_tag);
     647                                //current_node = (Node) format_tag;
     648                            }
     649                        }
     650                    }
     651                    else if (service.equals("AllClassifierBrowse"))
     652                    {
     653                        logger.error("Looking for browse");
     654                        current_node = GSXML.getChildByTagName(current_node, "browse");
     655                        if (GSXML.getChildByTagName(current_node, "format") == null)
     656                        {
     657                            logger.error("ERROR AllClassifierBrowse: all classifiers do not have a format child");
     658                            // well then create a format tag
     659                            Element format_tag = config.createElement("format");
     660                            current_node.appendChild(format_tag);
     661                            //current_node = (Node) format_tag;
     662                        }
     663                    }
     664                    else
     665                    {
     666                        // look in <format> with no attributes
     667                        logger.error("I presume this is search");
     668
     669                        current_node_list = GSXML.getChildrenByTagName(current_node, "search");
     670                        for (k = 0; k < current_node_list.getLength(); k++)
     671                        {
     672                            current_node = current_node_list.item(k);
     673                            // if current_node has no attributes then break
     674                            elem = (Element) current_node;
     675                            if (elem.hasAttribute("name") == false)
     676                                break;
     677                        }
     678                    }
     679
     680                    current_node.replaceChild(config.importNode(format_statement, true), GSXML.getChildByTagName(current_node, "format"));
     681
     682                    // Now convert config document to string for writing to file
     683                    logger.error("Convert config to string");
     684                    String new_config = this.converter.getString(config);
     685
     686                    new_config = StringUtils.replace(new_config, "&lt;", "<");
     687                    new_config = StringUtils.replace(new_config, "&gt;", ">");
     688                    new_config = StringUtils.replace(new_config, "&quot;", "\"");
     689
     690                    // Write to file (not original! for now)
     691                    BufferedWriter writer = new BufferedWriter(new FileWriter(collection_config + ".new"));
     692                    writer.write(new_config);
     693                    writer.close();
     694                    logger.error("All is happy with collection");
     695
     696                }
     697                catch (Exception ex)
     698                {
     699                    logger.error("There was an exception " + ex);
     700
     701                    StringWriter sw = new StringWriter();
     702                    PrintWriter pw = new PrintWriter(sw, true);
     703                    ex.printStackTrace(pw);
     704                    pw.flush();
     705                    sw.flush();
     706                    logger.error(sw.toString());
     707                }
     708
     709            }
     710        }
     711        else
     712        { // unknown type
     713            return super.processMessage(request);
     714
     715        }
     716        return response;
     717    }
    645718
    646719}
    647 
    648 
    649 
    650 
Note: See TracChangeset for help on using the changeset viewer.