001///////////////////////////////////////////////////////////////////////////// 002// Copyright (c) 2009 OPeNDAP, Inc. 003// All rights reserved. 004// Permission is hereby granted, without written agreement and without 005// license or royalty fees, to use, copy, modify, and distribute this 006// software and its documentation for any purpose, provided that the above 007// copyright notice and the following two paragraphs appear in all copies 008// of this software. 009// 010// IN NO EVENT SHALL OPeNDAP BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, 011// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF 012// THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF OPeNDAP HAS BEEN ADVISED 013// OF THE POSSIBILITY OF SUCH DAMAGE. 014// 015// OPeNDAP SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT 016// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 017// PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" 018// BASIS, AND OPeNDAP HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, 019// UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 020// 021// Author: Nathan David Potter <ndp@opendap.org> 022// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112. 023// 024///////////////////////////////////////////////////////////////////////////// 025 026package org.kepler.dataproxy.datasource.opendap; 027 028import java.io.IOException; 029import java.util.Collection; 030import java.util.Enumeration; 031import java.util.Iterator; 032import java.util.Vector; 033 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.ecoinformatics.seek.datasource.DataSourceIcon; 037 038import opendap.dap.DAP2Exception; 039import opendap.dap.DAS; 040import opendap.dap.DArray; 041import opendap.dap.DConnect2; 042import opendap.dap.DConstructor; 043import opendap.dap.DDS; 044import opendap.dap.parser.ParseException; 045import ptolemy.actor.TypedIOPort; 046import ptolemy.actor.lib.LimitedFiringSource; 047import ptolemy.actor.parameters.PortParameter; 048import ptolemy.data.StringToken; 049import ptolemy.data.expr.StringParameter; 050import ptolemy.data.type.BaseType; 051import ptolemy.data.type.Type; 052import ptolemy.kernel.CompositeEntity; 053import ptolemy.kernel.util.IllegalActionException; 054import ptolemy.kernel.util.NameDuplicationException; 055 056/** 057 * The OPeNDAP actor reads data from OPeNDAP data sources (i.e. servers). 058 * <p/> 059 * <h1>OPeNDAP Actor Overview</h1> 060 * <p/> 061 * The OPeNDAP actor provides access to data served by any Data Access Protocol 062 * (DAP) 2.0 compatible data source. The actor takes as configuration parameters 063 * the URL to the data source and an optional constraint expression (CE). Based 064 * on the URL and optional CE, the actor configures its output ports to match 065 * the variables to be read from the data source. 066 * <p/> 067 * <h2>More information about the OPeNDAP actor</h2> 068 * <p/> 069 * The OPeNDAP actor reads data from a single DAP data server and provides that 070 * data as either a vector.matrix or array for processing by downstream elements 071 * in a Kepler workflow. Each DAP server provides (serves) many data sources and 072 * each of those data sources can be uniquely identified using a URL in a way 073 * that's similar to how pages are provided by a web server. For more 074 * information on the DAP and on OPeNDAP's software, see www.opendap.org. 075 * <p/> 076 * <h3>Characterization of Data Sources</h3> 077 * <p/> 078 * Data sources accessible using DAP 2.0 are characterized by a URL that 079 * references a both a specific data server and a data granule available from 080 * that server and a Constraint Expression that describes which variables to 081 * read from within the data granule. In addition to reading data from a 082 * granule, a DAP 2.0 server can provide two pieces of information about the 083 * granule: a description of all of its variables, their names and their data 084 * types; and a collection of 'attributes' which are bound to those variables. 085 * <p/> 086 * <h3>Operation of the Actor</h3> 087 * <p/> 088 * The actor must have a valid URL before it can provide any information (just 089 * as a file reader actor need to point toward a file to provide data). Given a 090 * URL and the optional CE, the OPeNDAP actor will interrogate that data source 091 * and configure its output ports. 092 * <p/> 093 * <h3>Data Types Returned by the Actor</h3> 094 * <p/> 095 * There are two broad classes of data types returned by the actor. First there 096 * are vectors, matrices and arrays. These correspond to one, two and N (> 2) 097 * dimensional arrays. The distinction between the vector and matrix types and 098 * the N-dimensional array is that Kepler can operate on the vector and matrix 099 * types far more efficiently than the N-dimensional arrays. Many variables 100 * present in DAP data sources are of the N-dimensional array class and one way 101 * to work with these efficiently is to use the constraint expression to reduce 102 * the order of these data to one or two, thus causing the actor to store them 103 * in a vector or matrix. 104 * <p/> 105 * <p> 106 * As an example, consider the FNOC1 data source available at test.opendap.org. 107 * The full URL for this is http://test.opendap.org/opendap/data/nc/fnoc1.nc. It 108 * contains a variable 'u' which has three dimensions. We can constrain 'u' so 109 * that it has only two dimensions when read into Kepler using the CE 110 * 'u[0][0:16][0:20]' which selects only the first element (index 0) for the 111 * first dimension while requesting all of the remaining elements for the second 112 * and third dimensions. The www.opendap.org has documentation about the CE 113 * syntax. 114 * </p> 115 * <p/> 116 * <p> 117 * The second data type returned by the actor is a record. In reality, all DAP 118 * data sources are records but the actor automatically 'disassembles' the top 119 * most record since we know that's what the vast majority of users will want. 120 * However, some data sources contains nested hierarchies of records many levels 121 * deep. When dealing with those data sources you will need to use the Kepler 122 * record disassembler in your workflow. 123 * </p> 124 * 125 * @author Nathan Potter 126 * @version $Id: OpendapDataSource.java 33629 2015-08-24 22:43:10Z crawl $ 127 * @date Jul 17, 2007 128 * @since Kepler 1.0RC1 129 */ 130public class OpendapDataSource extends LimitedFiringSource { 131 132 static Log log; 133 134 static { 135 log = LogFactory 136 .getLog("org.kepler.dataproxy.datasource.opendap.OpendapDataSource"); 137 } 138 139 /** 140 * The OPeNDAP URL that identifies a (possibly constrained) dataset. 141 */ 142 public PortParameter opendapURLParameter; 143 private String opendapURL; 144 145 /** 146 * The OPeNDAP Constraint Expression used to sub sample the dataset. 147 */ 148 public PortParameter opendapCEParameter; 149 private String opendapCE; 150 151 /** 152 * Controls if and how the DAP2 metadata is incorporated into the Actors 153 * output. 154 * 155 */ 156 public StringParameter metadataOptionsParameter; 157 public static int NO_METADATA = 0; 158 public static int EMBEDDED_METADATA = 1; 159 public static int SEPARATE_METADATA_PORT = 2; 160 private String[] metadataChoices = { "No Metadata", "Embedded Metadata", 161 "Separate Metadata Port" }; 162 163 public static String separateMetadataPortName = "DAP2 Metadata"; 164 public static String globalMetadataPortName = "Global Metadata"; 165 166 private boolean imbedMetadata; 167 private boolean useSeparateMetadataPort; 168 169 private DConnect2 dapConnection; 170 private DataSourceIcon icon; 171 172 public OpendapDataSource(CompositeEntity container, String name) 173 throws NameDuplicationException, IllegalActionException { 174 super(container, name); 175 176 // hide the parent class's output port since we cannot delete it 177 new ptolemy.kernel.util.Attribute(output, "_hide"); 178 179 opendapURLParameter = new PortParameter(this, "DAP2 URL"); 180 opendapURLParameter.setStringMode(true); 181 opendapURLParameter.getPort().setTypeEquals(BaseType.STRING); 182 opendapURL = ""; 183 184 opendapCEParameter = new PortParameter(this, 185 "DAP2 Constraint Expression"); 186 opendapCEParameter.setStringMode(true); 187 opendapCEParameter.getPort().setTypeEquals(BaseType.STRING); 188 opendapCE = ""; 189 190 metadataOptionsParameter = new StringParameter(this, "Metadata Options"); 191 metadataOptionsParameter.setTypeEquals(BaseType.STRING); 192 metadataOptionsParameter.setToken(new StringToken(metadataChoices[0])); 193 for (String choice : metadataChoices) { 194 metadataOptionsParameter.addChoice(choice); 195 } 196 imbedMetadata = false; 197 useSeparateMetadataPort = false; 198 199 dapConnection = null; 200 try{ 201 icon = new DataSourceIcon(this); 202 }catch(Throwable ex){ 203 log.error(ex.getMessage()); 204 } 205 } 206 207 /** 208 * @param attribute 209 * The changed Attribute. 210 * @throws ptolemy.kernel.util.IllegalActionException 211 * When bad things happen. 212 */ 213 public void attributeChanged(ptolemy.kernel.util.Attribute attribute) 214 throws ptolemy.kernel.util.IllegalActionException { 215 216 if (attribute == opendapURLParameter || attribute == opendapCEParameter 217 || attribute == metadataOptionsParameter) { 218 updateParameters(); 219 } else { 220 super.attributeChanged(attribute); 221 } 222 } 223 224 /** 225 * Update values in URL and CE parameters. 226 * 227 * @throws IllegalActionException 228 * When the bad things happen. 229 */ 230 private void updateParameters() throws IllegalActionException { 231 232 boolean reload = false; 233 234 String url = ((StringToken) opendapURLParameter.getToken()) 235 .stringValue(); 236 if (!opendapURL.equals(url)) { 237 opendapURL = url; 238 239 if (opendapURL.contains("?")) 240 throw new IllegalActionException(this, 241 "The DAP2 URL must NOT contain a constraint expression or fragment thereof."); 242 243 // only reload if not empty. 244 if (!url.equals("")) { 245 reload = true; 246 } 247 248 } 249 250 String ce = ((StringToken) opendapCEParameter.getToken()).stringValue(); 251 if (!opendapCE.equals(ce)) { 252 opendapCE = ce; 253 254 if (!opendapURL.equals("")) // Only REload if it looks like they 255 // have a URL too. 256 reload = true; 257 } 258 259 String mdo = metadataOptionsParameter.stringValue(); 260 boolean goodChoice = false; 261 for (String choice : metadataChoices) { 262 if (mdo.equals(choice)) 263 goodChoice = true; 264 } 265 if (goodChoice) { 266 267 if (mdo.equals(metadataChoices[NO_METADATA])) { 268 269 if (imbedMetadata || useSeparateMetadataPort) 270 reload = true; 271 272 imbedMetadata = false; 273 useSeparateMetadataPort = false; 274 } else if (mdo.equals(metadataChoices[EMBEDDED_METADATA])) { 275 276 if (!imbedMetadata || useSeparateMetadataPort) 277 reload = true; 278 279 imbedMetadata = true; 280 useSeparateMetadataPort = false; 281 } else if (mdo.equals(metadataChoices[SEPARATE_METADATA_PORT])) { 282 283 if (imbedMetadata || !useSeparateMetadataPort) 284 reload = true; 285 286 imbedMetadata = false; 287 useSeparateMetadataPort = true; 288 } 289 290 } else { 291 String msg = "You may not edit the metadata options. " 292 + "You must chose one of: "; 293 for (String choice : this.metadataChoices) { 294 msg += "[" + choice + "] "; 295 } 296 throw new IllegalActionException(this, msg); 297 } 298 299 if (reload) { 300 301 try { 302 303 log.debug("OPeNDAP URL: " + opendapURL); 304 dapConnection = new DConnect2(opendapURL); 305 306 DDS dds = getDDS(ce, false); 307 308 // log.debug("Before ports configured."); 309 // dds.print(System.out); 310 311 log.debug("Configuring ports."); 312 configureOutputPorts(dds); 313 314 // log.debug("After ports configured."); 315 // dds.print(System.out); 316 317 } catch (Exception e) { 318 e.printStackTrace(); 319 throw new IllegalActionException(this, "Problem accessing " 320 + "OPeNDAP Data Source: " + e.getMessage()); 321 } 322 323 } 324 } 325 326 public void preinitialize() throws IllegalActionException { 327 super.preinitialize(); 328 329 String ce; 330 ce = ((StringToken) opendapCEParameter.getToken()).stringValue(); 331 log.debug("opendapCEParameter: " + ce); 332 333 if (ce.equals("")) { 334 ce = createCEfromWiredPorts(); 335 log.debug("Created CE from wired ports. CE: " + ce); 336 opendapCEParameter.setToken(new StringToken(ce)); 337 updateParameters(); 338 } 339 log.debug("metadataOptionsParameter.stringValue(): " 340 + metadataOptionsParameter.stringValue()); 341 log.debug("ce: " 342 + ((StringToken) opendapCEParameter.getToken()).stringValue()); 343 344 } 345 346 public void fire() throws IllegalActionException { 347 super.fire(); 348 log.debug("\n\n\n--- fire"); 349 350 opendapURLParameter.update(); 351 opendapCEParameter.update(); 352 updateParameters(); 353 354 try { 355 if (icon != null) 356 icon.setBusy(); 357 358 String ce; 359 ce = ((StringToken) opendapCEParameter.getToken()).stringValue(); 360 361 log.debug("ConstraintExpression: " + ce); 362 363 DDS dds = getDDS(ce, true); 364 365 // log.debug("fire(): dapConnection.getData(ce) returned DataDDS:"); 366 367 log.debug("Broadcasting DAP data arrays."); 368 broadcastDapData(dds); 369 370 if (icon != null) 371 icon.setReady(); 372 373 // log.debug("fire(): After data broadcast:"); 374 // dds.print(System.out); 375 376 } catch (Exception e) { 377 log.error("fire() Failed: ", e); 378 379 } 380 } 381 382 /** 383 * Build up the projection part of the constraint expression (CE) in order 384 * to minimize the amount of data retrieved. If the CE is empty, then this 385 * will build a list of projected variables based on which output ports are 386 * wired. If the CE is not empty then it will not be modified. 387 * 388 * @return A new CE if the passed one is not empty, a new one corresponding 389 * to the wired output ports otherwise. 390 * @exception IllegalActionException 391 * If thrown will getting the width of the ports. 392 */ 393 private String createCEfromWiredPorts() throws IllegalActionException { 394 395 String ce; 396 397 // Get the port list 398 Iterator i = this.outputPortList().iterator(); 399 400 String projection = ""; 401 int pcount = 0; 402 while (i.hasNext()) { 403 TypedIOPort port = (TypedIOPort) i.next(); 404 405 if (port.getWidth() > 0 406 && !port.getName().equals(separateMetadataPortName)) { 407 408 log.debug("Added " + port.getName() + " to projection."); 409 if (pcount > 0) 410 projection += ","; 411 projection += port.getName(); 412 pcount++; 413 } 414 } 415 ce = projection; 416 417 return ce; 418 419 } 420 421 /** 422 * Walks through the DDS, converts DAP data to ptII data, and broadcasts the 423 * data onto the appropriate ports. 424 * 425 * @param dds 426 * The DDS from which to get the data to send 427 * @throws IllegalActionException 428 * When bad things happen. 429 */ 430 private void broadcastDapData(DDS dds) throws IllegalActionException { 431 432 // log.debug("broadcastDapData(): DataDDS prior to broadcast:"); 433 // dds.print(System.out); 434 435 TypedIOPort port; 436 437 // log.debug("Broadcasting Dap Data for DDS: "); 438 // dds.print(System.out); 439 440 if (useSeparateMetadataPort) { 441 log.debug("Sending " + separateMetadataPortName + " data."); 442 port = (TypedIOPort) this.getPort(separateMetadataPortName); 443 port.broadcast(AttTypeMapper.buildMetaDataTokens(dds)); 444 log.debug("Sent " + separateMetadataPortName); 445 } 446 447 if (imbedMetadata) { 448 log.debug("Sending " + globalMetadataPortName + " port data."); 449 port = (TypedIOPort) this.getPort(globalMetadataPortName); 450 port.broadcast(AttTypeMapper.convertAttributeToToken(dds 451 .getAttribute())); 452 log.debug("Sent " + globalMetadataPortName); 453 } 454 455 Enumeration e = dds.getVariables(); 456 while (e.hasMoreElements()) { 457 opendap.dap.BaseType bt = (opendap.dap.BaseType) e.nextElement(); 458 459 String columnName = TypeMapper.replacePeriods(bt.getName().trim()); 460 // Get the port associated with this DDS variable. 461 port = (TypedIOPort) this.getPort(columnName); 462 if (port == null) { 463 throw new IllegalActionException(this, 464 "Request Output Port Missing: " + columnName); 465 } 466 467 log.debug("Translating data."); 468 // bt.printDecl(System.out); 469 470 // Map the DAP data for this variable into the ptII Token model. 471 ptolemy.data.Token token = TokenMapper.mapDapObjectToToken(bt, 472 imbedMetadata); 473 log.debug("Data Translated."); 474 // bt.printDecl(System.out); 475 476 // Send the data. 477 log.debug("Sending data."); 478 port.broadcast(token); 479 log.debug("Sent data."); 480 481 } 482 483 } 484 485 /** 486 * Configure the output ports to expose all of the variables at the top 487 * level of the (potentially constrained) DDS. 488 * 489 * @param dds 490 * The DDS 491 * @throws IllegalActionException 492 * When bad things happen. 493 */ 494 private void configureOutputPorts(DDS dds) throws IllegalActionException { 495 496 Vector<Type> types = new Vector<Type>(); 497 Vector<String> names = new Vector<String>(); 498 499 if (useSeparateMetadataPort) { 500 log.debug("Adding " + separateMetadataPortName 501 + " port to port list."); 502 names.add(separateMetadataPortName); 503 types.add(AttTypeMapper.buildMetaDataTypes(dds)); 504 log.debug("Added " + separateMetadataPortName 505 + " port to port list."); 506 } 507 508 if (imbedMetadata) { 509 log.debug("Adding " + globalMetadataPortName 510 + " port to port list."); 511 names.add(globalMetadataPortName); 512 types.add(AttTypeMapper.convertAttributeToType(dds.getAttribute())); 513 log.debug("Added " + globalMetadataPortName + " port."); 514 } 515 516 Enumeration e = dds.getVariables(); 517 while (e.hasMoreElements()) { 518 opendap.dap.BaseType bt = (opendap.dap.BaseType) e.nextElement(); 519 types.add(TypeMapper.mapDapObjectToType(bt, imbedMetadata)); 520 names.add(TypeMapper.replacePeriods(bt.getName())); 521 } 522 523 removeOtherOutputPorts(names); 524 525 Iterator ti = types.iterator(); 526 Iterator ni = names.iterator(); 527 528 while (ti.hasNext() && ni.hasNext()) { 529 Type type = (Type) ti.next(); 530 String name = (String) ni.next(); 531 initializePort(name, type); 532 } 533 534 } 535 536 /** 537 * Add a new port. 538 * 539 * @param aPortName 540 * name of new port 541 * @param aPortType 542 * Type of new port 543 * @throws IllegalActionException 544 * When bad things happen. 545 */ 546 void initializePort(String aPortName, Type aPortType) 547 throws IllegalActionException { 548 try { 549 String columnName = aPortName.trim(); 550 // Create a new port for each Column in the resultset 551 TypedIOPort port = (TypedIOPort) this.getPort(columnName); 552 boolean aIsNew = (port == null); 553 if (aIsNew) { 554 // Create a new typed port and add it to this container 555 port = new TypedIOPort(this, columnName, false, true); 556 new ptolemy.kernel.util.Attribute(port, "_showName"); 557 log.debug("Creating port [" + columnName + "]" + this); 558 } 559 560 // FIXME: we cannot set the port type during fire() since this 561 // requires write access to the workspace, which could lead to 562 // a deadlock. For now, we check to see if the port type is 563 // different; it usually is the same when we're in fire(). 564 565 // See if the port types are different. 566 if (!port.getType().equals(aPortType)) { 567 port.setTypeEquals(aPortType); 568 } 569 570 } catch (ptolemy.kernel.util.NameDuplicationException nde) { 571 throw new IllegalActionException(this, 572 "One or more attributes has the same name. Please correct this and try again."); 573 } 574 575 } 576 577 /** 578 * Remove all ports which's name is not in the selected vector 579 * 580 * @param nonRemovePortName 581 * The ports to NOT remove. That means keep. Whatever... 582 * @throws IllegalActionException 583 * When bad things happen. 584 */ 585 void removeOtherOutputPorts(Collection nonRemovePortName) 586 throws IllegalActionException { 587 // Use toArray() to make a deep copy of this.portList(). 588 // Do this to prevent ConcurrentModificationExceptions. 589 TypedIOPort[] l = new TypedIOPort[0]; 590 l = (TypedIOPort[]) this.portList().toArray(l); 591 592 for (TypedIOPort port : l) { 593 if (port == null || port.isInput()) { 594 continue; 595 } 596 String currPortName = port.getName(); 597 598 // Do not remove the output port since it belongs to a 599 // parent class. 600 if (!nonRemovePortName.contains(currPortName) 601 && !currPortName.equals("output")) { 602 try { 603 port.setContainer(null); 604 } catch (Exception ex) { 605 throw new IllegalActionException(this, 606 "Error removing port: " + currPortName); 607 } 608 } 609 } 610 } 611 612 /** 613 * Remove all ports. 614 * 615 * @throws IllegalActionException 616 * When bad things happen. 617 */ 618 void removeAllOutputPorts() throws IllegalActionException { 619 // Use toArray() to make a deep copy of this.portList(). 620 // Do this to prevent ConcurrentModificationExceptions. 621 TypedIOPort[] ports = new TypedIOPort[0]; 622 ports = (TypedIOPort[]) this.portList().toArray(ports); 623 624 for (TypedIOPort port : ports) { 625 if (port != null && port.isOutput()) { 626 String currPortName = port.getName(); 627 try { 628 port.setContainer(null); 629 } catch (Exception ex) { 630 throw new IllegalActionException(this, 631 "Error removing port: " + currPortName); 632 } 633 } 634 635 } 636 } 637 638 /** 639 * Probe a port 640 * 641 * @param port 642 * The port to probe. 643 * @return The probe report. 644 */ 645 public static String portInfo(TypedIOPort port) { 646 String width = ""; 647 try { 648 width = Integer.valueOf(port.getWidth()).toString(); 649 } catch (IllegalActionException ex) { 650 width = "Failed to get width of port " + port.getFullName() + ex; 651 } 652 653 String description = ""; 654 try { 655 description = port.description(); 656 } catch (IllegalActionException ex) { 657 description = "Failed to get the description of port " 658 + port.getFullName() + ": " + ex; 659 } 660 String msg = "Port Info: \n"; 661 msg += " getName(): " + port.getName() + "\n"; 662 msg += " getWidth(): " + width + "\n"; 663 msg += " isInput(): " + port.isInput() + "\n"; 664 msg += " isOutput(): " + port.isOutput() + "\n"; 665 msg += " isMultiport(): " + port.isMultiport() + "\n"; 666 msg += " className(): " + port.getClassName() + "\n"; 667 msg += " getDisplayName(): " + port.getDisplayName() + "\n"; 668 msg += " getElementName(): " + port.getElementName() + "\n"; 669 msg += " getFullName(): " + port.getFullName() + "\n"; 670 msg += " getSource(): " + port.getSource() + "\n"; 671 msg += " description(): " + description + "\n"; 672 msg += " toString(): " + port + "\n"; 673 return msg; 674 } 675 676 private DDS getDDS(String ce, boolean getData) throws IOException, 677 ParseException, DAP2Exception { 678 679 DDS dds, ddx; 680 DAS das; 681 682 /* 683 * ------------------------------------------------------------- Get the 684 * metadata - this is ugly because there is a bug in DConnect that 685 * causes calls to getDataDDX to return a DDS object that contains no 686 * data. 687 */ 688 689 try { 690 log.debug("Attempting to get DDX."); 691 692 ddx = dapConnection.getDDX(ce); 693 694 log.debug("Got DDX."); 695 // ddx.print(System.out); 696 } catch (Exception e) { 697 log.debug("Failed to get DataDDX. Msg: " + e.getMessage()); 698 699 log.debug("Attempting to get DDS. ce: " + ce); 700 ddx = dapConnection.getDDS(ce); 701 log.debug("Got DDS."); 702 703 log.debug("Attempting to get DAS."); 704 das = dapConnection.getDAS(); 705 log.debug("Got DAS."); 706 707 log.debug("Calling DDS.ingestDAS()."); 708 ddx.ingestDAS(das); 709 710 } 711 log.debug("Squeezing DDX arrays."); 712 squeezeArrays(ddx); 713 714 if (getData) { 715 // Get the data. 716 log.debug("Attempting to get DataDDS."); 717 dds = dapConnection.getData(ce); 718 log.debug("Got DataDDS."); 719 // dds.print(System.out); 720 721 log.debug("Squeezing DDS arrays."); 722 squeezeArrays(dds); 723 724 // Extract the metadata we got from the ddx. 725 log.debug("Retrieving DAS from DDX."); 726 das = ddx.getDAS(); 727 728 // Tie the extracted metadata back into the DataDDS 729 log.debug("Calling DDS.ingestDAS()."); 730 dds.ingestDAS(das); 731 // dds.print(System.out); 732 733 return dds; 734 } 735 /* 736 * End of the ugly bit. Once the bug in DConnect2 is fixed we can 737 * replace this with a call to getDataDDX(). 738 * 739 * ----------------------------------------------------------- 740 */ 741 // dds.print(System.out); 742 return ddx; 743 744 } 745 746 /** 747 * Eliminates array dimensions whose dimensions are 1 (and thus in practice 748 * don't exist) 749 * 750 * @param dds 751 * The DDS to traverse and squeeze its member arrays. 752 */ 753 public static void squeezeArrays(DConstructor dds) { 754 755 DArray a; 756 757 Enumeration e = dds.getVariables(); 758 while (e.hasMoreElements()) { 759 opendap.dap.BaseType bt = (opendap.dap.BaseType) e.nextElement(); 760 761 if (bt instanceof DArray) { 762 a = (DArray) bt; 763 log.debug("Squeezing array " + a.getTypeName() + " " 764 + a.getLongName() + ";"); 765 a.squeeze(); 766 // System.out.print("Post squeezing: "); 767 // a.printDecl(System.out); 768 bt = a.getPrimitiveVector().getTemplate(); 769 if (bt instanceof DConstructor) 770 squeezeArrays((DConstructor) bt); 771 } else if (bt instanceof DConstructor) { 772 squeezeArrays((DConstructor) bt); 773 } 774 775 } 776 } 777 778}