public void init() throws javax.servlet.ServletException {
    super.init();

    org.slf4j.Logger logServerStartup = org.slf4j.LoggerFactory.getLogger("serverStartup");
    logServerStartup.info(
        getClass().getName() + " initialization start - " + UsageLog.setupNonRequestContext());

    this.ascLimit = ThreddsConfig.getInt("Opendap.ascLimit", ascLimit);
    this.binLimit = ThreddsConfig.getInt("Opendap.binLimit", binLimit);

    this.odapVersionString = ThreddsConfig.get("Opendap.serverVersion", odapVersionString);
    logServerStartup.info(
        getClass().getName()
            + " version= "
            + odapVersionString
            + " ascLimit = "
            + ascLimit
            + " binLimit = "
            + binLimit);

    // debugging actions
    makeDebugActions();

    logServerStartup.info(
        getClass().getName()
            + " initialization done - "
            + UsageLog.closingMessageNonRequestContext());
  }
/** by Mr Skip on 09.02.2016. */
@Service
public class BusInfoService implements BusInfoLogic {
  private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(BusInfoService.class);

  @Autowired private BusInfoRepo busInfoRepo;

  @Override
  public BusInfo getByDriverName(String name) {
    BusInfo busInfo;
    if ((busInfo = busInfoRepo.getBusByDriverName(name)) == null) {
      LOGGER.warn(
          "Not find any Bus for driver name:" + name + ". In class: " + getClass().getName());
      busInfo = new BusInfo();
    }
    return busInfo;
  }

  @Override
  public BusInfo getByDriverPhone(String phone) {
    BusInfo busInfo;
    if ((busInfo = busInfoRepo.getBusByDriverPhone(phone)) == null) {
      LOGGER.warn(
          "Not find any Bus for driver phone:" + phone + ". In class: " + getClass().getName());
      busInfo = new BusInfo();
    }
    return busInfo;
  }

  @Override
  public List<BusInfo> getAll() {
    List<BusInfo> list;
    if ((list = busInfoRepo.findAll()).isEmpty()) {
      LOGGER.warn("Cant find any Bus in class: " + getClass().getName());
    }
    return list;
  }

  @Override
  public Map<Integer, String> getAllBusNumber() {
    Map<Integer, String> list = new HashMap<>();
    for (String s : busInfoRepo.getAllBusNumber()) {
      list.put(list.size() + 1, s);
    }
    return list;
  }
}
/** @author pepijn */
public class WPPluginManager {
  private WPPluginManager(UUID uuid, String logPrefix, ClassLoader classLoader) {
    allPlugins = org.pepsoft.util.PluginManager.findPlugins(Plugin.class, FILENAME, classLoader);
    Set<String> namesEncountered = new HashSet<>();
    for (Iterator<Plugin> i = allPlugins.iterator(); i.hasNext(); ) {
      Plugin plugin = i.next();
      if ((plugin.getUUIDs() != null) && (uuid != null) && (!plugin.getUUIDs().contains(uuid))) {
        logger.error(
            logPrefix
                + plugin.getName()
                + " plugin is not authorised for this installation; not loading it");
        i.remove();
        continue;
      }
      String name = plugin.getName();
      if (namesEncountered.contains(name)) {
        throw new RuntimeException("Multiple plugins with the same name (" + name + ") detected!");
      } else {
        namesEncountered.add(name);
      }
      logger.info(logPrefix + "Loaded plugin: " + name + " (version " + plugin.getVersion() + ")");
    }
  }

  public List<Plugin> getAllPlugins() {
    return Collections.unmodifiableList(allPlugins);
  }

  @SuppressWarnings("unchecked") // Guaranteed by Java
  public <T extends Plugin> List<T> getPlugins(Class<T> type) {
    List<T> plugins = new ArrayList<>(allPlugins.size());
    plugins.addAll(
        allPlugins
            .stream()
            .filter(plugin -> type.isAssignableFrom(plugin.getClass()))
            .map(plugin -> (T) plugin)
            .collect(Collectors.toList()));
    return plugins;
  }

  public static synchronized void initialise(UUID uuid) {
    initialise(uuid, "", ClassLoader.getSystemClassLoader());
  }

  public static synchronized void initialise(UUID uuid, String logPrefix, ClassLoader classLoader) {
    if (instance != null) {
      throw new IllegalStateException("Already initialised");
    }
    instance = new WPPluginManager(uuid, logPrefix, classLoader);
  }

  public static synchronized WPPluginManager getInstance() {
    return instance;
  }

  private final List<Plugin> allPlugins;

  private static WPPluginManager instance;
  private static final String FILENAME = "org.pepsoft.worldpainter.plugins";
  private static final org.slf4j.Logger logger =
      org.slf4j.LoggerFactory.getLogger(WPPluginManager.class);
}
public class HTMLDriver extends PanelDriver implements Exportable {
  private static transient org.slf4j.Logger log =
      org.slf4j.LoggerFactory.getLogger(HTMLDriver.class.getName());

  public static final String PARAMETER_HTML = "html_code";
  public static final String PARAMETER_EDITING_LANG = "edit_lang";

  private static final String PAGE_SHOW = "show";
  private static final String PAGE_EDIT = "edit";

  private static final String PAGE_CHOOSE_IMAGE = "imageSelect";
  private static final String PAGE_CHOOSE_LINK = "linkSelect";

  public static final String PARAMETER_USE_DEFAULTS = "useDefaultLanguage";

  private static final String ATTR_TEXT = "text";
  private static final String ATTR_EDITING_LANGUAGE = "lang";

  /** The locale manager. */
  protected LocaleManager localeManager;

  public HTMLDriver() {
    localeManager = LocaleManager.lookup();
  }

  public void init(PanelProvider provider) throws Exception {
    super.init(provider);
    addParameter(new BooleanParameter(provider, PARAMETER_USE_DEFAULTS, false, true));
    String[] methodsForEditMode = new String[] {"actionChangeEditingLanguage", "actionSaveChanges"};
    for (int i = 0; i < methodsForEditMode.length; i++) {
      String method = methodsForEditMode[i];
      addMethodPermission(method, PanelPermission.class, PanelPermission.ACTION_EDIT);
    }
  }

  public void initPanelSession(PanelSession status, HttpSession session) {
    status.setCurrentPageId(PAGE_SHOW);
  }

  protected void beforePanelInstanceRemove(final PanelInstance instance) throws Exception {
    super.beforePanelInstanceRemove(instance);

    new HibernateTxFragment() {
      protected void txFragment(Session session) throws Exception {
        HTMLText htmlText = load(instance);
        if (htmlText != null) htmlText.delete();
      }
    }.execute();
  }

  /** Returns if this driver defines support to activate edit mode. */
  public boolean supportsEditMode(Panel panel) {
    return true;
  }

  public int getEditWidth(Panel panel, CommandRequest request) {
    return 1000;
  }

  public int getEditHeight(Panel panel, CommandRequest request) {
    return 695;
  }

  /** Defines the action to be taken when activating edit mode */
  public void activateEditMode(Panel panel, CommandRequest request) throws Exception {
    super.activateEditMode(panel, request);
    HTMLText text = load(panel.getInstance());
    SessionManager.getPanelSession(panel).setAttribute(ATTR_TEXT, toEditableObject(text));
  }

  protected Map toEditableObject(HTMLText text) {
    Map m = new HashMap();
    for (Iterator it = text.getText().keySet().iterator(); it.hasNext(); ) {
      String lang = (String) it.next();
      String val = text.getText(lang);
      m.put(lang, val);
    }
    return m;
  }

  /** Defines the action to be taken when activating edit mode */
  public void activateNormalMode(Panel panel, CommandRequest request) throws Exception {
    super.activateNormalMode(panel, request);
    SessionManager.getPanelSession(panel).removeAttribute(ATTR_TEXT);
    SessionManager.getPanelSession(panel).removeAttribute(ATTR_EDITING_LANGUAGE);
  }

  /**
   * Returns if this driver is using default language
   *
   * @param panel
   * @return if this driver is using default language
   */
  public boolean isUsingDefaultLanguage(Panel panel) {
    return Boolean.valueOf(panel.getParameterValue(PARAMETER_USE_DEFAULTS)).booleanValue();
  }

  /**
   * Returns if this driver is using default language
   *
   * @param panel
   * @return if this driver is using default language
   */
  public boolean isUsingDefaultLanguage(PanelInstance panel) {
    return Boolean.valueOf(panel.getParameterValue(PARAMETER_USE_DEFAULTS)).booleanValue();
  }

  /**
   * Determine the text being shown for given panel.
   *
   * @param panel
   * @return The text shown, i18n.
   */
  public Map getHtmlCode(Panel panel) {
    PanelSession pSession = SessionManager.getPanelSession(panel);
    Map m = (Map) pSession.getAttribute(ATTR_TEXT);
    if (m != null) return m;
    HTMLText text = load(panel.getInstance());
    if (text != null) return text.getText();
    try {
      HTMLText textToCreate = new HTMLText();
      textToCreate.setPanelInstance(panel.getInstance());
      Locale[] locales = LocaleManager.lookup().getPlatformAvailableLocales();
      for (int i = 0; i < locales.length; i++) {
        Locale locale = locales[i];
        ResourceBundle i18n =
            localeManager.getBundle("org.jboss.dashboard.ui.panel.advancedHTML.messages", locale);
        textToCreate.setText(locale.getLanguage(), i18n.getString("defaultContent"));
      }
      textToCreate.save();
    } catch (Exception e) {
      log.error("Error creating empty text for panel: ", e);
    }
    text = load(panel.getInstance());
    if (text != null) return text.getText();
    log.error("Current HTML code is null for panel " + panel);
    return null;
  }

  /**
   * Determine the editing language.
   *
   * @return The text shown, i18n.
   */
  public String getEditingLanguage(Panel panel) {
    String lang =
        (String) SessionManager.getPanelSession(panel).getAttribute(ATTR_EDITING_LANGUAGE);
    return lang == null ? LocaleManager.lookup().getDefaultLang() : lang;
  }

  public CommandResponse actionChangeEditingLanguage(Panel panel, CommandRequest request)
      throws Exception {
    String currentText = request.getRequestObject().getParameter(PARAMETER_HTML);
    Map text = (Map) SessionManager.getPanelSession(panel).getAttribute(ATTR_TEXT);
    if (text == null) {
      text = toEditableObject(load(panel.getInstance()));
      SessionManager.getPanelSession(panel).setAttribute(ATTR_TEXT, text);
    }
    text.put(getEditingLanguage(panel), currentText);
    SessionManager.getPanelSession(panel).setAttribute(ATTR_TEXT, text);
    String paramLang = request.getRequestObject().getParameter(PARAMETER_EDITING_LANG);
    SessionManager.getPanelSession(panel).setAttribute(ATTR_EDITING_LANGUAGE, paramLang);
    return new ShowPanelPage();
  }

  public CommandResponse actionSaveChanges(Panel panel, CommandRequest request) throws Exception {
    String currentText = request.getRequestObject().getParameter(PARAMETER_HTML);
    Map m = (Map) SessionManager.getPanelSession(panel).getAttribute(ATTR_TEXT);
    HTMLText text = load(panel.getInstance());
    for (Iterator it = m.keySet().iterator(); it.hasNext(); ) {
      String lang = (String) it.next();
      String val = (String) m.get(lang);
      text.setText(lang, val);
    }
    text.setText(getEditingLanguage(panel), currentText);
    text.save();
    activateNormalMode(panel, request);
    return new ShowPanelPage();
  }

  public CommandResponse actionSelectImage(Panel panel, CommandRequest request) throws Exception {
    return new ShowPopupPanelPage(panel, PAGE_CHOOSE_IMAGE);
  }

  public CommandResponse actionSelectLink(Panel panel, CommandRequest request) throws Exception {
    return new ShowPopupPanelPage(panel, PAGE_CHOOSE_LINK);
  }

  public HTMLText load(final PanelInstance instance) {
    final List results = new ArrayList();
    try {
      new HibernateTxFragment() {
        protected void txFragment(Session session) throws Exception {
          FlushMode oldFlushMode = session.getFlushMode();
          session.setFlushMode(FlushMode.NEVER);
          Query query =
              session.createQuery(
                  " from "
                      + HTMLText.class.getName()
                      + " as text where text.panelInstance = :instance");
          query.setParameter("instance", instance);
          query.setCacheable(true);
          results.addAll(query.list());
          session.setFlushMode(oldFlushMode);
        }
      }.execute();
      HTMLText text = null;
      if (results.size() > 0) text = (HTMLText) results.get(0);
      else log.debug("Does not exist a html_text for HTML panel");
      return text;
    } catch (Exception e) {
      log.error("Can't retrive a data for HTML panel ", e);
      return null;
    }
  }

  /**
   * Replicates panel data.
   *
   * @param src Source PanelInstance
   * @param dest Destinaton PanelInstance
   */
  public void replicateData(final PanelInstance src, PanelInstance dest) throws Exception {
    super.replicateData(src, dest);
    log.debug(
        "HTMLDriver replicating Data from PanelInstance "
            + src.getDbid()
            + " to "
            + dest.getDbid()
            + ").");
    if (src.equals(dest)) {
      log.debug("Ignoring replication, panel instance is the same.");
      return;
    }

    final HTMLText[] textArray = new HTMLText[1];
    try {
      new HibernateTxFragment() {
        protected void txFragment(Session session) throws Exception {
          log.debug("Getting text to duplicate for instance " + src.getDbid());
          FlushMode oldMode = session.getFlushMode();
          session.setFlushMode(
              FlushMode
                  .COMMIT); // Avoids flushing, as we know the text was not modified in this
                            // transaction.
          textArray[0] = load(src);
          session.setFlushMode(oldMode);
          log.debug("Got text to duplicate for instance " + src.getDbid());
        }
      }.execute();
    } catch (Exception e) {
      log.error("Error loading text for instance. ", e);
    }
    HTMLText text = textArray[0];
    if (text == null) {
      log.debug(
          "Nothing to replicate from PanelInstance " + src.getDbid() + " to " + dest.getDbid());
      return;
    }
    Map htmlSrc = text.getText();

    log.debug("htmlCode to replicate = " + htmlSrc);
    HTMLText htmlDest = new HTMLText();
    htmlDest.setPanelInstance(dest.getInstance());
    for (Iterator it = htmlSrc.keySet().iterator(); it.hasNext(); ) {
      String key = (String) it.next();
      String val = (String) htmlSrc.get(key);
      htmlDest.setText(key, val);
    }
    try {
      log.debug("Updating HTMLText: IDText " + htmlDest.getDbid() + " Text " + htmlDest.getText());
      htmlDest.save();
    } catch (Exception e) {
      log.error("Replicating panel data", e);
    }
  }

  /** Write instance content to given OutputStream, which must not be closed. */
  public void exportContent(PanelInstance instance, OutputStream os) throws Exception {
    HTMLText text = load(instance);
    if (text == null) {
      try {
        text = new HTMLText();
        text.setPanelInstance(instance);
        text.save();
      } catch (Exception e) {
        log.error("Error creating empty HTMLText object", e);
      }
    }
    ObjectOutputStream oos = new ObjectOutputStream(os);
    if (log.isDebugEnabled()) log.debug("Exporting content: " + text.getText());
    HashMap h = new HashMap(); // Avoids serializing a hibernate map
    h.putAll(text.getText());
    oos.writeObject(h);
  }

  /** Read instance content from given InputStream, which must not be closed. */
  public void importContent(PanelInstance instance, InputStream is) throws Exception {
    HTMLText currentText = new HTMLText();
    currentText.setPanelInstance(instance);
    ObjectInputStream ois = new ObjectInputStream(is);
    Map text = (Map) ois.readObject();
    if (log.isDebugEnabled()) log.debug("Importing content: " + text);
    for (Iterator it = text.keySet().iterator(); it.hasNext(); ) {
      String lang = (String) it.next();
      String value = (String) text.get(lang);
      currentText.setText(lang, value);
    }
    currentText.save();
  }

  protected String getPanelHTMLContent(PanelInstance instance, String lang) {
    HTMLText text = load(instance);
    if (text != null) {
      String val = text.getText(lang);
      if (StringUtils.isEmpty(val) && isUsingDefaultLanguage(instance)) {
        LocaleManager localeManager = LocaleManager.lookup();
        val = text.getText(localeManager.getDefaultLang());
      }
      return val;
    }
    return null;
  }
}
/**
 * Choice list initializer that looks up direct sub types from the specified type If no param is
 * specified, get the list of nodeTypes
 */
public class SubNodeTypesChoiceListInitializerImpl implements ChoiceListInitializer {

  private static transient Logger logger =
      org.slf4j.LoggerFactory.getLogger(SubNodeTypesChoiceListInitializerImpl.class);

  @SuppressWarnings("unchecked")
  public List<ChoiceListValue> getChoiceListValues(
      ExtendedPropertyDefinition epd,
      String param,
      List<ChoiceListValue> values,
      Locale locale,
      Map<String, Object> context) {
    final SortedSet<ChoiceListValue> listValues = new TreeSet<ChoiceListValue>();
    if (StringUtils.isEmpty(param)) {
      param = "jmix:editorialContent";
    }
    try {
      String includedTypes = StringUtils.substringBefore(param, ";");

      Set<String> excludedTypes = new HashSet<String>();
      String exclusion = StringUtils.substringAfter(param, ";");
      if (StringUtils.isNotBlank(exclusion)) {
        excludedTypes.addAll(
            CollectionUtils.collect(
                Arrays.asList(StringUtils.substringAfter(param, ";").split(",")),
                new Transformer() {
                  public Object transform(Object input) {
                    return ((String) input).trim();
                  }
                }));
      }

      for (String nodeTypeName : includedTypes.split(",")) {
        nodeTypeName = nodeTypeName.trim();
        ExtendedNodeType nodeType = NodeTypeRegistry.getInstance().getNodeType(nodeTypeName);
        if (!isExcludedType(nodeType, excludedTypes)) {
          listValues.add(new ChoiceListValue(nodeType.getLabel(locale), nodeType.getName()));
        }
        NodeTypeIterator nti = nodeType.getSubtypes();
        while (nti.hasNext()) {
          ExtendedNodeType type = (ExtendedNodeType) nti.next();
          if (!isExcludedType(type, excludedTypes)) {
            listValues.add(new ChoiceListValue(type.getLabel(locale), type.getName()));
          }
        }
      }
    } catch (NoSuchNodeTypeException e) {
      logger.error("Cannot get type", e);
    }

    return new LinkedList<ChoiceListValue>(listValues);
  }

  private boolean isExcludedType(ExtendedNodeType nodeType, Set<String> excludedTypes) {
    if (excludedTypes.contains(nodeType.getName())) {
      return true;
    }
    boolean isExcluded = false;
    for (String excludedType : excludedTypes) {
      if (nodeType.isNodeType(excludedType)) {
        isExcluded = true;
        break;
      }
    }
    return isExcluded;
  }
}
Exemple #6
0
/**
 * _more_
 *
 * @author edavis
 * @since 4.0
 */
public class WcsCoverage {
  private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WcsCoverage.class);

  private GridDataset.Gridset coverage;
  private WcsDataset dataset;

  private String name;
  private String label;
  private String description;

  private GridCoordSystem coordSys;
  private String nativeCRS;

  private String defaultRequestCrs;

  private List<WcsRequest.Format> supportedCoverageFormatList;

  private HashMap<String, WcsRangeField> range;

  public WcsCoverage(GridDataset.Gridset coverage, WcsDataset dataset) {
    this.dataset = dataset;
    if (this.dataset == null) {
      log.error("WcsCoverage(): non-null dataset required.");
      throw new IllegalArgumentException("Non-null dataset required.");
    }

    this.coverage = coverage;
    if (this.coverage == null) {
      log.error("WcsCoverage(): non-null coverage required.");
      throw new IllegalArgumentException("Non-null coverage required.");
    }
    this.coordSys = coverage.getGeoCoordSystem();
    if (this.coordSys == null) {
      log.error("WcsCoverage(): Coverage must have non-null coordinate system.");
      throw new IllegalArgumentException("Non-null coordinate system required.");
    }

    this.name = this.coordSys.getName();
    this.label = this.coordSys.getName();

    this.range = new HashMap<String, WcsRangeField>();
    StringBuilder descripSB =
        new StringBuilder("All parameters on the \"")
            .append(this.name)
            .append("\" coordinate system: ");
    for (GridDatatype curField : this.coverage.getGrids()) {
      String stdName = curField.findAttValueIgnoreCase("standard_name", "");
      descripSB.append(stdName.length() == 0 ? curField.getFullName() : stdName).append(",");

      WcsRangeField field = new WcsRangeField(curField);
      range.put(field.getName(), field);
    }
    descripSB.setCharAt(descripSB.length() - 1, '.');
    this.description = descripSB.toString();

    this.nativeCRS = EPSG_OGC_CF_Helper.getWcs1_0CrsId(this.coordSys.getProjection());

    this.defaultRequestCrs = "OGC:CRS84";

    this.supportedCoverageFormatList = new ArrayList<WcsRequest.Format>();
    // this.supportedCoverageFormatList = "application/x-netcdf";
    this.supportedCoverageFormatList.add(WcsRequest.Format.GeoTIFF);
    this.supportedCoverageFormatList.add(WcsRequest.Format.GeoTIFF_Float);
    this.supportedCoverageFormatList.add(WcsRequest.Format.NetCDF3);
  }

  GridDataset.Gridset getGridset() {
    return coverage;
  }

  public String getName() {
    return this.name;
  }

  public String getLabel() {
    return this.label;
  }

  public String getDescription() {
    return this.description;
  }

  public GridCoordSystem getCoordinateSystem() {
    return coordSys;
  }

  public String getDefaultRequestCrs() {
    return defaultRequestCrs;
  }

  public String getNativeCrs() {
    return nativeCRS;
  }

  public List<WcsRequest.Format> getSupportedCoverageFormatList() {
    return Collections.unmodifiableList(supportedCoverageFormatList);
  }

  public boolean isSupportedCoverageFormat(WcsRequest.Format covFormat) {
    return this.supportedCoverageFormatList.contains(covFormat);
  }

  public boolean isRangeFieldName(String fieldName) {
    return range.containsKey(fieldName);
  }

  public Set<String> getRangeFieldNames() {
    return range.keySet();
  }

  public Collection<WcsRangeField> getRange() {
    return range.values();
  }

  private static DiskCache2 diskCache = null;

  public static void setDiskCache(DiskCache2 _diskCache) {
    diskCache = _diskCache;
  }

  private static DiskCache2 getDiskCache() {
    if (diskCache == null) {
      log.error("getDiskCache(): Disk cache has not been set.");
      throw new IllegalStateException(
          "Disk cache must be set before calling GetCoverage.getDiskCache().");
    }
    return diskCache;
  }

  public File writeCoverageDataToFile(
      WcsRequest.Format format,
      LatLonRect bboxLatLonRect,
      AxisSubset vertSubset,
      List<String> rangeSubset,
      CalendarDateRange timeRange)
      throws WcsException {
    boolean zRangeDone = false;
    boolean tRangeDone = false;

    try {
      // Get the height range.
      Range zRange = vertSubset != null ? vertSubset.getRange() : null;
      zRangeDone = true;

      // Get the time range.
      Range tRange = null;
      if (timeRange != null) {
        CoordinateAxis1DTime timeAxis = this.coordSys.getTimeAxis1D();
        int startIndex = timeAxis.findTimeIndexFromCalendarDate(timeRange.getStart());
        int endIndex = timeAxis.findTimeIndexFromCalendarDate(timeRange.getEnd());
        tRange = new Range(startIndex, endIndex);
        tRangeDone = true;
      }

      if (format == WcsRequest.Format.GeoTIFF || format == WcsRequest.Format.GeoTIFF_Float) {
        if (rangeSubset.size() != 1) {
          String msg =
              "GeoTIFF response encoding only available for single range field selection ["
                  + rangeSubset
                  + "].";
          log.error("writeCoverageDataToFile(): " + msg);
          throw new WcsException(WcsException.Code.InvalidParameterValue, "RangeSubset", msg);
        }
        String reqRangeFieldName = rangeSubset.get(0);

        File dir = new File(getDiskCache().getRootDirectory());
        File tifFile = File.createTempFile("WCS", ".tif", dir);
        if (log.isDebugEnabled())
          log.debug("writeCoverageDataToFile(): tifFile=" + tifFile.getPath());

        WcsRangeField rangeField = this.range.get(reqRangeFieldName);
        GridDatatype subset =
            rangeField.getGridDatatype().makeSubset(tRange, zRange, bboxLatLonRect, 1, 1, 1);
        Array data = subset.readDataSlice(0, 0, -1, -1);

        GeotiffWriter writer = new GeotiffWriter(tifFile.getPath());
        writer.writeGrid(
            this.dataset.getDataset(), subset, data, format == WcsRequest.Format.GeoTIFF);

        writer.close();

        return tifFile;
      } else if (format == WcsRequest.Format.NetCDF3) {
        File dir = new File(getDiskCache().getRootDirectory());
        File outFile = File.createTempFile("WCS", ".nc", dir);
        if (log.isDebugEnabled())
          log.debug("writeCoverageDataToFile(): ncFile=" + outFile.getPath());
        // WTF ?? this.coordSys.getVerticalAxis().isNumeric();

        NetcdfFileWriter writer =
            NetcdfFileWriter.createNew(NetcdfFileWriter.Version.netcdf3, outFile.getAbsolutePath());
        CFGridWriter2.writeFile(
            this.dataset.getDataset(),
            rangeSubset,
            bboxLatLonRect,
            null,
            1,
            zRange,
            timeRange,
            1,
            true,
            writer);
        return outFile;
      } else {
        log.error(
            "writeCoverageDataToFile(): Unsupported response encoding format [" + format + "].");
        throw new WcsException(
            WcsException.Code.InvalidFormat,
            "Format",
            "Unsupported response encoding format [" + format + "].");
      }
    } catch (InvalidRangeException e) {
      String msg = "Failed to subset coverage [" + this.getName();
      if (!zRangeDone) msg += "] along vertical axis [" + vertSubset + "]. ";
      else if (!tRangeDone) msg += "] along time axis [" + timeRange + "]. ";
      else msg += "] in horizontal plane [" + bboxLatLonRect + "]. ";
      log.error("writeCoverageDataToFile(): " + msg + e.getMessage());
      throw new WcsException(WcsException.Code.CoverageNotDefined, "", msg);
    } catch (IOException e) {
      log.error(
          "writeCoverageDataToFile(): Failed to write file for requested coverage <"
              + this.getName()
              + ">: "
              + e.getMessage());
      throw new WcsException(
          WcsException.Code.UNKNOWN, "", "Problem creating coverage [" + this.getName() + "].");
    }
  }
}
/**
 * THREDDS opendap server.
 *
 * @author jcaron
 * @author Nathan David Potter
 * @since Apr 27, 2009 (branched)
 */
public class OpendapServlet extends AbstractServlet {
  static final String DEFAULTCONTEXTPATH = "/thredds";
  static final String GDATASET = "guarded_dataset";
  private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OpendapServlet.class);

  private boolean allowSessions = false;
  private boolean allowDeflate = false; // handled by Tomcat

  private String odapVersionString = "opendap/3.7";

  private URI baseURI = null;

  private int ascLimit = 50;
  private int binLimit = 500;

  private boolean debugSession = false;

  public String getDefaultContextPath() {
    return DEFAULTCONTEXTPATH;
  }

  public void init() throws javax.servlet.ServletException {
    super.init();

    org.slf4j.Logger logServerStartup = org.slf4j.LoggerFactory.getLogger("serverStartup");
    logServerStartup.info(
        getClass().getName() + " initialization start - " + UsageLog.setupNonRequestContext());

    this.ascLimit = ThreddsConfig.getInt("Opendap.ascLimit", ascLimit);
    this.binLimit = ThreddsConfig.getInt("Opendap.binLimit", binLimit);

    this.odapVersionString = ThreddsConfig.get("Opendap.serverVersion", odapVersionString);
    logServerStartup.info(
        getClass().getName()
            + " version= "
            + odapVersionString
            + " ascLimit = "
            + ascLimit
            + " binLimit = "
            + binLimit);

    // debugging actions
    makeDebugActions();

    logServerStartup.info(
        getClass().getName()
            + " initialization done - "
            + UsageLog.closingMessageNonRequestContext());
  }

  public String getServerVersion() {
    return this.odapVersionString;
  }

  // Servlets that support HTTP GET requests and can quickly determine their last modification time
  // should
  // override this method. This makes browser and proxy caches work more effectively, reducing the
  // load on
  // server and network resources.
  protected long getLastModified(HttpServletRequest req) {
    String query = req.getQueryString();
    if (query != null) return -1;

    String path = req.getPathInfo();
    if (path == null) return -1;

    if (path.endsWith(".asc")) path = path.substring(0, path.length() - 4);
    else if (path.endsWith(".ascii")) path = path.substring(0, path.length() - 6);
    else if (path.endsWith(".das")) path = path.substring(0, path.length() - 4);
    else if (path.endsWith(".dds")) path = path.substring(0, path.length() - 4);
    else if (path.endsWith(".ddx")) path = path.substring(0, path.length() - 4);
    else if (path.endsWith(".dods")) path = path.substring(0, path.length() - 5);
    else if (path.endsWith(".html")) path = path.substring(0, path.length() - 5);
    else if (path.endsWith(".info")) path = path.substring(0, path.length() - 5);
    else if (path.endsWith(".opendap")) path = path.substring(0, path.length() - 5);
    else return -1;

    // if (null != DatasetHandler.findResourceControl( path)) return -1; // LOOK weird Firefox
    // beahviour?

    File file = DataRootHandler.getInstance().getCrawlableDatasetAsFile(path);
    if ((file != null) && file.exists()) return file.lastModified();

    return -1;
  }

  /////////////////////////////////////////////////////////////////////////////

  public void doGet(HttpServletRequest request, HttpServletResponse response) {
    log.info("doGet(): " + UsageLog.setupRequestContext(request));
    // System.out.printf("opendap doGet: req=%s%n%s%n", ServletUtil.getRequest(request),
    // ServletUtil.showRequestDetail(this, request));

    String path = null;

    ReqState rs = getRequestState(request, response);

    try {
      path = request.getPathInfo();
      log.debug("doGet path={}", path);

      if (thredds.servlet.Debug.isSet("showRequestDetail"))
        log.debug(ServletUtil.showRequestDetail(this, request));

      if (path == null) {
        log.info(
            "doGet(): "
                + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, -1));
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
      }

      if (baseURI == null) { // first time, set baseURI
        URI reqURI = ServletUtil.getRequestURI(request);
        // Build base URI from request (rather than hard-coding "/thredds/dodsC/").
        String baseUriString = request.getContextPath() + request.getServletPath() + "/";
        baseURI = reqURI.resolve(baseUriString);
        log.debug("doGet(): baseURI was set = {}", baseURI);
      }

      if (path.endsWith("latest.xml")) {
        DataRootHandler.getInstance().processReqForLatestDataset(this, request, response);
        return;
      }

      // Redirect all catalog requests at the root level.
      if (path.equals("/") || path.equals("/catalog.html") || path.equals("/catalog.xml")) {
        ServletUtil.sendPermanentRedirect(ServletUtil.getContextPath() + path, request, response);
        return;
      }

      // Make sure catalog requests match a dataRoot before trying to handle.
      if (path.endsWith("/") || path.endsWith("/catalog.html") || path.endsWith("/catalog.xml")) {
        if (!DataRootHandler.getInstance().hasDataRootMatch(path)) {
          log.info(
              "doGet(): "
                  + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, -1));
          response.sendError(HttpServletResponse.SC_NOT_FOUND);
          return;
        }

        if (!DataRootHandler.getInstance().processReqForCatalog(request, response))
          log.error(
              "doGet(): "
                  + UsageLog.closingMessageForRequestContext(
                      ServletUtil.STATUS_FORWARD_FAILURE, -1));

        return;
      }

      if (rs != null) {
        String dataSet = rs.getDataSet();
        String requestSuffix = rs.getRequestSuffix();

        if ((dataSet == null) || dataSet.equals("/") || dataSet.equals("")) {
          doGetDIR(rs);
        } else if (requestSuffix.equalsIgnoreCase("blob")) {
          doGetBLOB(rs);
        } else if (requestSuffix.equalsIgnoreCase("close")) {
          doClose(rs);
        } else if (requestSuffix.equalsIgnoreCase("dds")) {
          doGetDDS(rs);
        } else if (requestSuffix.equalsIgnoreCase("das")) {
          doGetDAS(rs);
        } else if (requestSuffix.equalsIgnoreCase("ddx")) {
          doGetDDX(rs);
        } else if (requestSuffix.equalsIgnoreCase("dods")) {
          doGetDAP2Data(rs);
        } else if (requestSuffix.equalsIgnoreCase("asc")
            || requestSuffix.equalsIgnoreCase("ascii")) {
          doGetASC(rs);
        } else if (requestSuffix.equalsIgnoreCase("info")) {
          doGetINFO(rs);
        } else if (requestSuffix.equalsIgnoreCase("html")
            || requestSuffix.equalsIgnoreCase("htm")) {
          doGetHTML(rs);
        } else if (requestSuffix.equalsIgnoreCase("ver")
            || requestSuffix.equalsIgnoreCase("version")
            || dataSet.equalsIgnoreCase("/version")
            || dataSet.equalsIgnoreCase("/version/")) {
          doGetVER(rs);
        } else if (dataSet.equalsIgnoreCase("/help")
            || dataSet.equalsIgnoreCase("/help/")
            || dataSet.equalsIgnoreCase("/" + requestSuffix)
            || requestSuffix.equalsIgnoreCase("help")) {
          doGetHELP(rs);
        } else {
          sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST, "Unrecognized request");
          return;
        }

      } else {
        sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST, "Unrecognized request");
        return;
      }

      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, -1));

      // plain ol' 404
    } catch (FileNotFoundException e) {
      sendErrorResponse(response, HttpServletResponse.SC_NOT_FOUND, e.getMessage());

      // DAP2Exception bad url
    } catch (BadURLException e) {
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, -1));
      response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
      dap2ExceptionHandler(e, rs);

      // all other DAP2Exception
    } catch (DAP2Exception de) {
      int status =
          (de.getErrorCode() == DAP2Exception.NO_SUCH_FILE)
              ? HttpServletResponse.SC_NOT_FOUND
              : HttpServletResponse.SC_BAD_REQUEST;
      if ((de.getErrorCode() != DAP2Exception.NO_SUCH_FILE) && (de.getErrorMessage() != null))
        log.debug(de.getErrorMessage());
      log.info(UsageLog.closingMessageForRequestContext(status, -1));
      response.setStatus(status);
      dap2ExceptionHandler(de, rs);

      // parsing, usually the CE
    } catch (ParseException pe) {
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, -1));
      response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
      parseExceptionHandler(pe, response);

      // 403 - request too big
    } catch (UnsupportedOperationException e) {
      sendErrorResponse(response, HttpServletResponse.SC_FORBIDDEN, e.getMessage());

    } catch (java.net.SocketException e) {
      log.info("SocketException: " + e.getMessage(), e);
      log.info(UsageLog.closingMessageForRequestContext(ServletUtil.STATUS_CLIENT_ABORT, -1));

    } catch (IOException e) {
      String eName =
          e.getClass().getName(); // dont want compile time dependency on ClientAbortException
      if (eName.equals("org.apache.catalina.connector.ClientAbortException")) {
        log.debug("ClientAbortException: " + e.getMessage());
        log.info(UsageLog.closingMessageForRequestContext(ServletUtil.STATUS_CLIENT_ABORT, -1));
        return;
      }

      log.error("path= " + path, e);
      sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());

      // everything else
    } catch (Throwable t) {
      log.error("path= " + path, t);
      t.printStackTrace();
      sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, t.getMessage());
    }
  }

  public void doGetASC(ReqState rs) throws Exception {
    HttpServletResponse response = rs.getResponse();

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (ds == null) return;

      response.setHeader("XDODS-Server", getServerVersion());
      response.setContentType("text/plain");
      response.setHeader("Content-Description", "dods-ascii");

      log.debug(
          "Sending OPeNDAP ASCII Data For: " + rs + "  CE: '" + rs.getConstraintExpression() + "'");

      ServerDDS dds = ds.getDDS();
      CEEvaluator ce = new CEEvaluator(dds);
      ce.parseConstraint(rs);
      checkSize(dds, true);

      PrintWriter pw = new PrintWriter(response.getOutputStream());
      dds.printConstrained(pw);
      pw.println("---------------------------------------------");

      AsciiWriter writer = new AsciiWriter(); // could be static
      writer.toASCII(pw, dds, ds);

      // the way that getDAP2Data works
      // DataOutputStream sink = new DataOutputStream(bOut);
      // ce.send(myDDS.getName(), sink, ds);

      pw.flush();

    } finally { // release lock if needed
      if (ds != null) ds.release();
    }
  }

  public void doGetDAS(ReqState rs) throws Exception {
    HttpServletResponse response = rs.getResponse();

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (ds == null) return;

      response.setContentType("text/plain");
      response.setHeader("XDODS-Server", getServerVersion());
      response.setHeader("Content-Description", "dods-das");

      OutputStream Out = new BufferedOutputStream(response.getOutputStream());

      DAS myDAS = ds.getDAS();
      myDAS.print(Out);

    } finally { // release lock if needed
      if (ds != null) ds.release();
    }
  }

  public void doGetDDS(ReqState rs) throws Exception {
    HttpServletResponse response = rs.getResponse();

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (null == ds) return;

      response.setContentType("text/plain");
      response.setHeader("XDODS-Server", getServerVersion());
      response.setHeader("Content-Description", "dods-dds");

      OutputStream out = new BufferedOutputStream(response.getOutputStream());
      ServerDDS myDDS = ds.getDDS();

      if (rs.getConstraintExpression().equals("")) { // No Constraint Expression?
        // Send the whole DDS
        myDDS.print(out);
        out.flush();

      } else { // Otherwise, send the constrained DDS
        // Instantiate the CEEvaluator and parse the constraint expression
        CEEvaluator ce = new CEEvaluator(myDDS);
        ce.parseConstraint(rs);

        // Send the constrained DDS back to the client
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
        myDDS.printConstrained(pw);
        pw.flush();
      }

    } finally { // release lock if needed
      if (ds != null) ds.release();
    }
  }

  public void doGetDDX(ReqState rs) throws Exception {
    HttpServletResponse response = rs.getResponse();

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (null == ds) return;

      response.setContentType("text/plain");
      response.setHeader("XDODS-Server", getServerVersion());
      response.setHeader("Content-Description", "dods-ddx");

      OutputStream out = new BufferedOutputStream(response.getOutputStream());

      ServerDDS myDDS = ds.getDDS();
      myDDS.ingestDAS(ds.getDAS());

      if (rs.getConstraintExpression().equals("")) { // No Constraint Expression?
        // Send the whole DDS
        myDDS.printXML(out);
        out.flush();
      } else { // Otherwise, send the constrained DDS

        // Instantiate the CEEvaluator and parse the constraint expression
        CEEvaluator ce = new CEEvaluator(myDDS);
        ce.parseConstraint(rs);

        // Send the constrained DDS back to the client
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
        myDDS.printConstrainedXML(pw);
        pw.flush();
      }

    } finally { // release lock if needed
      if (ds != null) ds.release();
    }
  }

  public void doGetBLOB(ReqState rs) throws Exception {
    HttpServletResponse response = rs.getResponse();

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (null == ds) return;

      response.setContentType("application/octet-stream");
      response.setHeader("XDODS-Server", getServerVersion());
      response.setHeader("Content-Description", "dods-blob");

      ServletOutputStream sOut = response.getOutputStream();
      OutputStream bOut;
      DeflaterOutputStream dOut = null;
      if (rs.getAcceptsCompressed() && allowDeflate) {
        response.setHeader("Content-Encoding", "deflate");
        dOut = new DeflaterOutputStream(sOut);
        bOut = new BufferedOutputStream(dOut);
      } else {
        bOut = new BufferedOutputStream(sOut);
      }

      ServerDDS myDDS = ds.getDDS();
      CEEvaluator ce = new CEEvaluator(myDDS);
      ce.parseConstraint(rs);
      checkSize(myDDS, false);

      // Send the binary data back to the client
      DataOutputStream sink = new DataOutputStream(bOut);
      ce.send(myDDS.getEncodedName(), sink, ds);
      sink.flush();

      // Finish up sending the compressed stuff, but don't
      // close the stream (who knows what the Servlet may expect!)
      if (null != dOut) dOut.finish();
      bOut.flush();

    } finally { // release lock if needed
      if (ds != null) ds.release();
    }
  }

  private void doClose(ReqState rs) throws Exception {
    HttpServletResponse response = rs.getResponse();
    HttpServletRequest request = rs.getRequest();
    String reqPath = rs.getDataSet();
    HttpSession session = request.getSession();
    session.removeAttribute(reqPath); // work done in the listener

    response.setHeader("XDODS-Server", getServerVersion()); // needed by client

    /* if (path.endsWith(".close")) {
      closeSession(request, response);
      response.setContentLength(0);
      log.info("doGet(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, 0));
      return;

    }

    // so we need to worry about deleting sessions?
    session.invalidate();  */
  }

  public void doGetDAP2Data(ReqState rs) throws Exception {
    HttpServletResponse response = rs.getResponse();

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (null == ds) return;

      response.setContentType("application/octet-stream");
      response.setHeader("XDODS-Server", getServerVersion());
      response.setHeader("Content-Description", "dods-data");

      ServletOutputStream sOut = response.getOutputStream();
      OutputStream bOut;
      DeflaterOutputStream dOut = null;
      if (rs.getAcceptsCompressed() && allowDeflate) {
        response.setHeader("Content-Encoding", "deflate");
        dOut = new DeflaterOutputStream(sOut);
        bOut = new BufferedOutputStream(dOut);

      } else {
        bOut = new BufferedOutputStream(sOut);
      }

      ServerDDS myDDS = ds.getDDS();
      CEEvaluator ce = new CEEvaluator(myDDS);
      ce.parseConstraint(rs);
      checkSize(myDDS, false);

      // Send the constrained DDS back to the client
      PrintWriter pw = new PrintWriter(new OutputStreamWriter(bOut));
      myDDS.printConstrained(pw);

      // Send the Data delimiter back to the client
      pw.flush();
      bOut.write("\nData:\n".getBytes());
      bOut.flush();

      // Send the binary data back to the client
      DataOutputStream sink = new DataOutputStream(bOut);
      ce.send(myDDS.getEncodedName(), sink, ds);
      sink.flush();

      // Finish up sending the compressed stuff, but don't
      // close the stream (who knows what the Servlet may expect!)
      if (null != dOut) dOut.finish();
      bOut.flush();

    } finally { // release lock if needed
      if (ds != null) ds.release();
    }
  }

  public void doGetVER(ReqState rs) throws Exception {
    HttpServletResponse response = rs.getResponse();

    response.setContentType("text/plain");
    response.setHeader("XDODS-Server", getServerVersion());
    response.setHeader("Content-Description", "dods-version");

    PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream()));

    pw.println("Server Version: " + getServerVersion());
    pw.flush();
  }

  public void doGetHELP(ReqState rs) throws Exception {
    HttpServletResponse response = rs.getResponse();

    response.setContentType("text/html");
    response.setHeader("XDODS-Server", getServerVersion());
    response.setHeader("Content-Description", "dods-help");

    PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream()));
    printHelpPage(pw);
    pw.flush();
  }

  public void doGetDIR(ReqState rs) throws Exception {
    // rather dangerous here, since you can go into an infinite loop
    // so we're going to insist that there's  no suffix
    HttpServletResponse response = rs.getResponse();
    HttpServletRequest request = rs.getRequest();
    if ((rs.getRequestSuffix() == null) || (rs.getRequestSuffix().length() == 0)) {
      ServletUtil.forwardToCatalogServices(request, response);
      return;
    }

    sendErrorResponse(response, 0, "Unrecognized request");
  }

  public void doGetINFO(ReqState rs) throws Exception {
    HttpServletResponse response = rs.getResponse();
    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (null == ds) return;

      PrintStream pw = new PrintStream(response.getOutputStream());
      response.setHeader("XDODS-Server", getServerVersion());
      response.setContentType("text/html");
      response.setHeader("Content-Description", "dods-description");

      GetInfoHandler di = new GetInfoHandler();
      di.sendINFO(pw, ds, rs);

    } finally { // release lock if needed
      if (ds != null) ds.release();
    }
  }

  public void doGetHTML(ReqState rs) throws Exception {
    HttpServletResponse response = rs.getResponse();
    HttpServletRequest request = rs.getRequest();
    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (ds == null) return;

      response.setHeader("XDODS-Server", getServerVersion());
      response.setContentType("text/html");
      response.setHeader("Content-Description", "dods-form");

      // Utilize the getDDS() method to get	a parsed and populated DDS
      // for this server.
      ServerDDS myDDS = ds.getDDS();
      DAS das = ds.getDAS();
      GetHTMLInterfaceHandler2 di = new GetHTMLInterfaceHandler2();
      di.sendDataRequestForm(request, response, rs.getDataSet(), myDDS, das);

    } finally { // release lock if needed
      if (ds != null) ds.release();
    }
  }

  ///////////////////////////////////////////////////////////////////////////////////////////////
  // debugging
  private void makeDebugActions() {
    DebugHandler debugHandler = DebugHandler.get("ncdodsServer");
    DebugHandler.Action act;

    act =
        new DebugHandler.Action("help", "Show help page") {
          public void doAction(DebugHandler.Event e) {
            try {
              doGetHELP(getRequestState(e.req, e.res));
            } catch (Exception ioe) {
              log.error("ShowHelp", ioe);
            }
          }
        };
    debugHandler.addAction(act);

    act =
        new DebugHandler.Action("version", "Show server version") {
          public void doAction(DebugHandler.Event e) {
            e.pw.println("  version= " + getServerVersion());
          }
        };
    debugHandler.addAction(act);
  }

  public String getServerName() {
    return this.getClass().getName();
  }

  /*protected ReqState getRequestState(HttpServletRequest request, HttpServletResponse response) {

    ReqState rs = null;
    // The url and query strings will come to us in encoded form
    // (see HTTPmethod.newMethod())
    String baseurl = request.getRequestURL().toString();
    baseurl = EscapeStrings.escapeURL(baseurl);
    log.debug("doGet baseurl={}", baseurl);

    String query = request.getQueryString();
    query = EscapeStrings.unescapeURLQuery(query);
    log.debug("doGet query={}", query);

    try {
      rs = new ReqState(request, response, getServletConfig(), getServerName(), baseurl, query);
    } catch (BadURLException bue) {
      rs = null;
    }

    return rs;
  }*/

  /**
   * ************************************************************************ Prints the OPeNDAP
   * Server help page to the passed PrintWriter
   *
   * @param pw PrintWriter stream to which to dump the help page.
   */
  private void printHelpPage(PrintWriter pw) {

    pw.println("<h3>OPeNDAP Server Help</h3>");
    pw.println("To access most of the features of this OPeNDAP server, append");
    pw.println(
        "one of the following a eight suffixes to a URL: .das, .dds, .dods, .ddx, .blob, .info,");
    pw.println(".ver or .help. Using these suffixes, you can ask this server for:");
    pw.println("<dl>");
    pw.println("<dt> das  </dt> <dd> Dataset Attribute Structure (DAS)</dd>");
    pw.println("<dt> dds  </dt> <dd> Dataset Descriptor Structure (DDS)</dd>");
    pw.println("<dt> dods </dt> <dd> DataDDS object (A constrained DDS populated with data)</dd>");
    pw.println("<dt> ddx  </dt> <dd> XML version of the DDS/DAS</dd>");
    pw.println(
        "<dt> blob </dt> <dd> Serialized binary data content for requested data set, "
            + "with the constraint expression applied.</dd>");
    pw.println("<dt> info </dt> <dd> info object (attributes, types and other information)</dd>");
    pw.println("<dt> html </dt> <dd> html form for this dataset</dd>");
    pw.println("<dt> ver  </dt> <dd> return the version number of the server</dd>");
    pw.println("<dt> help </dt> <dd> help information (this text)</dd>");
    pw.println("</dl>");
    pw.println("For example, to request the DAS object from the FNOC1 dataset at URI/GSO (a");
    pw.println("test dataset) you would appand `.das' to the URL:");
    pw.println("http://opendap.gso.url.edu/cgi-bin/nph-nc/data/fnoc1.nc.das.");

    pw.println("<p><b>Note</b>: Many OPeNDAP clients supply these extensions for you so you don't");
    pw.println("need to append them (for example when using interfaces supplied by us or");
    pw.println("software re-linked with a OPeNDAP client-library). Generally, you only need to");
    pw.println("add these if you are typing a URL directly into a WWW browser.");
    pw.println("<p><b>Note</b>: If you would like version information for this server but");
    pw.println("don't know a specific data file or data set name, use `/version' for the");
    pw.println("filename. For example: http://opendap.gso.url.edu/cgi-bin/nph-nc/version will");
    pw.println("return the version number for the netCDF server used in the first example. ");

    pw.println("<p><b>Suggestion</b>: If you're typing this URL into a WWW browser and");
    pw.println("would like information about the dataset, use the `.info' extension.");

    pw.println("<p>If you'd like to see a data values, use the `.html' extension and submit a");
    pw.println("query using the customized form.");
  }
  // **************************************************************************

  /**
   * ************************************************************************ Prints the Bad URL
   * Page page to the passed PrintWriter
   *
   * @param pw PrintWriter stream to which to dump the bad URL page.
   */
  private void printBadURLPage(PrintWriter pw) {

    String serverContactName = ThreddsConfig.get("serverInformation.contact.name", "UNKNOWN");
    String serverContactEmail = ThreddsConfig.get("serverInformation.contact.email", "UNKNOWN");
    pw.println("<h3>Error in URL</h3>");
    pw.println("The URL extension did not match any that are known by this");
    pw.println("server. Below is a list of the five extensions that are be recognized by");
    pw.println("all OPeNDAP servers. If you think that the server is broken (that the URL you");
    pw.println("submitted should have worked), then please contact the");
    pw.println("administrator of this server [" + serverContactName + "] at: ");
    pw.println("<a href='mailto:" + serverContactEmail + "'>" + serverContactEmail + "</a><p>");
  }

  ///////////////////////////////////////////////////////
  // utils
  /**
   * @param request
   * @param response
   * @return the request state
   */
  protected ReqState getRequestState(HttpServletRequest request, HttpServletResponse response) {
    ReqState rs = null;

    // Assume url was encoded
    String baseurl = request.getRequestURL().toString();
    baseurl = EscapeStrings.unescapeURL(baseurl);

    // Assume query  was encoded
    String query = request.getQueryString();
    query = EscapeStrings.unescapeURLQuery(query);

    log.info(String.format("OpendapServlet: nominal url: %s?%s", baseurl, query));

    try {
      rs = new ReqState(request, response, this, getServerName(), baseurl, query);
    } catch (Exception bue) {
      rs = null;
    }

    return rs;
  }

  private void checkSize(ServerDDS dds, boolean isAscii) throws Exception {
    long size = computeSize(dds, isAscii);
    // System.err.printf("total (constrained) size=%s\n", size);
    log.debug("total (constrained) size={}", size);
    double dsize = size / (1000 * 1000);
    double maxSize = isAscii ? ascLimit : binLimit; // Mbytes
    if (dsize > maxSize) {
      log.info("Reject request size = {} Mbytes", dsize);
      throw new UnsupportedOperationException(
          "Request too big=" + dsize + " Mbytes, max=" + maxSize);
    }
  }

  // Recursively compute size of the dds to be returned
  private long computeSize(DConstructor ctor, boolean isAscii) throws Exception {
    long projectsize = 0; // accumulate size of projected variables
    long othersize = 0; // accumulate size of non-projected variables
    long fieldsize = 0;
    int projectedcount = 0;
    int fieldcount = 0;
    Enumeration vars = ctor.getVariables();
    while (vars.hasMoreElements()) {
      fieldcount++;
      BaseType field = (BaseType) vars.nextElement();
      fieldsize = computeFieldSize(field, isAscii);
      // accumulate the field sizes
      if (field.isProject()) {
        projectsize += fieldsize;
        projectedcount++;
      } else {
        othersize += fieldsize;
      }
    }
    // Cases to consider:
    // 1. If all of the fields of this ctor are projected,
    //    then return projectsize
    // 2. If none of the fields of this ctor are projected,
    //    then return othersize
    // 3. otherwise, at least one field, but not all, is projected,
    //    => return projectsize;
    if (projectedcount == fieldcount) return projectsize;
    else if (projectedcount == 0) return othersize;
    else {
      assert (projectedcount > 0 && projectedcount < fieldcount);
      return projectsize;
    }
  }

  long computeFieldSize(BaseType bt, boolean isAscii) throws Exception {
    long fieldsize = 0;
    // Figure out what this field is (e.g. primitive or not)
    // Somewhat convoluted.
    if (bt instanceof DConstructor) {
      // simple struct, seq, or grid => recurse
      fieldsize = computeSize((DConstructor) bt, isAscii);
    } else if (bt instanceof DArray) {
      SDArray da = (SDArray) bt;
      // Separate structure arrays from primitive arrays
      if (da.getContainerVar() instanceof DPrimitive) {
        fieldsize = computeArraySize(da);
      } else if (da.getContainerVar() instanceof DStructure) {
        fieldsize = computeSize((DStructure) da.getContainerVar(), isAscii); // recurse
      } else { // Some kind of problem
        throw new NoSuchTypeException("Computesize: unexpected type for " + bt.getLongName());
      }
    } else if (bt instanceof DPrimitive) {
      DPrimitive dp = (DPrimitive) bt;
      if (dp instanceof DString) {
        String v = ((DString) dp).getValue();
        fieldsize = (v == null ? 0 : v.length());
      } else {
        DataType dtype = DODSNetcdfFile.convertToNCType(bt);
        fieldsize = dtype.getSize();
      }
    } else { // Some kind of problem
      throw new NoSuchTypeException("Computesize: unknown type for " + bt.getLongName());
    }
    return fieldsize;
  }

  long computeArraySize(SDArray da) throws Exception {
    assert (da.getContainerVar() instanceof DPrimitive);
    BaseType base = da.getPrimitiveVector().getTemplate();
    DataType dtype = DODSNetcdfFile.convertToNCType(base);
    int elemSize = dtype.getSize();
    int n = da.numDimensions();
    List<Range> ranges = new ArrayList<Range>(n);
    long size = 0;
    for (int i = 0; i < n; i++) {
      ranges.add(new Range(da.getStart(i), da.getStop(i), da.getStride(i)));
      Section s = new Section(ranges);
      size += s.computeSize() * elemSize;
    }
    return size;
  }

  /*
   * *********************** dataset caching ***********************************************
   */

  // any time the server needs access to the dataset, it gets a "GuardedDataset" which allows us to
  // add caching
  // optionally, a session may be established, which allows us to reserve the dataset for that
  // session.
  protected GuardedDataset getDataset(ReqState preq) throws Exception {
    HttpServletRequest req = preq.getRequest();
    String reqPath = preq.getDataSet();

    // see if the client wants sessions
    boolean acceptSession = false;
    String s = req.getHeader("X-Accept-Session");
    if (s != null && s.equalsIgnoreCase("true") && allowSessions) acceptSession = true;

    HttpSession session = null;
    if (acceptSession) {
      // see if theres already a session established, create one if not
      session = req.getSession();
      if (!session.isNew()) {
        GuardedDataset gdataset = (GuardedDataset) session.getAttribute(reqPath);
        if (null != gdataset) {
          if (debugSession)
            System.out.printf(" found gdataset %s in session %s %n", reqPath, session.getId());
          if (log.isDebugEnabled())
            log.debug(" found gdataset " + gdataset + " in session " + session.getId());
          return gdataset;
        }
      }
    }

    NetcdfFile ncd = DatasetHandler.getNetcdfFile(req, preq.getResponse(), reqPath);
    if (null == ncd) return null;

    GuardedDataset gdataset = new GuardedDatasetCacheAndClone(reqPath, ncd, acceptSession);
    // GuardedDataset gdataset = new GuardedDatasetImpl(reqPath, ncd, acceptSession);

    if (acceptSession) {
      String cookiePath = req.getRequestURI();
      String suffix = "." + preq.getRequestSuffix();
      if (cookiePath.endsWith(suffix)) // snip off the suffix
      cookiePath = cookiePath.substring(0, cookiePath.length() - suffix.length());
      session.setAttribute(reqPath, gdataset);
      session.setAttribute(CookieFilter.SESSION_PATH, cookiePath);
      // session.setAttribute("dataset", ncd.getLocation());  // for UsageValve
      // session.setMaxInactiveInterval(30); // 30 second timeout !!
      if (debugSession)
        System.out.printf(
            " added gdataset %s in session %s cookiePath %s %n",
            reqPath, session.getId(), cookiePath);
      if (log.isDebugEnabled())
        log.debug(" added gdataset " + gdataset + " in session " + session.getId());
    } /* else {
        session = req.getSession();
        session.setAttribute("dataset", ncd.getLocation()); // for UsageValve
      } */

    return gdataset;
  }

  //////////////////////////////////////////////////////////////////////////////

  public void parseExceptionHandler(ParseException pe, HttpServletResponse response) {
    try {
      BufferedOutputStream eOut = new BufferedOutputStream(response.getOutputStream());
      response.setHeader("Content-Description", "dods-error");
      response.setContentType("text/plain");

      String msg = pe.getMessage().replace('\"', '\'');

      DAP2Exception de2 = new DAP2Exception(opendap.dap.DAP2Exception.CANNOT_READ_FILE, msg);
      de2.print(eOut);
    } catch (Exception e) {
      System.err.println("parseExceptionHandler: " + e);
    }
  }

  public void dap2ExceptionHandler(DAP2Exception de, ReqState rs) {
    rs.getResponse().setHeader("Content-Description", "dods-error");
    rs.getResponse().setContentType("text/plain");
    try {
      de.print(rs.getResponse().getOutputStream());
    } catch (Exception e) {
      System.err.println("dap2ExceptionHandler: " + e);
    }
  }

  private void sendErrorResponse(HttpServletResponse response, int errorCode, String errorMessage) {
    try {
      log.info(UsageLog.closingMessageForRequestContext(errorCode, -1));
      response.setStatus(errorCode);
      response.setHeader("Content-Description", "dods-error");
      response.setContentType("text/plain");

      PrintWriter pw = new PrintWriter(response.getOutputStream());
      pw.println("Error {");
      pw.println("    code = " + errorCode + ";");
      pw.println("    message = \"" + errorMessage + "\";");

      pw.println("};");
      pw.flush();
    } catch (Exception e) {
      System.err.println("sendErrorResponse: " + e);
    }
  }
}
/** Created by Martin on 26.6.2015. */
public class ProjectsClient {
  private static final Logger logger = org.slf4j.LoggerFactory.getLogger(ProjectsClient.class);

  private GerritApi api;
  private IProjectsRepository repo;
  private ProjectsCache cache;
  private Boolean downloadParents;
  private WaitCaller caller;

  public ProjectsClient(
      GerritApi api, WaitCaller caller, IProjectsRepository repository, Boolean downloadParents) {
    this.api = api;
    this.repo = repository;
    this.caller = caller;

    this.cache = new ProjectsCache();
    this.downloadParents = downloadParents;
  }

  public void prepare() {
    cache.restore(repo.getAllProjects());
  }

  public ProjectDto getProject(String id) {
    if (cache.isCached(id)) {
      return cache.tryGetCached(id);
    }

    ProjectInfo info = null;

    try {
      info = caller.waitOrCall(() -> api.projects().name(id).get());
    } catch (Exception e) {

      logger.error(Logging.prepare("getProject", id), e);
    }

    if (info != null) {
      ProjectDto project = new ProjectDto(info.id, info.name);

      if (info.parent != null && downloadParents) {
        ProjectDto parent = getProject(info.parent);
        project.parentId = Optional.of(parent.id);
      }

      project = repo.add(project);

      // getProjectBranches(project);
      // getApprovals(project);

      cache.cache(project);

      return project;
    }

    return null;
  }

  //
  //    private void getApprovals(ProjectDto project) {
  //        int start = 0;
  //        int limit = 1;
  //
  //        boolean getCommands = true;
  //
  //        List<ApprovalTypeDto> approvals = new ArrayList<>();
  //        while (approvals.size() <= 0) {
  //            List<ChangeInfo> changes = null;
  //
  //            try {
  //                Changes.QueryRequest request = api.changes().query()
  //                        .withStart(start)
  //                        .withLimit(limit)
  //                        .withOptions(ListChangesOption.DETAILED_LABELS);
  //                if (getCommands) {
  //                    request.withOption(ListChangesOption.DOWNLOAD_COMMANDS);
  //                }
  //
  //                changes = caller.waitOrCall(() -> request.get());
  //            } catch (Exception e) {
  //                e.printStackTrace();
  //                getCommands = false;
  //            }
  //
  //            if (changes != null && changes.size() > 0) {
  //                ChangeInfo change = changes.get(0);
  //
  //                if (change.labels != null && change.labels.size() > 0) {
  //                    approvals.addAll(createApprovalTypes(project, change.labels));
  //                }
  //
  //            }
  //
  //            start += limit;
  //        }
  //
  //        approvals.forEach(repo::addApprovalType);
  //
  //        approvals.forEach(a -> project.approvals.put(a.name, a));
  //    }

  private List<ApprovalTypeDto> createApprovalTypes(
      ProjectDto project, Map<String, LabelInfo> labels) {
    return labels
        .entrySet()
        .stream()
        .filter(e -> project.approvals.containsKey(e.getKey()) == false)
        .map(e -> createApprovalType(project, e.getKey(), e.getValue()))
        .collect(Collectors.toList());
  }

  private ApprovalTypeDto createApprovalType(ProjectDto project, String type, LabelInfo label) {
    short defaultValue = 0;
    if (label != null && label.defaultValue != null) {
      defaultValue = label.defaultValue;
    }

    return new ApprovalTypeDto(
        project,
        type,
        defaultValue,
        label
            .values
            .entrySet()
            .stream()
            .map(ev -> new ApprovalValueDto(ev.getKey(), ev.getValue()))
            .toArray(ApprovalValueDto[]::new));
  }

  public void addApprovals(ProjectDto project, Map<String, LabelInfo> labels) {
    if (labels.size() > 0) {
      labels.entrySet().forEach(e -> addApproval(project, e.getKey(), e.getValue()));
    }
  }

  public void addApproval(ProjectDto project, String key, LabelInfo label) {
    if (project.approvals.containsKey(key) == false) {
      ApprovalTypeDto approval = createApprovalType(project, key, label);
      repo.addApprovalType(approval);
      project.approvals.put(approval.name, approval);
    }
  }

  private void getProjectBranches(ProjectDto project) {
    List<BranchInfo> branches = null;
    try {
      ProjectApi.ListBranchesRequest request = api.projects().name(project.projectId).branches();
      branches = caller.waitOrCall(() -> request.get());
    } catch (Exception e) {
      logger.error(Logging.prepare("getProjectBranches", project.projectId), e);
    }

    if (branches != null) {
      List<BranchDto> projectBranches =
          branches
              .stream()
              .filter(b -> project.hasBranch(b.ref) == false)
              .map(b -> repo.addBranch(project, b.ref, b.revision))
              .filter(b -> b != null)
              .collect(Collectors.toList());

      projectBranches.forEach(project.branches::add);
    }
  }

  public BranchDto getBranch(ProjectDto project, String name) {
    if (project.hasBranch(name) == false) {
      repo.loadProjectBranches(project);
    }

    if (project.hasBranch(name) == false) {
      getProjectBranches(project);
    }
    return project.getBranch(name);
  }
}
/**
 * @author : rincevent
 * @since JAHIA 6.5 Created : 14 sept. 2010
 */
public class JBPMMailProducer extends MailProducerImpl {
  private static final long serialVersionUID = -5084848266010688683L;
  private static transient Logger logger =
      org.slf4j.LoggerFactory.getLogger(JBPMMailProducer.class);
  ScriptEngine scriptEngine;
  private Bindings bindings;

  private String templateKey;

  public String getTemplateKey() {
    return templateKey;
  }

  public void setTemplateKey(String templateKey) {
    this.templateKey = templateKey;
  }

  public Collection<Message> produce(final Execution execution) {
    final Map<String, Object> vars = ((ExecutionImpl) execution).getVariables();
    Locale locale = (Locale) vars.get("locale");

    if (templateKey != null) {
      MailTemplate template = null;
      MailTemplateRegistry templateRegistry =
          ((ProcessEngine) SpringContextSingleton.getBean("processEngine"))
              .get(MailTemplateRegistry.class);
      if (locale != null) {
        template = (templateRegistry.getTemplate(templateKey + "." + locale.toString()));
        if (template == null) {
          template = (templateRegistry.getTemplate(templateKey + "." + locale.getLanguage()));
        }
      }
      if (template == null) {
        template = templateRegistry.getTemplate(templateKey);
      }
      setTemplate(template);
    }

    if (ServicesRegistry.getInstance().getMailService().isEnabled() && getTemplate() != null) {
      try {
        return JCRTemplate.getInstance()
            .doExecuteWithSystemSession(
                null,
                "default",
                locale,
                new JCRCallback<Collection<Message>>() {
                  public Collection<Message> doInJCR(JCRSessionWrapper session)
                      throws RepositoryException {
                    try {
                      scriptEngine =
                          ScriptEngineUtils.getInstance()
                              .getEngineByName(getTemplate().getLanguage());
                      bindings = null;
                      Message email = instantiateEmail();
                      fillFrom(email, execution, session);
                      fillRecipients(email, execution, session);
                      fillSubject(email, execution, session);
                      fillContent(email, execution, session);
                      Address[] addresses = email.getRecipients(Message.RecipientType.TO);
                      if (addresses != null && addresses.length > 0) {
                        return Collections.singleton(email);
                      } else {
                        return Collections.emptyList();
                      }
                    } catch (MessagingException e) {
                      logger.error(e.getMessage(), e);
                    } catch (ScriptException e) {
                      logger.error(e.getMessage(), e);
                    }
                    return Collections.emptyList();
                  }
                });
      } catch (RepositoryException e) {
        logger.error(e.getMessage(), e);
      }
    }
    return Collections.emptyList();
  }

  /**
   * Fills the <code>from</code> attribute of the given email. The sender addresses are an optional
   * element in the mail template. If absent, each mail server supplies the current user's email
   * address.
   *
   * @see {@link InternetAddress#getLocalAddress(Session)}
   */
  protected void fillFrom(Message email, Execution execution, JCRSessionWrapper session)
      throws MessagingException {
    try {
      AddressTemplate fromTemplate = getTemplate().getFrom();
      // "from" attribute is optional
      if (fromTemplate == null) return;

      // resolve and parse addresses
      String addresses = fromTemplate.getAddresses();
      if (addresses != null) {
        addresses = evaluateExpression(execution, addresses, session);
        // non-strict parsing applies to a list of mail addresses entered by a human
        email.addFrom(InternetAddress.parse(addresses, false));
      }

      EnvironmentImpl environment = EnvironmentImpl.getCurrent();
      IdentitySession identitySession = environment.get(IdentitySession.class);
      AddressResolver addressResolver = environment.get(AddressResolver.class);

      // resolve and tokenize users
      String userList = fromTemplate.getUsers();
      if (userList != null) {
        String[] userIds = tokenizeActors(userList, execution, session);
        List<User> users = identitySession.findUsersById(userIds);
        email.addFrom(resolveAddresses(users, addressResolver));
      }

      // resolve and tokenize groups
      String groupList = fromTemplate.getGroups();
      if (groupList != null) {
        for (String groupId : tokenizeActors(groupList, execution, session)) {
          Group group = identitySession.findGroupById(groupId);
          email.addFrom(addressResolver.resolveAddresses(group));
        }
      }
    } catch (ScriptException e) {
      logger.error(e.getMessage(), e);
    } catch (RepositoryException e) {
      logger.error(e.getMessage(), e);
    }
  }

  protected void fillRecipients(Message email, Execution execution, JCRSessionWrapper session)
      throws MessagingException {
    try {
      // to
      AddressTemplate to = getTemplate().getTo();
      if (to != null) {
        fillRecipients(to, email, Message.RecipientType.TO, execution, session);
      }

      // cc
      AddressTemplate cc = getTemplate().getCc();
      if (cc != null) {
        fillRecipients(cc, email, Message.RecipientType.CC, execution, session);
      }

      // bcc
      AddressTemplate bcc = getTemplate().getBcc();
      if (bcc != null) {
        fillRecipients(bcc, email, Message.RecipientType.BCC, execution, session);
      }
    } catch (ScriptException e) {
      logger.error(e.getMessage(), e);
    } catch (RepositoryException e) {
      logger.error(e.getMessage(), e);
    }
  }

  private void fillRecipients(
      AddressTemplate addressTemplate,
      Message email,
      Message.RecipientType recipientType,
      Execution execution,
      JCRSessionWrapper session)
      throws MessagingException, RepositoryException, ScriptException {
    // resolve and parse addresses
    String addresses = addressTemplate.getAddresses();
    if (addresses != null) {
      addresses = evaluateExpression(execution, addresses, session);
      // non-strict parsing applies to a list of mail addresses entered by a human
      email.addRecipients(recipientType, InternetAddress.parse(addresses, false));
    }

    EnvironmentImpl environment = EnvironmentImpl.getCurrent();
    IdentitySession identitySession = environment.get(IdentitySession.class);
    AddressResolver addressResolver = environment.get(AddressResolver.class);

    // resolve and tokenize users
    String userList = addressTemplate.getUsers();
    if (userList != null) {
      String[] userIds = tokenizeActors(userList, execution, session);
      List<User> users = identitySession.findUsersById(userIds);
      email.addRecipients(recipientType, resolveAddresses(users, addressResolver));
    }

    // resolve and tokenize groups
    String groupList = addressTemplate.getGroups();
    if (groupList != null) {
      for (String groupId : tokenizeActors(groupList, execution, session)) {
        Group group = identitySession.findGroupById(groupId);
        email.addRecipients(recipientType, addressResolver.resolveAddresses(group));
      }
    }
  }

  private String[] tokenizeActors(String recipients, Execution execution, JCRSessionWrapper session)
      throws RepositoryException, ScriptException {
    String[] actors = evaluateExpression(execution, recipients, session).split("[,;\\s]+");
    if (actors.length == 0) throw new JbpmException("recipient list is empty: " + recipients);
    return actors;
  }

  /** construct recipient addresses from user entities */
  private Address[] resolveAddresses(List<User> users, AddressResolver addressResolver) {
    int userCount = users.size();
    List<Address> addresses = new ArrayList<Address>();
    for (int i = 0; i < userCount; i++) {
      if (!StringUtils.isEmpty(users.get(i).getBusinessEmail())) {
        addresses.add(addressResolver.resolveAddress(users.get(i)));
      }
    }
    return addresses.toArray(new Address[addresses.size()]);
  }

  protected void fillSubject(Message email, Execution execution, JCRSessionWrapper session)
      throws MessagingException {
    String subject = getTemplate().getSubject();
    if (subject != null) {
      try {
        String evaluatedSubject =
            evaluateExpression(execution, subject, session).replaceAll("[\r\n]", "");
        email.setSubject(WordUtils.abbreviate(evaluatedSubject, 60, 74, "..."));
      } catch (RepositoryException e) {
        logger.error(e.getMessage(), e);
      } catch (ScriptException e) {
        logger.error(e.getMessage(), e);
      }
    }
  }

  protected void fillContent(Message email, Execution execution, JCRSessionWrapper session)
      throws MessagingException {
    String text = getTemplate().getText();
    String html = getTemplate().getHtml();
    List<AttachmentTemplate> attachmentTemplates = getTemplate().getAttachmentTemplates();

    try {
      if (html != null || !attachmentTemplates.isEmpty()) {
        // multipart
        MimeMultipart multipart = new MimeMultipart("related");

        BodyPart p = new MimeBodyPart();
        Multipart alternatives = new MimeMultipart("alternative");
        p.setContent(alternatives, "multipart/alternative");
        multipart.addBodyPart(p);

        // html
        if (html != null) {
          BodyPart htmlPart = new MimeBodyPart();
          html = evaluateExpression(execution, html, session);
          htmlPart.setContent(html, "text/html; charset=UTF-8");
          alternatives.addBodyPart(htmlPart);
        }

        // text
        if (text != null) {
          BodyPart textPart = new MimeBodyPart();
          text = evaluateExpression(execution, text, session);
          textPart.setContent(text, "text/plain; charset=UTF-8");
          alternatives.addBodyPart(textPart);
        }

        // attachments
        if (!attachmentTemplates.isEmpty()) {
          addAttachments(execution, multipart);
        }

        email.setContent(multipart);
      } else if (text != null) {
        // unipart
        text = evaluateExpression(execution, text, session);
        email.setText(text);
      }
    } catch (RepositoryException e) {
      logger.error(e.getMessage(), e);
    } catch (ScriptException e) {
      logger.error(e.getMessage(), e);
    }
  }

  private String evaluateExpression(
      Execution execution, String scriptToExecute, JCRSessionWrapper session)
      throws RepositoryException, ScriptException {
    ScriptContext scriptContext = scriptEngine.getContext();
    if (bindings == null) {
      bindings = getBindings(execution, session);
    }
    scriptContext.setWriter(new StringWriter());
    scriptContext.setErrorWriter(new StringWriter());
    scriptEngine.eval(scriptToExecute, bindings);
    String error = scriptContext.getErrorWriter().toString();
    if (error.length() > 0) {
      logger.error("Scripting error : " + error);
    }
    return scriptContext.getWriter().toString().trim();
  }

  private Bindings getBindings(Execution execution, JCRSessionWrapper session)
      throws RepositoryException {
    EnvironmentImpl environment = EnvironmentImpl.getCurrent();
    final Map<String, Object> vars = ((ExecutionImpl) execution).getVariables();
    Locale locale = (Locale) vars.get("locale");
    final Bindings bindings = new MyBindings(environment);
    ResourceBundle resourceBundle =
        JahiaResourceBundle.lookupBundle(
            "org.jahia.services.workflow."
                + ((ExecutionImpl) execution).getProcessDefinition().getKey(),
            locale);
    bindings.put("bundle", resourceBundle);
    JahiaUser jahiaUser =
        ServicesRegistry.getInstance()
            .getJahiaUserManagerService()
            .lookupUserByKey((String) vars.get("user"));
    bindings.put("user", jahiaUser);
    bindings.put("date", new DateTool());
    bindings.put("submissionDate", Calendar.getInstance());
    bindings.put("locale", locale);
    bindings.put("workspace", vars.get("workspace"));

    List<JCRNodeWrapper> nodes = new LinkedList<JCRNodeWrapper>();
    @SuppressWarnings("unchecked")
    List<String> stringList = (List<String>) vars.get("nodeIds");
    for (String s : stringList) {
      JCRNodeWrapper nodeByUUID = session.getNodeByUUID(s);
      if (!nodeByUUID.isNodeType("jnt:translation")) {
        nodes.add(nodeByUUID);
      }
    }
    bindings.put("nodes", nodes);
    return bindings;
  }

  public class MyBindings extends SimpleBindings {
    private final Environment environment;

    public MyBindings(Environment environment) {
      super();
      this.environment = environment;
    }

    @Override
    public boolean containsKey(Object key) {
      return super.containsKey(key) || environment.get((String) key) != null;
    }

    @Override
    public Object get(Object key) {
      return super.containsKey(key) ? super.get(key) : environment.get((String) key);
    }
  }
}
Exemple #10
0
public class RC {
  static boolean showlog = false; /* do not do any logging */
  public static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RC.class);

  //////////////////////////////////////////////////
  // Predefined flags
  // To add a new flag:
  // 1. choose a name for the flag
  // 2. Define the protected static field with default value
  // 3. Define a get function
  // 4. Add an arm to the set function
  // 5. Add any usefull utilities like booleanize()

  public static final String USEGROUPSKEY = "ucar.nc2.cdm.usegroups";
  public static final String VERIFYSERVERKEY = "ucar.nc2.net.verifyserver";
  public static final String ALLOWSELFSIGNEDKEY = "ucar.nc2.net.allowselfsigned";

  protected static boolean useGroups = true;
  protected static boolean verifyServer = false;
  protected static boolean allowSelfSigned = true;

  public static boolean getUseGroups() {
    if (!initialized) RC.initialize();
    return useGroups;
  }

  public static boolean getVerifyServer() {
    if (!initialized) RC.initialize();
    return verifyServer;
  }

  public static boolean getAllowSelfSigned() {
    if (!initialized) RC.initialize();
    return allowSelfSigned;
  }

  public static void set(String key, String value) {
    // TODO: think about the rc properties naming hierarchy
    assert (key != null);
    if (USEGROUPSKEY.equals(key)) {
      useGroups = booleanize(value);
    } else if (VERIFYSERVERKEY.equals(key)) {
      verifyServer = booleanize(value);
    } else if (ALLOWSELFSIGNEDKEY.equals(key)) {
      allowSelfSigned = booleanize(value);
    }
  }

  static boolean booleanize(String value) {
    // canonical boolean values
    if (value == null || "0".equals(value) || "false".equalsIgnoreCase(value)) return false;
    if (value.length() == 0 || "1".equals(value) || "true".equalsIgnoreCase(value)) return true;
    return value != null; // any non-null value?
  }

  //////////////////////////////////////////////////

  static final String DFALTRCFILE = ".threddsrc";

  static final char LTAG = '[';
  static final char RTAG = ']';

  static final String[] rcfilelist = new String[] {".dodsrc", ".tdsrc"};

  static int urlCompare(URL u1, URL u2) {
    int relation;
    if (u1 == null && u2 == null) return 0;
    if (u1 == null) return -1;
    if (u2 == null) return +1;
    // 1. host test
    String host1 = (new StringBuilder(u1.getHost())).reverse().toString();
    String host2 = (new StringBuilder(u2.getHost())).reverse().toString();
    // Use lexical order on the reversed host names
    relation = host1.compareTo(host2);
    if (relation != 0) return relation;
    // 2. path test
    relation = (u1.getPath().compareTo(u2.getPath()));
    if (relation != 0) return relation;
    // 3. port number
    relation = (u1.getPort() - u2.getPort());
    if (relation != 0) return relation;
    // 4. note: all other fields are ignored
    return 0;
  }

  // Match has different semantics than urlCompare
  static boolean urlMatch(URL pattern, URL url) {
    int relation;

    if (pattern == null) return (url == null);

    if (!(url.getHost().endsWith(pattern.getHost())))
      return false; // e.g. pattern=x.y.org url=y.org

    if (!(url.getPath().startsWith(pattern.getPath())))
      return false; // e.g. pattern=y.org/a/b url=y.org/a

    if (pattern.getPort() > 0 && pattern.getPort() != url.getPort()) return false;

    // note: all other fields are ignored

    return true;
  }

  public static class Triple implements Comparable {
    public String key; // also sort key
    public String value;
    public URL url;

    public Triple(String key, String value, String url) {
      URL u = null;
      if (url != null && url.length() > 0)
        try {
          u = new URL(url);
        } catch (MalformedURLException e) {
          u = null;
        }
      set(key, value, u);
    }

    public Triple(String key, String value, URL url) {
      set(key, value, url);
    }

    void set(String key, String value, URL url) {
      this.key = key.trim().toLowerCase();
      this.url = url;
      this.value = value;
      if (this.value == null) this.value = "";
    }

    public boolean equals(Object o) {
      if (o == null || !(o instanceof Triple)) return false;
      return (compareTo((Triple) o) == 0);
    }

    public int compareTo(Object o) {
      if (o == null) throw new NullPointerException();
      Triple t = (Triple) o;
      int relation = key.compareTo(t.key);
      if (relation != 0) return relation;
      relation = urlCompare(this.url, t.url);
      return relation;
    }

    // toString produces an rc line
    public String toString() {
      StringBuilder line = new StringBuilder();
      if (url != null) {
        line.append("[");
        line.append(url.toString());
        line.append("]");
      }
      line.append(key);
      line.append("=");
      line.append(value);
      return line.toString();
    }
  }

  // Define a singlton RC instance for general global use
  static RC dfaltRC = null;

  private static boolean initialized = false;

  static {
    RC.initialize();
  }

  public static synchronized void initialize() {
    if (!initialized) {
      initialized = true;
      RC.loadDefaults();
      RC.setWellKnown();
      RC.loadFromJava();
    }
  }

  /**
   * Allow users to add to the default rc
   *
   * @param key
   * @param value
   * @param url null => not url specific
   */
  public static synchronized void add(String key, String value, String url) {
    if (key == null) return;
    if (!initialized) RC.initialize();
    Triple t = new Triple(key, value, url);
    dfaltRC.insert(t);
    // recompute well-knowns
    setWellKnown();
  }

  /**
   * Allow users to search the default rc
   *
   * @param key
   * @param url null => not url specific
   * @return value corresponding to key+url, or null if does not exist
   */
  public static synchronized String find(String key, String url) {
    if (key == null) return null;
    if (!initialized) RC.initialize();
    Triple t = dfaltRC.lookup(key, url);
    return (t == null ? null : t.value);
  }

  /** Record some well known parameters */
  static void setWellKnown() {
    if (dfaltRC.triplestore.size() == 0) return;
    // Walk the set of triples looking for those that have no url
    for (String key : dfaltRC.keySet()) {
      Triple triple = dfaltRC.lookup(key);
      if (triple.url == null) {
        RC.set(key, triple.value); // let set sort it out
      }
    }
  }

  static void loadDefaults() {
    RC rc0 = new RC();
    String[] locations =
        new String[] {
          System.getProperty("user.home"), System.getProperty("user.dir"),
        };

    boolean found1 = false;
    for (String loc : locations) {
      if (loc == null) continue;
      String dir = loc.replace('\\', '/');
      if (dir.endsWith("/")) dir = dir.substring(0, dir.length() - 1);
      for (String rcpath : rcfilelist) {
        String filepath = loc + "/" + rcpath;
        if (rc0.load(filepath)) found1 = true;
      }
    }
    if (!found1) if (showlog) log.debug("No .rc file found");
    dfaltRC = rc0;
  }

  static void loadFromJava() {
    String[] flags = new String[] {USEGROUPSKEY, VERIFYSERVERKEY, ALLOWSELFSIGNEDKEY};
    for (String flag : flags) {
      String value = System.getProperty(flag);
      if (value != null) {
        set(flag, value);
      }
    }
  }

  static RC getDefault() {
    return dfaltRC;
  }

  //////////////////////////////////////////////////
  // Instance Data

  Map<String, List<Triple>> triplestore;

  //////////////////////////////////////////////////
  // constructors

  public RC() {
    triplestore = new HashMap<String, List<Triple>>();
  }

  //////////////////////////////////////////////////
  // Loaders

  // Load this triple store from an rc file
  // overwrite existing entries

  public boolean load(String abspath) {
    abspath = abspath.replace('\\', '/');
    File rcFile = new File(abspath);
    if (!rcFile.exists() || !rcFile.canRead()) {
      return false;
    }
    if (showlog) log.debug("Loading rc file: " + abspath);
    try (BufferedReader rdr =
        new BufferedReader(new InputStreamReader(new FileInputStream(rcFile), CDM.UTF8))) {
      for (int lineno = 1; ; lineno++) {
        URL url = null;
        String line = rdr.readLine();
        if (line == null) break;
        // trim leading blanks
        line = line.trim();
        if (line.length() == 0) continue; // empty line
        if (line.charAt(0) == '#') continue; // check for comment
        // parse the line
        if (line.charAt(0) == LTAG) {
          int rindex = line.indexOf(RTAG);
          if (rindex < 0) return false;
          if (showlog) log.error("Malformed [url] at " + abspath + "." + lineno);
          String surl = line.substring(1, rindex);
          try {
            url = new URL(surl);
          } catch (MalformedURLException mue) {
            if (showlog) log.error("Malformed [url] at " + abspath + "." + lineno);
          }
          line = line.substring(rindex + 1);
          // trim again
          line = line.trim();
        }
        // Get the key,value part
        String[] pieces = line.split("\\s*=\\s*");
        assert (pieces.length == 1 || pieces.length == 2);
        // Create the triple
        String value = "1";
        if (pieces.length == 2) value = pieces[1].trim();
        Triple triple = new Triple(pieces[0].trim(), value, url);
        List<Triple> list = triplestore.get(triple.key);
        if (list == null) list = new ArrayList<Triple>();
        Triple prev = addtriple(list, triple);
        triplestore.put(triple.key, list);
      }

    } catch (FileNotFoundException fe) {
      if (showlog) log.debug("Loading rc file: " + abspath);
      return false;

    } catch (IOException ioe) {
      if (showlog) log.error("File " + abspath + ": IO exception: " + ioe.getMessage());
      return false;
    }
    return true;
  }

  public Set<String> keySet() {
    return triplestore.keySet();
  }

  public List<Triple> getTriples(String key) {
    List<Triple> list = triplestore.get(key);
    if (list == null) list = new ArrayList<Triple>();
    return list;
  }

  public Triple lookup(String key) {
    return lookup(key, (URL) null);
  }

  public Triple lookup(String key, String url) {
    if (url == null || url.length() == 0) return lookup(key);
    try {
      URL u = new URL(url);
      return lookup(key, u);
    } catch (MalformedURLException m) {
    }
    return null;
  }

  public Triple lookup(String key, URL url) {
    List<Triple> list = triplestore.get(key);
    if (list == null) return null;
    if (url == null) {
      if (list.size() == 0) return null;
      return list.get(0);
    } else
      for (Triple t : list) {
        if (urlMatch(t.url, url)) return t;
      }
    return null;
  }

  Triple addtriple(List<Triple> list, Triple triple) {
    Triple prev = null;
    assert (list != null);
    // Look for duplicates
    int i = list.indexOf(triple);
    if (i >= 0) {
      prev = list.remove(i);
    }
    list.add(triple);
    Collections.sort(list);
    return prev;
  }

  // Allow for external loading
  public Triple insert(Triple t) {
    if (t.key == null) return null;
    List<Triple> list = triplestore.get(t.key);
    if (list == null) list = new ArrayList<Triple>();
    Triple prev = addtriple(list, t);
    triplestore.put(t.key, list);
    return prev;
  }

  // Output in .rc form
  public String toString() {
    StringBuilder rc = new StringBuilder();
    for (String key : keySet()) {
      List<Triple> list = getTriples(key);
      for (Triple triple : list) {
        String line = triple.toString();
        rc.append(line);
        rc.append("\n");
      }
    }
    return rc.toString();
  }
} // class RC
/**
 * An IOServiceProvider for CINRAD level II files.
 *
 * @author caron
 */
public class Cinrad2IOServiceProvider extends AbstractIOServiceProvider {
  private static org.slf4j.Logger logger =
      org.slf4j.LoggerFactory.getLogger(Cinrad2IOServiceProvider.class);
  private static final int MISSING_INT = -9999;
  private static final float MISSING_FLOAT = Float.NaN;

  public boolean isValidFileOld(RandomAccessFile raf) {
    try {
      String loc = raf.getLocation();
      int posFirst = loc.lastIndexOf('/') + 1;
      if (posFirst < 0) posFirst = 0;
      String stationId = loc.substring(posFirst, posFirst + 4);
      NexradStationDB.init();
      NexradStationDB.Station station = NexradStationDB.get("K" + stationId);
      if (station != null) return true;
      else return false;
    } catch (IOException ioe) {
      return false;
    }
  }

  public boolean isValidFile(RandomAccessFile raf) {
    int data_msecs = 0;
    short data_julian_date = 0;

    try {
      raf.order(RandomAccessFile.LITTLE_ENDIAN);
      raf.seek(0);
      raf.skipBytes(14);
      short message_type = raf.readShort();
      if (message_type != 1) return false;

      raf.skipBytes(12);
      // data header
      byte[] b4 = raf.readBytes(4);
      data_msecs = bytesToInt(b4, true);
      byte[] b2 = raf.readBytes(2);
      data_julian_date = (short) bytesToShort(b2, true);
      java.util.Date dd = Cinrad2Record.getDate(data_julian_date, data_msecs);

      Calendar cal = new GregorianCalendar(new SimpleTimeZone(0, "GMT"));
      cal.clear();
      cal.setTime(dd);
      int year = cal.get(Calendar.YEAR);
      cal.setTime(new Date());
      int cyear = cal.get(Calendar.YEAR);
      if (year < 1990 || year > cyear) return false;
      return true;
    } catch (IOException ioe) {
      return false;
    }
  }

  public static int bytesToInt(byte[] bytes, boolean swapBytes) {
    byte a = bytes[0];
    byte b = bytes[1];
    byte c = bytes[2];
    byte d = bytes[3];
    if (swapBytes) {
      return ((a & 0xff)) + ((b & 0xff) << 8) + ((c & 0xff) << 16) + ((d & 0xff) << 24);
    } else {
      return ((a & 0xff) << 24) + ((b & 0xff) << 16) + ((c & 0xff) << 8) + ((d & 0xff));
    }
  }

  public static int bytesToShort(byte[] bytes, boolean swapBytes) {
    byte a = bytes[0];
    byte b = bytes[1];

    if (swapBytes) {
      return ((a & 0xff)) + ((b & 0xff) << 8);

    } else {
      return ((a & 0xff) << 24) + ((b & 0xff) << 16);
    }
  }

  public String getFileTypeId() {
    return "CINRAD";
  }

  public String getFileTypeDescription() {
    return "Chinese Level-II Base Data";
  }

  private Cinrad2VolumeScan volScan;
  private Dimension radialDim;
  private double radarRadius;
  private DateFormatter formatter = new DateFormatter();

  public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask)
      throws IOException {
    NexradStationDB.init();

    volScan = new Cinrad2VolumeScan(raf, cancelTask);
    if (volScan.hasDifferentDopplarResolutions())
      throw new IllegalStateException("volScan.hasDifferentDopplarResolutions");

    radialDim = new Dimension("radial", volScan.getMaxRadials());
    ncfile.addDimension(null, radialDim);

    makeVariable(
        ncfile,
        Cinrad2Record.REFLECTIVITY,
        "Reflectivity",
        "Reflectivity",
        "R",
        volScan.getReflectivityGroups());
    int velocity_type =
        (volScan.getDopplarResolution() == Cinrad2Record.DOPPLER_RESOLUTION_HIGH_CODE)
            ? Cinrad2Record.VELOCITY_HI
            : Cinrad2Record.VELOCITY_LOW;
    Variable v =
        makeVariable(
            ncfile,
            velocity_type,
            "RadialVelocity",
            "Radial Velocity",
            "V",
            volScan.getVelocityGroups());
    makeVariableNoCoords(
        ncfile, Cinrad2Record.SPECTRUM_WIDTH, "SpectrumWidth", "Spectrum Width", v);

    if (volScan.getStationId() != null) {
      ncfile.addAttribute(null, new Attribute("Station", volScan.getStationId()));
      ncfile.addAttribute(null, new Attribute("StationName", volScan.getStationName()));
      ncfile.addAttribute(
          null, new Attribute("StationLatitude", new Double(volScan.getStationLatitude())));
      ncfile.addAttribute(
          null, new Attribute("StationLongitude", new Double(volScan.getStationLongitude())));
      ncfile.addAttribute(
          null,
          new Attribute("StationElevationInMeters", new Double(volScan.getStationElevation())));

      double latRadiusDegrees = Math.toDegrees(radarRadius / ucar.unidata.geoloc.Earth.getRadius());
      ncfile.addAttribute(
          null,
          new Attribute(
              "geospatial_lat_min", new Double(volScan.getStationLatitude() - latRadiusDegrees)));
      ncfile.addAttribute(
          null,
          new Attribute(
              "geospatial_lat_max", new Double(volScan.getStationLatitude() + latRadiusDegrees)));
      double cosLat = Math.cos(Math.toRadians(volScan.getStationLatitude()));
      double lonRadiusDegrees =
          Math.toDegrees(radarRadius / cosLat / ucar.unidata.geoloc.Earth.getRadius());
      ncfile.addAttribute(
          null,
          new Attribute(
              "geospatial_lon_min", new Double(volScan.getStationLongitude() - lonRadiusDegrees)));
      ncfile.addAttribute(
          null,
          new Attribute(
              "geospatial_lon_max", new Double(volScan.getStationLongitude() + lonRadiusDegrees)));

      // add a radial coordinate transform (experimental)
      Variable ct = new Variable(ncfile, null, null, "radialCoordinateTransform");
      ct.setDataType(DataType.CHAR);
      ct.setDimensions(""); // scalar
      ct.addAttribute(new Attribute("transform_name", "Radial"));
      ct.addAttribute(new Attribute("center_latitude", new Double(volScan.getStationLatitude())));
      ct.addAttribute(new Attribute("center_longitude", new Double(volScan.getStationLongitude())));
      ct.addAttribute(new Attribute("center_elevation", new Double(volScan.getStationElevation())));
      ct.addAttribute(new Attribute(_Coordinate.TransformType, "Radial"));
      ct.addAttribute(
          new Attribute(_Coordinate.AxisTypes, "RadialElevation RadialAzimuth RadialDistance"));

      Array data =
          Array.factory(DataType.CHAR.getPrimitiveClassType(), new int[0], new char[] {' '});
      ct.setCachedData(data, true);
      ncfile.addVariable(null, ct);
    }

    DateFormatter formatter = new DateFormatter();

    ncfile.addAttribute(null, new Attribute(CDM.CONVENTIONS, _Coordinate.Convention));
    ncfile.addAttribute(null, new Attribute("format", volScan.getDataFormat()));
    ncfile.addAttribute(null, new Attribute(CF.FEATURE_TYPE, FeatureType.RADIAL.toString()));
    // Date d = Cinrad2Record.getDate(volScan.getTitleJulianDays(), volScan.getTitleMsecs());
    // ncfile.addAttribute(null, new Attribute("base_date", formatter.toDateOnlyString(d)));

    ncfile.addAttribute(
        null,
        new Attribute(
            "time_coverage_start", formatter.toDateTimeStringISO(volScan.getStartDate())));
    ; // .toDateTimeStringISO(d)));
    ncfile.addAttribute(
        null,
        new Attribute("time_coverage_end", formatter.toDateTimeStringISO(volScan.getEndDate())));

    ncfile.addAttribute(
        null,
        new Attribute(CDM.HISTORY, "Direct read of Nexrad Level 2 file into NetCDF-Java 2.2 API"));
    ncfile.addAttribute(null, new Attribute("DataType", "Radial"));

    ncfile.addAttribute(
        null,
        new Attribute(
            "Title",
            "Nexrad Level 2 Station "
                + volScan.getStationId()
                + " from "
                + formatter.toDateTimeStringISO(volScan.getStartDate())
                + " to "
                + formatter.toDateTimeStringISO(volScan.getEndDate())));

    ncfile.addAttribute(
        null,
        new Attribute(
            "Summary",
            "Weather Surveillance Radar-1988 Doppler (WSR-88D) "
                + "Level II data are the three meteorological base data quantities: reflectivity, mean radial velocity, and "
                + "spectrum width."));

    ncfile.addAttribute(
        null,
        new Attribute(
            "keywords",
            "WSR-88D; NEXRAD; Radar Level II; reflectivity; mean radial velocity; spectrum width"));

    ncfile.addAttribute(
        null,
        new Attribute(
            "VolumeCoveragePatternName",
            Cinrad2Record.getVolumeCoveragePatternName(volScan.getVCP())));
    ncfile.addAttribute(
        null, new Attribute("VolumeCoveragePattern", new Integer(volScan.getVCP())));
    ncfile.addAttribute(
        null,
        new Attribute(
            "HorizonatalBeamWidthInDegrees", new Double(Cinrad2Record.HORIZONTAL_BEAM_WIDTH)));

    ncfile.finish();
  }

  public Variable makeVariable(
      NetcdfFile ncfile,
      int datatype,
      String shortName,
      String longName,
      String abbrev,
      List groups)
      throws IOException {
    int nscans = groups.size();

    if (nscans == 0) {
      throw new IllegalStateException("No data for " + shortName);
    }

    // get representative record
    List firstGroup = (List) groups.get(0);
    Cinrad2Record firstRecord = (Cinrad2Record) firstGroup.get(0);
    int ngates = firstRecord.getGateCount(datatype);

    String scanDimName = "scan" + abbrev;
    String gateDimName = "gate" + abbrev;
    Dimension scanDim = new Dimension(scanDimName, nscans);
    Dimension gateDim = new Dimension(gateDimName, ngates);
    ncfile.addDimension(null, scanDim);
    ncfile.addDimension(null, gateDim);

    ArrayList dims = new ArrayList();
    dims.add(scanDim);
    dims.add(radialDim);
    dims.add(gateDim);

    Variable v = new Variable(ncfile, null, null, shortName);
    v.setDataType(DataType.BYTE);
    v.setDimensions(dims);
    ncfile.addVariable(null, v);

    v.addAttribute(new Attribute(CDM.UNITS, Cinrad2Record.getDatatypeUnits(datatype)));
    v.addAttribute(new Attribute(CDM.LONG_NAME, longName));

    byte[] b = new byte[2];
    b[0] = Cinrad2Record.MISSING_DATA;
    b[1] = Cinrad2Record.BELOW_THRESHOLD;
    Array missingArray = Array.factory(DataType.BYTE.getPrimitiveClassType(), new int[] {2}, b);

    v.addAttribute(new Attribute(CDM.MISSING_VALUE, missingArray));
    v.addAttribute(
        new Attribute("signal_below_threshold", new Byte(Cinrad2Record.BELOW_THRESHOLD)));
    v.addAttribute(
        new Attribute(CDM.SCALE_FACTOR, new Float(Cinrad2Record.getDatatypeScaleFactor(datatype))));
    v.addAttribute(
        new Attribute(CDM.ADD_OFFSET, new Float(Cinrad2Record.getDatatypeAddOffset(datatype))));
    v.addAttribute(new Attribute(CDM.UNSIGNED, "true"));

    ArrayList dim2 = new ArrayList();
    dim2.add(scanDim);
    dim2.add(radialDim);

    // add time coordinate variable
    String timeCoordName = "time" + abbrev;
    Variable timeVar = new Variable(ncfile, null, null, timeCoordName);
    timeVar.setDataType(DataType.INT);
    timeVar.setDimensions(dim2);
    ncfile.addVariable(null, timeVar);

    // int julianDays = volScan.getTitleJulianDays();
    // Date d = Cinrad2Record.getDate( julianDays, 0);
    // Date d = Cinrad2Record.getDate(volScan.getTitleJulianDays(), volScan.getTitleMsecs());
    Date d = volScan.getStartDate();
    String units = "msecs since " + formatter.toDateTimeStringISO(d);

    timeVar.addAttribute(new Attribute(CDM.LONG_NAME, "time since base date"));
    timeVar.addAttribute(new Attribute(CDM.UNITS, units));
    timeVar.addAttribute(new Attribute(CDM.MISSING_VALUE, new Integer(MISSING_INT)));
    timeVar.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Time.toString()));

    // add elevation coordinate variable
    String elevCoordName = "elevation" + abbrev;
    Variable elevVar = new Variable(ncfile, null, null, elevCoordName);
    elevVar.setDataType(DataType.FLOAT);
    elevVar.setDimensions(dim2);
    ncfile.addVariable(null, elevVar);

    elevVar.addAttribute(new Attribute(CDM.UNITS, "degrees"));
    elevVar.addAttribute(
        new Attribute(
            CDM.LONG_NAME,
            "elevation angle in degres: 0 = parallel to pedestal base, 90 = perpendicular"));
    elevVar.addAttribute(new Attribute(CDM.MISSING_VALUE, new Float(MISSING_FLOAT)));
    elevVar.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.RadialElevation.toString()));

    // add azimuth coordinate variable
    String aziCoordName = "azimuth" + abbrev;
    Variable aziVar = new Variable(ncfile, null, null, aziCoordName);
    aziVar.setDataType(DataType.FLOAT);
    aziVar.setDimensions(dim2);
    ncfile.addVariable(null, aziVar);

    aziVar.addAttribute(new Attribute(CDM.UNITS, "degrees"));
    aziVar.addAttribute(
        new Attribute(CDM.LONG_NAME, "azimuth angle in degrees: 0 = true north, 90 = east"));
    aziVar.addAttribute(new Attribute(CDM.MISSING_VALUE, new Float(MISSING_FLOAT)));
    aziVar.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.RadialAzimuth.toString()));

    // add gate coordinate variable
    String gateCoordName = "distance" + abbrev;
    Variable gateVar = new Variable(ncfile, null, null, gateCoordName);
    gateVar.setDataType(DataType.FLOAT);
    gateVar.setDimensions(gateDimName);
    Array data =
        Array.makeArray(
            DataType.FLOAT,
            ngates,
            (double) firstRecord.getGateStart(datatype),
            (double) firstRecord.getGateSize(datatype));
    gateVar.setCachedData(data, false);
    ncfile.addVariable(null, gateVar);
    radarRadius = firstRecord.getGateStart(datatype) + ngates * firstRecord.getGateSize(datatype);

    gateVar.addAttribute(new Attribute(CDM.UNITS, "m"));
    gateVar.addAttribute(new Attribute(CDM.LONG_NAME, "radial distance to start of gate"));
    gateVar.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.RadialDistance.toString()));

    // add number of radials variable
    String nradialsName = "numRadials" + abbrev;
    Variable nradialsVar = new Variable(ncfile, null, null, nradialsName);
    nradialsVar.setDataType(DataType.INT);
    nradialsVar.setDimensions(scanDim.getName());
    nradialsVar.addAttribute(new Attribute(CDM.LONG_NAME, "number of valid radials in this scan"));
    ncfile.addVariable(null, nradialsVar);

    // add number of gates variable
    String ngateName = "numGates" + abbrev;
    Variable ngateVar = new Variable(ncfile, null, null, ngateName);
    ngateVar.setDataType(DataType.INT);
    ngateVar.setDimensions(scanDim.getName());
    ngateVar.addAttribute(new Attribute(CDM.LONG_NAME, "number of valid gates in this scan"));
    ncfile.addVariable(null, ngateVar);

    makeCoordinateDataWithMissing(
        datatype, timeVar, elevVar, aziVar, nradialsVar, ngateVar, groups);

    // back to the data variable
    String coordinates =
        timeCoordName + " " + elevCoordName + " " + aziCoordName + " " + gateCoordName;
    v.addAttribute(new Attribute(_Coordinate.Axes, coordinates));

    // make the record map
    int nradials = radialDim.getLength();
    Cinrad2Record[][] map = new Cinrad2Record[nscans][nradials];
    for (int i = 0; i < groups.size(); i++) {
      Cinrad2Record[] mapScan = map[i];
      List group = (List) groups.get(i);
      for (int j = 0; j < group.size(); j++) {
        Cinrad2Record r = (Cinrad2Record) group.get(j);
        int radial = r.radial_num - 1;
        mapScan[radial] = r;
      }
    }

    Vgroup vg = new Vgroup(datatype, map);
    v.setSPobject(vg);

    return v;
  }

  private void makeVariableNoCoords(
      NetcdfFile ncfile, int datatype, String shortName, String longName, Variable from) {

    Variable v = new Variable(ncfile, null, null, shortName);
    v.setDataType(DataType.BYTE);
    v.setDimensions(from.getDimensions());
    ncfile.addVariable(null, v);

    v.addAttribute(new Attribute(CDM.UNITS, Cinrad2Record.getDatatypeUnits(datatype)));
    v.addAttribute(new Attribute(CDM.LONG_NAME, longName));

    byte[] b = new byte[2];
    b[0] = Cinrad2Record.MISSING_DATA;
    b[1] = Cinrad2Record.BELOW_THRESHOLD;
    Array missingArray = Array.factory(DataType.BYTE.getPrimitiveClassType(), new int[] {2}, b);

    v.addAttribute(new Attribute(CDM.MISSING_VALUE, missingArray));
    v.addAttribute(
        new Attribute("signal_below_threshold", new Byte(Cinrad2Record.BELOW_THRESHOLD)));
    v.addAttribute(
        new Attribute(CDM.SCALE_FACTOR, new Float(Cinrad2Record.getDatatypeScaleFactor(datatype))));
    v.addAttribute(
        new Attribute(CDM.ADD_OFFSET, new Float(Cinrad2Record.getDatatypeAddOffset(datatype))));
    v.addAttribute(new Attribute(CDM.UNSIGNED, "true"));

    Attribute fromAtt = from.findAttribute(_Coordinate.Axes);
    v.addAttribute(new Attribute(_Coordinate.Axes, fromAtt));

    Vgroup vgFrom = (Vgroup) from.getSPobject();
    Vgroup vg = new Vgroup(datatype, vgFrom.map);
    v.setSPobject(vg);
  }

  private void makeCoordinateData(
      int datatype,
      Variable time,
      Variable elev,
      Variable azi,
      Variable nradialsVar,
      Variable ngatesVar,
      List groups) {

    Array timeData = Array.factory(time.getDataType().getPrimitiveClassType(), time.getShape());
    IndexIterator timeDataIter = timeData.getIndexIterator();

    Array elevData = Array.factory(elev.getDataType().getPrimitiveClassType(), elev.getShape());
    IndexIterator elevDataIter = elevData.getIndexIterator();

    Array aziData = Array.factory(azi.getDataType().getPrimitiveClassType(), azi.getShape());
    IndexIterator aziDataIter = aziData.getIndexIterator();

    Array nradialsData =
        Array.factory(nradialsVar.getDataType().getPrimitiveClassType(), nradialsVar.getShape());
    IndexIterator nradialsIter = nradialsData.getIndexIterator();

    Array ngatesData =
        Array.factory(ngatesVar.getDataType().getPrimitiveClassType(), ngatesVar.getShape());
    IndexIterator ngatesIter = ngatesData.getIndexIterator();

    int last_msecs = Integer.MIN_VALUE;
    int nscans = groups.size();
    int maxRadials = volScan.getMaxRadials();
    for (int i = 0; i < nscans; i++) {
      List scanGroup = (List) groups.get(i);
      int nradials = scanGroup.size();

      Cinrad2Record first = null;
      for (int j = 0; j < nradials; j++) {
        Cinrad2Record r = (Cinrad2Record) scanGroup.get(j);
        if (first == null) first = r;

        timeDataIter.setIntNext(r.data_msecs);
        elevDataIter.setFloatNext(r.getElevation());
        aziDataIter.setFloatNext(r.getAzimuth());

        if (r.data_msecs < last_msecs)
          logger.warn("makeCoordinateData time out of order " + r.data_msecs);
        last_msecs = r.data_msecs;
      }

      for (int j = nradials; j < maxRadials; j++) {
        timeDataIter.setIntNext(MISSING_INT);
        elevDataIter.setFloatNext(MISSING_FLOAT);
        aziDataIter.setFloatNext(MISSING_FLOAT);
      }

      nradialsIter.setIntNext(nradials);
      ngatesIter.setIntNext(first.getGateCount(datatype));
    }

    time.setCachedData(timeData, false);
    elev.setCachedData(elevData, false);
    azi.setCachedData(aziData, false);
    nradialsVar.setCachedData(nradialsData, false);
    ngatesVar.setCachedData(ngatesData, false);
  }

  private void makeCoordinateDataWithMissing(
      int datatype,
      Variable time,
      Variable elev,
      Variable azi,
      Variable nradialsVar,
      Variable ngatesVar,
      List groups) {

    Array timeData = Array.factory(time.getDataType().getPrimitiveClassType(), time.getShape());
    Index timeIndex = timeData.getIndex();

    Array elevData = Array.factory(elev.getDataType().getPrimitiveClassType(), elev.getShape());
    Index elevIndex = elevData.getIndex();

    Array aziData = Array.factory(azi.getDataType().getPrimitiveClassType(), azi.getShape());
    Index aziIndex = aziData.getIndex();

    Array nradialsData =
        Array.factory(nradialsVar.getDataType().getPrimitiveClassType(), nradialsVar.getShape());
    IndexIterator nradialsIter = nradialsData.getIndexIterator();

    Array ngatesData =
        Array.factory(ngatesVar.getDataType().getPrimitiveClassType(), ngatesVar.getShape());
    IndexIterator ngatesIter = ngatesData.getIndexIterator();

    // first fill with missing data
    IndexIterator ii = timeData.getIndexIterator();
    while (ii.hasNext()) ii.setIntNext(MISSING_INT);

    ii = elevData.getIndexIterator();
    while (ii.hasNext()) ii.setFloatNext(MISSING_FLOAT);

    ii = aziData.getIndexIterator();
    while (ii.hasNext()) ii.setFloatNext(MISSING_FLOAT);

    // now set the  coordinate variables from the Cinrad2Record radial
    int last_msecs = Integer.MIN_VALUE;
    int nscans = groups.size();
    try {
      for (int scan = 0; scan < nscans; scan++) {
        List scanGroup = (List) groups.get(scan);
        int nradials = scanGroup.size();

        Cinrad2Record first = null;
        for (int j = 0; j < nradials; j++) {
          Cinrad2Record r = (Cinrad2Record) scanGroup.get(j);
          if (first == null) first = r;

          int radial = r.radial_num - 1;
          timeData.setInt(timeIndex.set(scan, radial), r.data_msecs);
          elevData.setFloat(elevIndex.set(scan, radial), r.getElevation());
          aziData.setFloat(aziIndex.set(scan, radial), r.getAzimuth());

          if (r.data_msecs < last_msecs)
            logger.warn("makeCoordinateData time out of order " + r.data_msecs);
          last_msecs = r.data_msecs;
        }

        nradialsIter.setIntNext(nradials);
        ngatesIter.setIntNext(first.getGateCount(datatype));
      }
    } catch (java.lang.ArrayIndexOutOfBoundsException ae) {
      logger.debug("Cinrad2IOSP.uncompress ", ae);
    }
    time.setCachedData(timeData, false);
    elev.setCachedData(elevData, false);
    azi.setCachedData(aziData, false);
    nradialsVar.setCachedData(nradialsData, false);
    ngatesVar.setCachedData(ngatesData, false);
  }

  public Array readData(Variable v2, Section section) throws IOException, InvalidRangeException {
    Vgroup vgroup = (Vgroup) v2.getSPobject();

    Range scanRange = section.getRange(0);
    Range radialRange = section.getRange(1);
    Range gateRange = section.getRange(2);

    Array data = Array.factory(v2.getDataType().getPrimitiveClassType(), section.getShape());
    IndexIterator ii = data.getIndexIterator();

    for (int i = scanRange.first(); i <= scanRange.last(); i += scanRange.stride()) {
      Cinrad2Record[] mapScan = vgroup.map[i];
      readOneScan(mapScan, radialRange, gateRange, vgroup.datatype, ii);
    }

    return data;
  }

  private void readOneScan(
      Cinrad2Record[] mapScan, Range radialRange, Range gateRange, int datatype, IndexIterator ii)
      throws IOException {
    for (int i = radialRange.first(); i <= radialRange.last(); i += radialRange.stride()) {
      Cinrad2Record r = mapScan[i];
      readOneRadial(r, datatype, gateRange, ii);
    }
  }

  private void readOneRadial(Cinrad2Record r, int datatype, Range gateRange, IndexIterator ii)
      throws IOException {
    if (r == null) {
      for (int i = gateRange.first(); i <= gateRange.last(); i += gateRange.stride())
        ii.setByteNext(Cinrad2Record.MISSING_DATA);
      return;
    }
    r.readData(volScan.raf, datatype, gateRange, ii);
  }

  private class Vgroup {
    Cinrad2Record[][] map;
    int datatype;

    Vgroup(int datatype, Cinrad2Record[][] map) {
      this.datatype = datatype;
      this.map = map;
    }
  }

  /////////////////////////////////////////////////////////////////////

  public void close() throws IOException {
    volScan.raf.close();
  }
}
Exemple #12
0
/**
 * A session is encapsulated in an instance of the class HTTPSession. The encapsulation is with
 * respect to a specific HttpHost "realm", where the important part is is host+port. This means that
 * once a session is specified, it is tied permanently to that realm.
 *
 * <p>A Session encapsulate a number of other objects:
 *
 * <ul>
 *   <li>An instance of an Apache HttpClient.
 *   <li>A http session id
 *   <li>A RequestContext object; this also includes authentication: specifically a credential and a
 *       credentials provider.
 * </ul>
 *
 * <p>Currently, it is assumed that only one set of credentials is needed, whether directly for
 * server X or for server Y. This may change in the future.
 *
 * <p>As a rule, if the client gives an HTTPSession object to the "create method" procedures of
 * HTTPFactory (e.g. HTTPFactory.Get or HTTPFactory.Post) then that creation call must specify a url
 * that is "compatible" with the scope of the session. The method url is <it>compatible</i> if its
 * host+port is the same as the session's host+port (=scope) and its scheme is compatible, where
 * e.g. http is compatible with https (see HTTPAuthUtil.httphostCompatible)
 *
 * <p>If the HTTPFactory method creation call does not specify a session object, then one is created
 * (and destroyed) behind the scenes along with the method.
 *
 * <p>Note that the term legalurl in the following code means that the url has reserved characters
 * within identifieers in escaped form. This is particularly and issue for queries. Especially:
 * ?x[0:5] is legal and the square brackets need not be encoded.
 *
 * <p>As of the move to Apache Httpclient 4.4 and later, the underlying HttpClient objects are
 * generally immutable. This means that at least this class (HTTPSession) and the HTTPMethod class
 * must store the relevant info and create the HttpClient and HttpMethod objects dynamically. This
 * also means that when a parameter is changed (Agent, for example), any existing cached HttpClient
 * must be thrown away and reconstructed using the change. As a rule, the HttpClient object will be
 * created at the last minute so that multiple parameter changes can be effected without have to
 * re-create the HttpClient for each parameter change. Also note that the immutable objects will be
 * cached and reused if no parameters are changed.
 *
 * <p><em>Authorization</em> We assume that the session supports two CredentialsProvider instances:
 * one global to all HTTPSession objects and one specific to each HTTPSession object.
 *
 * <p>As an aside, authentication is a bit tricky because some authorization schemes use
 * redirection. That is, the initial request is made to server X, but X says: goto to server Y" to
 * get, say, and authorization token. Then Y says: return to X with this token and proceed.
 *
 * <p><em>SSL</em> TBD.
 */
public class HTTPSession implements Closeable {
  //////////////////////////////////////////////////
  // Constants

  // Define all the legal properties
  // Previously taken from class AllClientPNames, but that is now
  // deprecated, so just use an enum

  static /*package*/ enum Prop {
    ALLOW_CIRCULAR_REDIRECTS,
    HANDLE_REDIRECTS,
    HANDLE_AUTHENTICATION,
    MAX_REDIRECTS,
    MAX_THREADS,
    SO_TIMEOUT,
    CONN_TIMEOUT,
    CONN_REQ_TIMEOUT,
    USER_AGENT,
    COOKIE_STORE,
    RETRIES,
    UNAVAILRETRIES,
    COMPRESSION,
    CREDENTIALS,
    USESESSIONS,
  }

  // Header names
  // from: http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
  public static final String HEADER_USERAGENT = "User-Agent";
  public static final String ACCEPT_ENCODING = "Accept-Encoding";

  static final int DFALTTHREADCOUNT = 50;
  static final int DFALTREDIRECTS = 25;
  static final int DFALTCONNTIMEOUT = 1 * 60 * 1000; // 1 minutes (60000 milliseconds)
  static final int DFALTCONNREQTIMEOUT = DFALTCONNTIMEOUT;
  static final int DFALTSOTIMEOUT = 5 * 60 * 1000; // 5 minutes (300000 milliseconds)

  static final int DFALTRETRIES = 3;
  static final int DFALTUNAVAILRETRIES = 3;
  static final int DFALTUNAVAILINTERVAL = 3000; // 3 seconds
  static final String DFALTUSERAGENT = "/NetcdfJava/HttpClient4.4";

  static final String[] KNOWNCOMPRESSORS = {"gzip", "deflate"};

  //////////////////////////////////////////////////////////////////////////

  static final boolean IGNORECERTS = false;

  //////////////////////////////////////////////////////////////////////////
  // Type Declaration(s)

  // Support loose certificate acceptance
  static class LooseTrustStrategy extends TrustSelfSignedStrategy {
    @Override
    public boolean isTrusted(final X509Certificate[] chain, String authType)
        throws CertificateException {
      try {
        if (super.isTrusted(chain, authType)) return true;
        // check expiration dates
        for (X509Certificate x5 : chain) {
          try {
            x5.checkValidity();
          } catch (CertificateExpiredException | CertificateNotYetValidException ce) {
            return true;
          }
        }
      } catch (CertificateException e) {
        return true; // temporary
      }
      return false;
    }
  }

  /** Sub-class HashTable<String,Object> for mnemonic convenience and for synchronized access. */
  static class Settings extends Hashtable<Prop, Object> {
    public Settings() {}

    public Set<Prop> getKeys() {
      return keySet();
    }

    public Object getParameter(Prop param) {
      return super.get(param);
    }

    public long getIntParameter(Prop param) {
      return (Long) super.get(param);
    }

    public Settings setParameter(Prop param, Object value) {
      super.put(param, value);
      return this;
    }

    public Object removeParameter(Prop param) {
      return super.remove(param);
    }
  }

  // For communication between HTTPSession.execute and HTTPMethod.execute.
  static /*package*/ class ExecState {
    public HttpRequestBase request = null;
    public CloseableHttpResponse response = null;
  }

  static /*package*/ enum Methods {
    Get("get"),
    Head("head"),
    Put("put"),
    Post("post"),
    Options("options");
    private final String name;

    Methods(String name) {
      this.name = name;
    }

    public String getName() {
      return name;
    }
  }

  static class GZIPResponseInterceptor implements HttpResponseInterceptor {
    public void process(final HttpResponse response, final HttpContext context)
        throws HttpException, IOException {
      HttpEntity entity = response.getEntity();
      if (entity != null) {
        Header ceheader = entity.getContentEncoding();
        if (ceheader != null) {
          HeaderElement[] codecs = ceheader.getElements();
          for (HeaderElement h : codecs) {
            if (h.getName().equalsIgnoreCase("gzip")) {
              response.setEntity(new GzipDecompressingEntity(response.getEntity()));
              return;
            }
          }
        }
      }
    }
  }

  static class DeflateResponseInterceptor implements HttpResponseInterceptor {
    public void process(final HttpResponse response, final HttpContext context)
        throws HttpException, IOException {
      HttpEntity entity = response.getEntity();
      if (entity != null) {
        Header ceheader = entity.getContentEncoding();
        if (ceheader != null) {
          HeaderElement[] codecs = ceheader.getElements();
          for (HeaderElement h : codecs) {
            if (h.getName().equalsIgnoreCase("deflate")) {
              response.setEntity(new DeflateDecompressingEntity(response.getEntity()));
              return;
            }
          }
        }
      }
    }
  }

  static class ZipStreamFactory implements InputStreamFactory {
    // InputStreamFactory methods
    @Override
    public InputStream create(InputStream instream) throws IOException {
      return new ZipInputStream(instream, HTTPUtil.UTF8);
    }
  }

  static class GZIPStreamFactory implements InputStreamFactory {
    // InputStreamFactory methods
    @Override
    public InputStream create(InputStream instream) throws IOException {
      return new GZIPInputStream(instream);
    }
  }

  ////////////////////////////////////////////////////////////////////////
  // Static variables

  public static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(HTTPSession.class);

  protected static PoolingHttpClientConnectionManager connmgr;

  // Define a settings object to hold all the
  // settable values; there will be one
  // instance for global and one for local.

  static Settings globalsettings;

  // Define interceptor instances
  static List<HttpRequestInterceptor> reqintercepts = new ArrayList<HttpRequestInterceptor>();
  static List<HttpResponseInterceptor> rspintercepts = new ArrayList<HttpResponseInterceptor>();
  // This is a hack to suppress content-encoding headers from request
  protected static HttpResponseInterceptor CEKILL;
  // Debug Header interceptors
  protected static List<HttpRequestInterceptor> dbgreq = new ArrayList<>();
  protected static List<HttpResponseInterceptor> dbgrsp = new ArrayList<>();

  protected static Map<String, InputStreamFactory> contentDecoderMap;

  // public final HttpClientBuilder setContentDecoderRegistry(Map<String,InputStreamFactory>
  // contentDecoderMap)

  // Since can't access CredentialsProvider map, mimic
  protected static Map<AuthScope, CredentialsProvider> globalcreds = new HashMap<>();

  // As taken from the command line, usually
  protected static KeyStore keystore = null;
  protected static KeyStore truststore = null;
  protected static String keypath = null;
  protected static String keypassword = null;
  protected static String trustpath = null;
  protected static String trustpassword = null;
  protected static SSLConnectionSocketFactory globalsslfactory = null;
  protected static Registry<ConnectionSocketFactory> sslregistry = null;

  protected static HttpHost httpproxy = null;
  protected static HttpHost httpsproxy = null;
  protected static String proxyuser = null;
  protected static String proxypwd = null;

  // For debugging
  protected static Boolean globaldebugheaders = null;

  static { // watch out: order is important for these initializers
    CEKILL = new HTTPUtil.ContentEncodingInterceptor();
    contentDecoderMap = new HashMap<String, InputStreamFactory>();
    contentDecoderMap.put("zip", new ZipStreamFactory());
    contentDecoderMap.put("gzip", new GZIPStreamFactory());
    globalsettings = new Settings();
    setDefaults(globalsettings);
    processDFlags(); // Process all -D flags
    connmgr = new PoolingHttpClientConnectionManager(sslregistry);
    setGlobalUserAgent(DFALTUSERAGENT);
    // does not work setGlobalThreadCount(DFALTTHREADCOUNT);
    setGlobalConnectionTimeout(DFALTCONNTIMEOUT);
    setGlobalSoTimeout(DFALTSOTIMEOUT);
  }

  //////////////////////////////////////////////////////////////////////////
  // Static Initialization

  // Provide defaults for a settings map
  protected static void setDefaults(Settings props) {
    if (false) { // turn off for now
      props.setParameter(Prop.HANDLE_AUTHENTICATION, Boolean.TRUE);
    }
    props.setParameter(Prop.HANDLE_REDIRECTS, Boolean.TRUE);
    props.setParameter(Prop.ALLOW_CIRCULAR_REDIRECTS, Boolean.TRUE);
    props.setParameter(Prop.MAX_REDIRECTS, (Integer) DFALTREDIRECTS);
    props.setParameter(Prop.SO_TIMEOUT, (Integer) DFALTSOTIMEOUT);
    props.setParameter(Prop.CONN_TIMEOUT, (Integer) DFALTCONNTIMEOUT);
    props.setParameter(Prop.CONN_REQ_TIMEOUT, (Integer) DFALTCONNREQTIMEOUT);
    props.setParameter(Prop.USER_AGENT, DFALTUSERAGENT);
  }

  static synchronized void processDFlags() {
    // SSL flags
    keypath = cleanproperty("keystore");
    keypassword = cleanproperty("keystorepassword");
    trustpath = cleanproperty("truststore");
    trustpassword = cleanproperty("truststorepassword");
    setGlobalSSLAuth(keypath, keypassword, trustpath, trustpassword);

    // Proxy flags
    String proxyurl = cleanproperty("proxyurl");
    if (proxyurl != null) setGlobalProxy(proxyurl);
    else { // Check the java.net flags
      String proxyhost = cleanproperty("https.proxyHost");
      if (proxyhost != null) {
        StringBuilder buf = new StringBuilder();
        buf.append("https://");
        buf.append(proxyhost);
        String proxyport = cleanproperty("https.proxyPort");
        if (proxyport != null) {
          buf.append(":");
          buf.append(proxyport);
        }
        setGlobalProxy(buf.toString());
      }
    }
  }

  //////////////////////////////////////////////////////////////////////////
  // Static Methods (Mostly global accessors)

  public static synchronized void setGlobalUserAgent(String userAgent) {
    globalsettings.setParameter(Prop.USER_AGENT, userAgent);
  }

  public static synchronized String getGlobalUserAgent() {
    return (String) globalsettings.getParameter(Prop.USER_AGENT);
  }

  public static synchronized void setGlobalThreadCount(int nthreads) {
    // globalsettings.setParameter(Prop.MAX_THREADS,nthreads);
    throw new UnsupportedOperationException(
        "HTTPSession.setGlobalThreadCount is currently not working");
  }

  // Alias
  public static void setGlobalMaxConnections(int nthreads) {
    setGlobalThreadCount(nthreads);
  }

  public static synchronized int getGlobalThreadCount() {
    return connmgr.getMaxTotal();
  }

  // Timeouts

  public static synchronized void setGlobalConnectionTimeout(int timeout) {
    if (timeout >= 0) {
      globalsettings.setParameter(Prop.CONN_TIMEOUT, (Integer) timeout);
      globalsettings.setParameter(Prop.CONN_REQ_TIMEOUT, (Integer) timeout);
    }
  }

  public static synchronized void setGlobalSoTimeout(int timeout) {
    if (timeout >= 0) globalsettings.setParameter(Prop.SO_TIMEOUT, (Integer) timeout);
  }

  /** Enable/disable redirection following Default is yes. */
  public static synchronized void setGlobalFollowRedirects(boolean tf) {
    globalsettings.setParameter(Prop.HANDLE_REDIRECTS, (Boolean) tf);
  }

  /**
   * Set the max number of redirects to follow
   *
   * @param n
   */
  public static synchronized void setGlobalMaxRedirects(int n) {
    if (n < 0) // validate
    throw new IllegalArgumentException("setMaxRedirects");
    globalsettings.setParameter(Prop.MAX_REDIRECTS, n);
  }

  public static synchronized Object getGlobalSetting(String key) {
    return globalsettings.get(key);
  }

  //////////////////////////////////////////////////
  // Compression

  public static synchronized void setGlobalCompression(String compressors) {
    if (globalsettings.getParameter(Prop.COMPRESSION) != null) removeGlobalCompression();
    String compresslist = checkCompressors(compressors);
    if (HTTPUtil.nullify(compresslist) == null)
      throw new IllegalArgumentException("Bad compressors: " + compressors);
    globalsettings.setParameter(Prop.COMPRESSION, compresslist);
    HttpResponseInterceptor hrsi;
    if (compresslist.contains("gzip")) {
      hrsi = new GZIPResponseInterceptor();
      rspintercepts.add(hrsi);
    }
    if (compresslist.contains("deflate")) {
      hrsi = new DeflateResponseInterceptor();
      rspintercepts.add(hrsi);
    }
  }

  public static void removeGlobalCompression() {
    if (globalsettings.removeParameter(Prop.COMPRESSION) != null) {
      for (int i = rspintercepts.size() - 1; i >= 0; i--) { // walk backwards
        HttpResponseInterceptor hrsi = rspintercepts.get(i);
        if (hrsi instanceof GZIPResponseInterceptor || hrsi instanceof DeflateResponseInterceptor)
          rspintercepts.remove(i);
      }
    }
  }

  protected static synchronized String checkCompressors(String compressors) {
    // Syntactic check of compressors
    Set<String> cset = new HashSet<>();
    compressors = compressors.replace(',', ' ');
    compressors = compressors.replace('\t', ' ');
    String[] pieces = compressors.split("[ ]+");
    for (String p : pieces) {
      for (String c : KNOWNCOMPRESSORS) {
        if (p.equalsIgnoreCase(c)) {
          cset.add(c);
          break;
        }
      }
    }
    StringBuilder buf = new StringBuilder();
    for (String s : cset) {
      if (buf.length() > 0) buf.append(",");
      buf.append(s);
    }
    return buf.toString();
  }
  //////////////////////////////////////////////////
  // Authorization

  /**
   * @param provider
   * @throws HTTPException
   */
  public static void setGlobalCredentialsProvider(CredentialsProvider provider)
      throws HTTPException {
    setGlobalCredentialsProvider(provider, (AuthScope) null);
  }

  /**
   * This is the most general case
   *
   * @param provider the credentials provider
   * @param scope where to use it (i.e. on what host)
   * @throws HTTPException
   */
  public static void setGlobalCredentialsProvider(CredentialsProvider provider, AuthScope scope)
      throws HTTPException {
    mapcreds(provider, scope, globalcreds);
  }

  /**
   * It is convenient to be able to directly set the Credentials (not the provider) when those
   * credentials are fixed. Scope defaults to ANY
   *
   * @param creds
   * @throws HTTPException
   */
  public static void setGlobalCredentials(Credentials creds) throws HTTPException {
    setGlobalCredentials(creds, null);
  }

  /**
   * It is convenient to be able to directly set the Credentials (not the provider) when those
   * credentials are fixed.
   *
   * @param creds
   * @param scope where to use it (i.e. on what host)
   * @throws HTTPException
   */
  public static void setGlobalCredentials(Credentials creds, AuthScope scope) throws HTTPException {
    assert (creds != null);
    if (scope == null) scope = AuthScope.ANY;
    CredentialsProvider provider = new BasicCredentialsProvider();
    provider.setCredentials(scope, creds);
    setGlobalCredentialsProvider(provider, scope);
  }

  /* Make this externally accessible primarily for testing */

  public static synchronized void setGlobalSSLAuth(
      String keypath, String keypassword, String trustpath, String trustpassword) {
    // load the stores if defined
    try {
      if (trustpath != null && trustpassword != null) {
        truststore = KeyStore.getInstance(KeyStore.getDefaultType());
        try (FileInputStream instream = new FileInputStream(new File(trustpath))) {
          truststore.load(instream, trustpassword.toCharArray());
        }
      } else truststore = null;
      if (keypath != null && keypassword != null) {
        keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        try (FileInputStream instream = new FileInputStream(new File(keypath))) {
          keystore.load(instream, keypassword.toCharArray());
        }
      } else keystore = null;
    } catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException ex) {
      log.error("Illegal -D keystore parameters: " + ex.getMessage());
      truststore = null;
      keystore = null;
    }
    try {
      // set up the context
      SSLContext scxt = null;
      if (IGNORECERTS) {
        scxt = SSLContext.getInstance("TLS");
        TrustManager[] trust_mgr =
            new TrustManager[] {
              new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                  return null;
                }

                public void checkClientTrusted(X509Certificate[] certs, String t) {}

                public void checkServerTrusted(X509Certificate[] certs, String t) {}
              }
            };
        scxt.init(
            null, // key manager
            trust_mgr, // trust manager
            new SecureRandom()); // random number generator
      } else {
        SSLContextBuilder sslbuilder = SSLContexts.custom();
        TrustStrategy strat = new LooseTrustStrategy();
        if (truststore != null) sslbuilder.loadTrustMaterial(truststore, strat);
        else sslbuilder.loadTrustMaterial(strat);
        sslbuilder.loadTrustMaterial(truststore, new LooseTrustStrategy());
        if (keystore != null) sslbuilder.loadKeyMaterial(keystore, keypassword.toCharArray());
        scxt = sslbuilder.build();
      }
      globalsslfactory = new SSLConnectionSocketFactory(scxt, new NoopHostnameVerifier());

      RegistryBuilder rb = RegistryBuilder.<ConnectionSocketFactory>create();
      rb.register("https", globalsslfactory);
      sslregistry = rb.build();
    } catch (KeyStoreException
        | NoSuchAlgorithmException
        | KeyManagementException
        | UnrecoverableEntryException e) {
      log.error("Failed to set key/trust store(s): " + e.getMessage());
      sslregistry = null;
      globalsslfactory = null;
    }
  }

  public static synchronized void setGlobalProxy(String proxyurl) {
    if (proxyurl == null) throw new IllegalArgumentException("Bad proxy URL: " + proxyurl);
    URI uri;
    try {
      uri = HTTPUtil.parseToURI(proxyurl);
    } catch (URISyntaxException e) {
      throw new IllegalArgumentException("Bad proxy URL: " + proxyurl);
    }
    if (uri.getScheme().equals("http"))
      httpproxy = new HttpHost(uri.getHost(), uri.getPort(), "http");
    else if (uri.getScheme().equals("https"))
      httpsproxy = new HttpHost(uri.getHost(), uri.getPort(), "https");
    String upw = uri.getUserInfo();
    if (upw != null) {
      String[] pieces = upw.split("[:]");
      if (pieces.length != 2
          || HTTPUtil.nullify(pieces[0]) == null
          || HTTPUtil.nullify(pieces[1]) == null)
        throw new IllegalArgumentException("Bad userinfo: " + proxyurl);
      proxyuser = pieces[0];
      proxypwd = pieces[1];
    }
  }

  //////////////////////////////////////////////////
  // Instance variables

  // Currently, the granularity of authorization is host+port.
  protected String sessionURI = null; // This is either a real url
  // or one constructed from an AuthScope
  protected URI scopeURI = null; // constructed
  protected AuthScope scope = null;
  protected boolean closed = false;

  // Since can't access CredentialsProvider map, mimic
  protected Map<AuthScope, CredentialsProvider> localcreds = new HashMap<>();

  protected List<ucar.httpservices.HTTPMethod> methodList = new Vector<HTTPMethod>();
  protected String identifier = "Session";
  protected Settings localsettings = new Settings();

  // We currently only allow the use of global interceptors
  //    protected List<Object> intercepts = new ArrayList<Object>(); // current set of interceptors;

  // This context is re-used over all method executions so that we maintain
  // cookies, credentials, etc.
  // But we do need away to clear so that e.g. we can clear credentials cache
  protected HttpClientContext sessioncontext = HttpClientContext.create();

  // cached and recreated as needed
  protected boolean cachevalid = false; // Are cached items up-to-date?
  protected CloseableHttpClient cachedclient = null;
  protected RequestConfig cachedconfig = null;
  protected URI requestURI = null; // full uri from the HTTPMethod call
  protected ExecState execution = new ExecState();

  //////////////////////////////////////////////////
  // Constructor(s)
  // All are package level so that only HTTPFactory can be used externally

  protected HTTPSession() throws HTTPException {}

  HTTPSession(String host, int port) throws HTTPException {
    init(new AuthScope(host, port, null, null), null);
  }

  HTTPSession(String uri) throws HTTPException {
    init(HTTPAuthUtil.uriToAuthScope(uri), uri);
  }

  HTTPSession(HttpHost httphost) throws HTTPException {
    init(HTTPAuthUtil.hostToAuthScope(httphost), null);
  }

  protected void init(AuthScope scope, String actualurl) throws HTTPException {
    assert (scope != null);
    if (actualurl != null) this.sessionURI = actualurl;
    else this.sessionURI = HTTPAuthUtil.authscopeToURI(scope).toString();
    this.scope = scope;
    this.scopeURI = HTTPAuthUtil.authscopeToURI(scope);
    this.cachevalid = false; // Force build on first use
    this.sessioncontext.setCookieStore(new BasicCookieStore());
    this.sessioncontext.setAttribute(HttpClientContext.AUTH_CACHE, new BasicAuthCache());
  }

  //////////////////////////////////////////////////
  // Interceptors: Only supported at global level

  protected static void setInterceptors(HttpClientBuilder cb) {
    for (HttpRequestInterceptor hrq : reqintercepts) {
      cb.addInterceptorLast(hrq);
    }
    for (HttpResponseInterceptor hrs : rspintercepts) {
      cb.addInterceptorLast(hrs);
    }
    // Add debug interceptors
    for (HttpRequestInterceptor hrq : dbgreq) {
      cb.addInterceptorFirst(hrq);
    }
    for (HttpResponseInterceptor hrs : dbgrsp) {
      cb.addInterceptorFirst(hrs);
    }
    // Hack: add Content-Encoding suppressor
    cb.addInterceptorFirst(CEKILL);
  }

  //////////////////////////////////////////////////
  // Accessor(s)

  public AuthScope getAuthScope() {
    return this.scope;
  }

  public String getSessionURI() {
    return this.sessionURI;
  }

  /**
   * Extract the sessionid cookie value
   *
   * @return sessionid string
   */
  public String getSessionID() {
    String sid = null;
    String jsid = null;
    List<Cookie> cookies = this.sessioncontext.getCookieStore().getCookies();
    for (Cookie cookie : cookies) {
      if (cookie.getName().equalsIgnoreCase("sessionid")) sid = cookie.getValue();
      if (cookie.getName().equalsIgnoreCase("jsessionid")) jsid = cookie.getValue();
    }
    return (sid == null ? jsid : sid);
  }

  public HTTPSession setUserAgent(String agent) {
    if (agent == null || agent.length() == 0) throw new IllegalArgumentException("null argument");
    localsettings.setParameter(Prop.USER_AGENT, agent);
    this.cachevalid = false;
    return this;
  }

  public HTTPSession setSoTimeout(int timeout) {
    if (timeout <= 0) throw new IllegalArgumentException("setSoTimeout");
    localsettings.setParameter(Prop.SO_TIMEOUT, timeout);
    this.cachevalid = false;
    return this;
  }

  public HTTPSession setConnectionTimeout(int timeout) {
    if (timeout <= 0) throw new IllegalArgumentException("setConnectionTImeout");
    localsettings.setParameter(Prop.CONN_TIMEOUT, timeout);
    localsettings.setParameter(Prop.CONN_REQ_TIMEOUT, timeout);
    this.cachevalid = false;
    return this;
  }

  /**
   * Set the max number of redirects to follow
   *
   * @param n
   */
  public HTTPSession setMaxRedirects(int n) {
    if (n < 0) // validate
    throw new IllegalArgumentException("setMaxRedirects");
    localsettings.setParameter(Prop.MAX_REDIRECTS, n);
    this.cachevalid = false;
    return this;
  }

  /** Enable/disable redirection following Default is yes. */
  public HTTPSession setFollowRedirects(boolean tf) {
    localsettings.setParameter(Prop.HANDLE_REDIRECTS, (Boolean) tf);
    this.cachevalid = false;
    return this;
  }

  /**
   * Should we use sessionid's?
   *
   * @param tf
   */
  public HTTPSession setUseSessions(boolean tf) {
    localsettings.setParameter(Prop.USESESSIONS, (Boolean) tf);
    this.cachevalid = false;
    return this;
  }

  public List<Cookie> getCookies() {
    if (this.sessioncontext == null) return null;
    List<Cookie> cookies = this.sessioncontext.getCookieStore().getCookies();
    return cookies;
  }

  public HTTPSession clearCookies() {
    BasicCookieStore cookies = (BasicCookieStore) this.sessioncontext.getCookieStore();
    if (cookies != null) cookies.clear();
    return this;
  }

  public HTTPSession clearCredentialsCache() {
    BasicAuthCache ac =
        (BasicAuthCache) this.sessioncontext.getAttribute(HttpClientContext.AUTH_CACHE);
    if (ac != null) ac.clear();
    return this;
  }

  // make package specific

  HttpClient getClient() {
    return this.cachedclient;
  }

  HttpClientContext getExecutionContext() {
    return this.sessioncontext;
  }

  public Object getSetting(String key) {
    return localsettings.get(key);
  }

  //////////////////////////////////////////////////

  /** Close the session. This implies closing any open methods. */
  public synchronized void close() {
    if (this.closed) return; // multiple calls ok
    while (methodList.size() > 0) {
      HTTPMethod m = methodList.get(0);
      m.close(); // forcibly close; will invoke removemethod().
    }
    closed = true;
  }

  synchronized HTTPSession addMethod(HTTPMethod m) {
    if (!methodList.contains(m)) methodList.add(m);
    return this;
  }

  synchronized HTTPSession removeMethod(HTTPMethod m) {
    methodList.remove(m);
    return this;
  }

  //////////////////////////////////////////////////
  // Authorization
  // per-session versions of the global accessors

  /**
   * @param provider
   * @throws HTTPException
   */
  public HTTPSession setCredentialsProvider(CredentialsProvider provider) throws HTTPException {
    setCredentialsProvider(provider, null);
    return this;
  }

  /**
   * This is the most general case
   *
   * @param provider the credentials provider
   * @param scope where to use it (i.e. on what host+port)
   * @throws HTTPException
   */
  public HTTPSession setCredentialsProvider(CredentialsProvider provider, AuthScope scope)
      throws HTTPException {
    mapcreds(provider, scope, localcreds);
    return this;
  }

  /**
   * It is convenient to be able to directly set the Credentials (not the provider) when those
   * credentials are fixed. Scope defaults to ANY
   *
   * @param creds
   * @throws HTTPException
   */
  public HTTPSession setCredentials(Credentials creds) throws HTTPException {
    setCredentials(creds, null);
    return this;
  }

  /**
   * It is convenient to be able to directly set the Credentials (not the provider) when those
   * credentials are fixed.
   *
   * @param creds
   * @param scope where to use it (i.e. on what host)
   * @throws HTTPException
   */
  public HTTPSession setCredentials(Credentials creds, AuthScope scope) throws HTTPException {
    assert (creds != null);
    if (scope == null) scope = AuthScope.ANY;
    CredentialsProvider provider = new BasicCredentialsProvider();
    provider.setCredentials(scope, creds);
    setCredentialsProvider(provider, scope);
    return this;
  }

  //////////////////////////////////////////////////
  // Execution (do an actual execution)
  // Package visible

  /**
   * Called primarily from HTTPMethod to do the bulk of the execution. Assumes HTTPMethod has
   * inserted its headers into request.
   *
   * @param method
   * @param methoduri
   * @param rb
   * @return Request+Response pair
   * @throws HTTPException
   */
  ExecState execute(HTTPMethod method, URI methoduri, RequestBuilder rb) throws HTTPException {
    this.execution = new ExecState();
    this.requestURI = methoduri;
    AuthScope methodscope = HTTPAuthUtil.uriToAuthScope(methoduri);
    AuthScope target = HTTPAuthUtil.authscopeUpgrade(this.scope, methodscope);
    synchronized (this) { // keep coverity happy
      // Merge Settings;
      Settings merged = HTTPUtil.merge(globalsettings, localsettings);
      if (!this.cachevalid) {
        RequestConfig.Builder rcb = RequestConfig.custom();
        this.cachedconfig = configureRequest(rcb, merged);
        HttpClientBuilder cb = HttpClients.custom();
        configClient(cb, merged);
        setAuthenticationAndProxy(cb);
        this.cachedclient = cb.build();
        rb.setConfig(this.cachedconfig);
        this.cachevalid = true;
      }
    }
    this.execution.request = (HttpRequestBase) rb.build();
    try {
      HttpHost targethost = HTTPAuthUtil.authscopeToHost(target);
      this.execution.response =
          cachedclient.execute(targethost, this.execution.request, this.sessioncontext);
    } catch (IOException ioe) {
      throw new HTTPException(ioe);
    }
    return this.execution;
  }

  protected RequestConfig configureRequest(RequestConfig.Builder rcb, Settings settings)
      throws HTTPException {
    // Configure the RequestConfig
    for (Prop key : settings.getKeys()) {
      Object value = settings.getParameter(key);
      boolean tf = (value instanceof Boolean ? (Boolean) value : false);
      if (key == Prop.ALLOW_CIRCULAR_REDIRECTS) {
        rcb.setCircularRedirectsAllowed(tf);
      } else if (key == Prop.HANDLE_REDIRECTS) {
        rcb.setRedirectsEnabled(tf);
        rcb.setRelativeRedirectsAllowed(tf);
      } else if (key == Prop.MAX_REDIRECTS) {
        rcb.setMaxRedirects((Integer) value);
      } else if (key == Prop.SO_TIMEOUT) {
        rcb.setSocketTimeout((Integer) value);
      } else if (key == Prop.CONN_TIMEOUT) {
        rcb.setConnectTimeout((Integer) value);
      } else if (key == Prop.CONN_REQ_TIMEOUT) {
        rcb.setConnectionRequestTimeout((Integer) value);
      } else if (key == Prop.MAX_THREADS) {
        connmgr.setMaxTotal((Integer) value);
        connmgr.setDefaultMaxPerRoute((Integer) value);
      } /* else ignore */
    }
    RequestConfig cfg = rcb.build();
    return cfg;
  }

  protected void configClient(HttpClientBuilder cb, Settings settings) throws HTTPException {
    cb.useSystemProperties();
    String agent = (String) settings.get(Prop.USER_AGENT);
    if (agent != null) cb.setUserAgent(agent);
    setInterceptors(cb);
    cb.setContentDecoderRegistry(contentDecoderMap);
  }

  /**
   * Handle authentication and Proxy'ing
   *
   * @param cb
   * @throws HTTPException
   */
  protected synchronized void setAuthenticationAndProxy(HttpClientBuilder cb) throws HTTPException {
    // First, setup the ssl factory
    cb.setSSLSocketFactory(globalsslfactory);

    // Second, Construct a CredentialsProvider that is
    // the union of the Proxy credentials plus
    // either the global local credentials; local overrides global
    // Unfortunately, we cannot either clone or extract the contents
    // of the client supplied provider, so we are forced (for now)
    // to modify the client supplied provider.

    // Look in the local authcreds for best scope match
    AuthScope bestMatch = HTTPAuthUtil.bestmatch(scope, localcreds.keySet());
    CredentialsProvider cp = null;
    if (bestMatch != null) {
      cp = localcreds.get(bestMatch);
    } else {
      bestMatch = HTTPAuthUtil.bestmatch(scope, globalcreds.keySet());
      if (bestMatch != null) cp = globalcreds.get(bestMatch);
    }
    // Build the proxy credentials and AuthScope
    Credentials proxycreds = null;
    AuthScope proxyscope = null;
    if (proxyuser != null && (httpproxy != null || httpsproxy != null)) {
      if (httpproxy != null) proxyscope = HTTPAuthUtil.hostToAuthScope(httpproxy);
      else // httpsproxy != null
      proxyscope = HTTPAuthUtil.hostToAuthScope(httpsproxy);
      proxycreds = new UsernamePasswordCredentials(proxyuser, proxypwd);
    }
    if (cp == null && proxycreds != null && proxyscope != null) {
      // If client provider is null and proxycreds are not,
      // then use proxycreds alone
      cp = new BasicCredentialsProvider();
      cp.setCredentials(proxyscope, proxycreds);
    } else if (cp != null && proxycreds != null && proxyscope != null) {
      // If client provider is not null and proxycreds are not,
      // then add proxycreds to the client provider
      cp.setCredentials(proxyscope, proxycreds);
    }
    if (cp != null) this.sessioncontext.setCredentialsProvider(cp);
  }

  //////////////////////////////////////////////////
  // Utilities Static and Per-Instance

  static String getCanonicalURL(String legalurl) {
    if (legalurl == null) return null;
    int index = legalurl.indexOf('?');
    if (index >= 0) legalurl = legalurl.substring(0, index);
    // remove any trailing extension
    // index = legalurl.lastIndexOf('.');
    // if(index >= 0) legalurl = legalurl.substring(0,index);
    return HTTPUtil.canonicalpath(legalurl);
  }

  static String getUrlAsString(String url) throws HTTPException {
    try (HTTPMethod m = HTTPFactory.Get(url); ) {
      int status = m.execute();
      String content = null;
      if (status == 200) {
        content = m.getResponseAsString();
      }
      return content;
    }
  }

  static int putUrlAsString(String content, String url) throws HTTPException {
    int status = 0;
    try {
      try (HTTPMethod m = HTTPFactory.Put(url)) {
        m.setRequestContent(
            new StringEntity(content, ContentType.create("application/text", "UTF-8")));
        status = m.execute();
      }
    } catch (UnsupportedCharsetException uce) {
      throw new HTTPException(uce);
    }
    return status;
  }

  static String getstorepath(String prefix) {
    String path = System.getProperty(prefix + "store");
    if (path != null) {
      path = path.trim();
      if (path.length() == 0) path = null;
    }
    return path;
  }

  static String getpassword(String prefix) {
    String password = System.getProperty(prefix + "storepassword");
    if (password != null) {
      password = password.trim();
      if (password.length() == 0) password = null;
    }
    return password;
  }

  static String cleanproperty(String property) {
    String value = System.getProperty(property);
    if (value != null) {
      value = value.trim();
      if (value.length() == 0) value = null;
    }
    return value;
  }

  static void mapcreds(
      CredentialsProvider provider,
      AuthScope scope,
      Map<AuthScope, CredentialsProvider> authcreds) {
    assert (provider != null);
    if (scope == null) scope = AuthScope.ANY;
    authcreds.put(scope, provider);
  }

  //////////////////////////////////////////////////
  // Testing support

  // Expose the state for testing purposes
  public synchronized boolean isClosed() {
    return this.closed;
  }

  public synchronized int getMethodcount() {
    return methodList.size();
  }

  public RequestConfig getDebugConfig() {
    return (this.cachevalid ? this.cachedconfig : null);
  }

  public Header[] getRequestHeaders() {
    if (!this.cachevalid) return null;
    Header[] hdrs = null;
    if (this.execution.request != null) hdrs = this.execution.request.getAllHeaders();
    return hdrs;
  }

  //////////////////////////////////////////////////
  // Debug interface

  // Provide a way to kill everything at the end of a Test

  // When testing, we need to be able to clean up
  // all existing sessions because JUnit can run all
  // test within a single jvm.
  static List<HTTPSession> sessionList = null; // List of all HTTPSession instances

  // only used when testing flag is set
  public static boolean TESTING = false; // set to true during testing, should be false otherwise

  protected static synchronized void kill() {
    if (sessionList != null) {
      for (HTTPSession session : sessionList) {
        session.close();
      }
      sessionList.clear();
      // Rebuild the connection manager
      connmgr.shutdown();
      connmgr = new PoolingHttpClientConnectionManager(sslregistry);
      setGlobalThreadCount(DFALTTHREADCOUNT);
    }
  }

  // If we are testing, then track the sessions for kill
  protected static synchronized void track(HTTPSession session) {
    if (sessionList == null) sessionList = new ArrayList<HTTPSession>();
    sessionList.add(session);
  }

  public static synchronized void debugHeaders(boolean print) {
    HTTPUtil.InterceptRequest rq = new HTTPUtil.InterceptRequest();
    HTTPUtil.InterceptResponse rs = new HTTPUtil.InterceptResponse();
    rq.setPrint(print);
    rs.setPrint(print);
    /* remove any previous */
    for (int i = reqintercepts.size() - 1; i >= 0; i--) {
      HttpRequestInterceptor hr = reqintercepts.get(i);
      if (hr instanceof HTTPUtil.InterceptCommon) reqintercepts.remove(i);
    }
    for (int i = rspintercepts.size() - 1; i >= 0; i--) {
      HttpResponseInterceptor hr = rspintercepts.get(i);
      if (hr instanceof HTTPUtil.InterceptCommon) rspintercepts.remove(i);
    }
    reqintercepts.add(rq);
    rspintercepts.add(rs);
  }

  public static void debugReset() {
    for (HttpRequestInterceptor hri : reqintercepts) {
      if (hri instanceof HTTPUtil.InterceptCommon) ((HTTPUtil.InterceptCommon) hri).clear();
    }
  }

  public static HTTPUtil.InterceptRequest debugRequestInterceptor() {
    for (HttpRequestInterceptor hri : reqintercepts) {
      if (hri instanceof HTTPUtil.InterceptRequest) return ((HTTPUtil.InterceptRequest) hri);
    }
    return null;
  }

  public static HTTPUtil.InterceptResponse debugResponseInterceptor() {
    for (HttpResponseInterceptor hri : rspintercepts) {
      if (hri instanceof HTTPUtil.InterceptResponse) return ((HTTPUtil.InterceptResponse) hri);
    }
    return null;
  }

  //////////////////////////////////////////////////
  // Deprecated, but here for back compatibility

  @Deprecated
  public static void setGlobalCredentialsProvider(AuthScope scope, CredentialsProvider provider)
      throws HTTPException {
    setGlobalCredentialsProvider(provider, scope);
  }

  @Deprecated
  public static void setGlobalCredentialsProvider(String url, CredentialsProvider provider)
      throws HTTPException {
    assert (url != null && provider != null);
    AuthScope scope = HTTPAuthUtil.uriToAuthScope(url);
    setGlobalCredentialsProvider(provider, scope);
  }

  @Deprecated
  public static void setGlobalCredentials(String url, Credentials creds) throws HTTPException {
    assert (url != null && creds != null);
    AuthScope scope = HTTPAuthUtil.uriToAuthScope(url);
    CredentialsProvider provider = new BasicCredentialsProvider();
    provider.setCredentials(scope, creds);
    setGlobalCredentialsProvider(provider, scope);
  }

  @Deprecated
  public void setCredentials(String url, Credentials creds) throws HTTPException {
    assert (creds != null);
    AuthScope scope = HTTPAuthUtil.uriToAuthScope(url);
    setCredentials(creds, scope);
  }

  @Deprecated
  public void setCredentialsProvider(String url, CredentialsProvider provider)
      throws HTTPException {
    assert (url != null && provider != null);
    AuthScope scope = HTTPAuthUtil.uriToAuthScope(url);
    setCredentialsProvider(provider, scope);
  }

  @Deprecated
  public void setCredentialsProvider(AuthScope scope, CredentialsProvider provider)
      throws HTTPException {
    setCredentialsProvider(provider, scope);
  }

  @Deprecated
  public static int getRetryCount() {
    throw new UnsupportedOperationException();
  }

  @Deprecated
  public static void setGlobalCompression() {
    setGlobalCompression("gzip,deflate");
  }

  @Deprecated
  public static void setGlobalProxy(String host, int port) {
    try {
      URL u = new URL("http", host, port, null);
      setGlobalProxy(u.toString());
    } catch (MalformedURLException e) {
      throw new IllegalArgumentException(e);
    }
  }

  @Deprecated
  public void setProxy(String host, int port) {
    setGlobalProxy(host, port);
  }

  @Deprecated
  public static void setGlobalCredentialsProvider(CredentialsProvider provider, String scheme)
      throws HTTPException {
    setGlobalCredentialsProvider(provider);
  }

  @Deprecated
  public static void setRetryCount(int count) {
    throw new UnsupportedOperationException();
  }

  @Deprecated
  public void clearState() {
    // no-op
  }

  @Deprecated
  public String getSessionURL() {
    return getSessionURI();
  }
}
/**
 * This class reads a NEXRAD level II data file. It can handle NCDC archives (ARCHIVE2), as well as
 * CRAFT/IDD compressed files (AR2V0001).
 *
 * <p>Adapted with permission from the Java Iras software developed by David Priegnitz at NSSL.
 *
 * <p>
 *
 * <p>Documentation on Archive Level II data format can be found at: <a
 * href="http://www.ncdc.noaa.gov/oa/radar/leveliidoc.html">
 * http://www.ncdc.noaa.gov/oa/radar/leveliidoc.html</a>
 *
 * @author caron
 * @author David Priegnitz
 */
public class Level2VolumeScan {

  // data formats
  public static final String ARCHIVE2 = "ARCHIVE2";
  public static final String AR2V0001 = "AR2V0001";
  public static final String AR2V0002 = "AR2V0002";
  public static final String AR2V0003 = "AR2V0003";
  public static final String AR2V0004 = "AR2V0004";
  public static final String AR2V0006 = "AR2V0006";

  private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Level2VolumeScan.class);
  ////////////////////////////////////////////////////////////////////////////////////

  // Data file
  RandomAccessFile raf;

  private String dataFormat = null; // ARCHIVE2 or AR2V0001
  private int title_julianDay; // days since 1/1/70
  private int title_msecs; // milliseconds since midnight
  private String stationId; // 4 letter station assigned by ICAO
  private NexradStationDB.Station station; // from lookup table, may be null
  private Level2Record first, last;

  private int vcp = 0; // Volume coverage pattern
  private int max_radials = 0;
  private int min_radials = Integer.MAX_VALUE;
  private int max_radials_hr = 0;
  private int min_radials_hr = Integer.MAX_VALUE;
  private int dopplarResolution;
  private boolean hasDifferentDopplarResolutions;
  private boolean hasHighResolutionData;

  private boolean hasHighResolutionREF;
  private boolean hasHighResolutionVEL;
  private boolean hasHighResolutionSW;
  private boolean hasHighResolutionZDR;
  private boolean hasHighResolutionPHI;
  private boolean hasHighResolutionRHO;
  // List of List of Level2Record
  private List<List<Level2Record>> reflectivityGroups, dopplerGroups;

  // private ArrayList reflectivityGroups, dopplerGroups;

  private List<List<Level2Record>> reflectivityHighResGroups;
  private List<List<Level2Record>> velocityHighResGroups;
  private List<List<Level2Record>> spectrumHighResGroups;
  private ArrayList diffReflectHighResGroups;
  private ArrayList diffPhaseHighResGroups;
  private ArrayList coefficientHighResGroups;

  private boolean showMessages = false,
      showData = false,
      debugScans = false,
      debugGroups2 = false,
      debugRadials = false,
      debugStats = false;
  private boolean runCheck = false;

  Level2VolumeScan(RandomAccessFile orgRaf, CancelTask cancelTask) throws IOException {
    this.raf = orgRaf;

    if (log.isDebugEnabled()) log.debug("Level2VolumeScan on " + raf.getLocation());

    raf.seek(0);
    raf.order(RandomAccessFile.BIG_ENDIAN);

    // volume scan header
    dataFormat = raf.readString(8);
    raf.skipBytes(1);
    String volumeNo = raf.readString(3);
    title_julianDay = raf.readInt(); // since 1/1/70
    title_msecs = raf.readInt();
    stationId = raf.readString(4).trim(); // only in AR2V0001
    if (log.isDebugEnabled()) log.debug(" dataFormat= " + dataFormat + " stationId= " + stationId);

    if (stationId.length() == 0) {
      // try to get it from the filename LOOK

      stationId = null;
    }

    // try to find the station
    if (stationId != null) {
      if (!stationId.startsWith("K") && stationId.length() == 4) {
        String _stationId = "K" + stationId;
        station = NexradStationDB.get(_stationId);
      } else station = NexradStationDB.get(stationId);
    }

    // see if we have to uncompress
    if (dataFormat.equals(AR2V0001)
        || dataFormat.equals(AR2V0003)
        || dataFormat.equals(AR2V0004)
        || dataFormat.equals(AR2V0006)) {
      raf.skipBytes(4);
      String BZ = raf.readString(2);
      if (BZ.equals("BZ")) {
        RandomAccessFile uraf;
        File uncompressedFile = DiskCache.getFileStandardPolicy(raf.getLocation() + ".uncompress");

        if (uncompressedFile.exists() && uncompressedFile.length() > 0) {
          // see if its locked - another thread is writing it
          FileInputStream fstream = null;
          FileLock lock = null;
          try {
            fstream = new FileInputStream(uncompressedFile);
            // lock = fstream.getChannel().lock(0, 1, true); // wait till its unlocked

            while (true) { // loop waiting for the lock
              try {
                lock = fstream.getChannel().lock(0, 1, true); // wait till its unlocked
                break;

              } catch (OverlappingFileLockException oe) { // not sure why lock() doesnt block
                try {
                  Thread.sleep(100); // msecs
                } catch (InterruptedException e1) {
                  break;
                }
              }
            }

          } finally {
            if (lock != null) lock.release();
            if (fstream != null) fstream.close();
          }
          uraf = new ucar.unidata.io.RandomAccessFile(uncompressedFile.getPath(), "r");

        } else {
          // nope, gotta uncompress it
          uraf = uncompress(raf, uncompressedFile.getPath());
          if (log.isDebugEnabled())
            log.debug("made uncompressed file= " + uncompressedFile.getPath());
        }

        // switch to uncompressed file
        raf.close();
        raf = uraf;
        raf.order(RandomAccessFile.BIG_ENDIAN);
      }

      raf.seek(Level2Record.FILE_HEADER_SIZE);
    }

    List<Level2Record> reflectivity = new ArrayList<Level2Record>();
    List<Level2Record> doppler = new ArrayList<Level2Record>();
    List<Level2Record> highReflectivity = new ArrayList<Level2Record>();
    List<Level2Record> highVelocity = new ArrayList<Level2Record>();
    List<Level2Record> highSpectrum = new ArrayList<Level2Record>();
    List<Level2Record> highDiffReflectivity = new ArrayList<Level2Record>();
    List<Level2Record> highDiffPhase = new ArrayList<Level2Record>();
    List<Level2Record> highCorreCoefficient = new ArrayList<Level2Record>();

    long message_offset31 = 0;
    int recno = 0;
    while (true) {

      Level2Record r = Level2Record.factory(raf, recno++, message_offset31);
      if (r == null) break;
      if (showData) r.dump2(System.out);
      // skip non-data messages
      if (r.message_type == 31) {
        message_offset31 = message_offset31 + (r.message_size * 2 + 12 - 2432);
      }

      if (r.message_type != 1 && r.message_type != 31) {
        if (showMessages) r.dumpMessage(System.out);
        continue;
      }

      //  if (showData) r.dump2(System.out);

      /* skip bad
      if (!r.checkOk()) {
        r.dump(System.out);
        continue;
      }   */

      // some global params
      if (vcp == 0) vcp = r.vcp;
      if (first == null) first = r;
      last = r;

      if (runCheck && !r.checkOk()) {
        continue;
      }

      if (r.hasReflectData) reflectivity.add(r);
      if (r.hasDopplerData) doppler.add(r);

      if (r.message_type == 31) {
        if (r.hasHighResREFData) highReflectivity.add(r);
        if (r.hasHighResVELData) highVelocity.add(r);
        if (r.hasHighResSWData) highSpectrum.add(r);
        if (r.hasHighResZDRData) highDiffReflectivity.add(r);
        if (r.hasHighResPHIData) highDiffPhase.add(r);
        if (r.hasHighResRHOData) highCorreCoefficient.add(r);
      }

      if ((cancelTask != null) && cancelTask.isCancel()) return;
    }
    if (debugRadials)
      System.out.println(" reflect ok= " + reflectivity.size() + " doppler ok= " + doppler.size());
    if (highReflectivity.size() == 0) {
      reflectivityGroups = sortScans("reflect", reflectivity, 600);
      dopplerGroups = sortScans("doppler", doppler, 600);
    }
    if (highReflectivity.size() > 0)
      reflectivityHighResGroups = sortScans("reflect_HR", highReflectivity, 720);
    if (highVelocity.size() > 0)
      velocityHighResGroups = sortScans("velocity_HR", highVelocity, 720);
    if (highSpectrum.size() > 0)
      spectrumHighResGroups = sortScans("spectrum_HR", highSpectrum, 720);
    if (highDiffReflectivity.size() > 0)
      diffReflectHighResGroups = sortScans("diffReflect_HR", highDiffReflectivity, 720);
    if (highDiffPhase.size() > 0)
      diffPhaseHighResGroups = sortScans("diffPhase_HR", highDiffPhase, 720);
    if (highCorreCoefficient.size() > 0)
      coefficientHighResGroups = sortScans("coefficient_HR", highCorreCoefficient, 720);
  }

  private ArrayList sortScans(String name, List<Level2Record> scans, int siz) {

    // now group by elevation_num
    Map<Short, List<Level2Record>> groupHash = new HashMap<Short, List<Level2Record>>(siz);
    for (Level2Record record : scans) {
      List<Level2Record> group = groupHash.get(record.elevation_num);
      if (null == group) {
        group = new ArrayList<Level2Record>();
        groupHash.put(record.elevation_num, group);
      }
      group.add(record);
    }

    // sort the groups by elevation_num
    ArrayList groups = new ArrayList(groupHash.values());
    Collections.sort(groups, new GroupComparator());

    // use the maximum radials
    for (int i = 0; i < groups.size(); i++) {
      ArrayList group = (ArrayList) groups.get(i);
      Level2Record r = (Level2Record) group.get(0);
      if (runCheck) testScan(name, group);
      if (r.getGateCount(REFLECTIVITY_HIGH) > 500 || r.getGateCount(VELOCITY_HIGH) > 1000) {
        max_radials_hr = Math.max(max_radials_hr, group.size());
        min_radials_hr = Math.min(min_radials_hr, group.size());
      } else {
        max_radials = Math.max(max_radials, group.size());
        min_radials = Math.min(min_radials, group.size());
      }
    }

    if (debugRadials) {
      System.out.println(name + " min_radials= " + min_radials + " max_radials= " + max_radials);
      for (int i = 0; i < groups.size(); i++) {
        ArrayList group = (ArrayList) groups.get(i);
        Level2Record lastr = (Level2Record) group.get(0);
        for (int j = 1; j < group.size(); j++) {
          Level2Record r = (Level2Record) group.get(j);
          if (r.data_msecs < lastr.data_msecs) System.out.println(" out of order " + j);
          lastr = r;
        }
      }
    }

    testVariable(name, groups);
    if (debugScans) System.out.println("-----------------------------");

    return groups;
  }

  public int getMaxRadials(int r) {
    if (r == 0) return max_radials;
    else if (r == 1) return max_radials_hr;
    else return 0;
  }

  public int getMinRadials(int r) {
    if (r == 0) return min_radials;
    else if (r == 1) return min_radials_hr;
    else return 0;
  }

  public int getDopplarResolution() {
    return dopplarResolution;
  }

  public boolean hasDifferentDopplarResolutions() {
    return hasDifferentDopplarResolutions;
  }

  public boolean hasHighResolutions(int dt) {
    if (dt == 0) return hasHighResolutionData;
    else if (dt == 1) return hasHighResolutionREF;
    else if (dt == 2) return hasHighResolutionVEL;
    else if (dt == 3) return hasHighResolutionSW;
    else if (dt == 4) return hasHighResolutionZDR;
    else if (dt == 5) return hasHighResolutionPHI;
    else if (dt == 6) return hasHighResolutionRHO;
    else return false;
  }

  // do we have same characteristics for all records in a scan?
  private int MAX_RADIAL = 721;
  private int[] radial = new int[MAX_RADIAL];

  private boolean testScan(String name, ArrayList group) {
    int datatype = name.equals("reflect") ? Level2Record.REFLECTIVITY : Level2Record.VELOCITY_HI;
    Level2Record first = (Level2Record) group.get(0);

    int n = group.size();
    if (debugScans) {
      boolean hasBoth = first.hasDopplerData && first.hasReflectData;
      System.out.println(
          name
              + " "
              + first
              + " has "
              + n
              + " radials resolution= "
              + first.resolution
              + " has both = "
              + hasBoth);
    }

    boolean ok = true;
    double sum = 0.0;
    double sum2 = 0.0;

    for (int i = 0; i < MAX_RADIAL; i++) radial[i] = 0;

    for (int i = 0; i < group.size(); i++) {
      Level2Record r = (Level2Record) group.get(i);

      /* this appears to be common - seems to be ok, we put missing values in
      if (r.getGateCount(datatype) != first.getGateCount(datatype)) {
        log.error(raf.getLocation()+" different number of gates ("+r.getGateCount(datatype)+
                "!="+first.getGateCount(datatype)+") in record "+name+ " "+r);
        ok = false;
      } */

      if (r.getGateSize(datatype) != first.getGateSize(datatype)) {
        log.warn(
            raf.getLocation()
                + " different gate size ("
                + r.getGateSize(datatype)
                + ") in record "
                + name
                + " "
                + r);
        ok = false;
      }
      if (r.getGateStart(datatype) != first.getGateStart(datatype)) {
        log.warn(
            raf.getLocation()
                + " different gate start ("
                + r.getGateStart(datatype)
                + ") in record "
                + name
                + " "
                + r);
        ok = false;
      }
      if (r.resolution != first.resolution) {
        log.warn(
            raf.getLocation()
                + " different resolution ("
                + r.resolution
                + ") in record "
                + name
                + " "
                + r);
        ok = false;
      }

      if ((r.radial_num < 0) || (r.radial_num >= MAX_RADIAL)) {
        log.info(
            raf.getLocation()
                + " radial out of range= "
                + r.radial_num
                + " in record "
                + name
                + " "
                + r);
        continue;
      }
      if (radial[r.radial_num] > 0) {
        log.warn(
            raf.getLocation()
                + " duplicate radial = "
                + r.radial_num
                + " in record "
                + name
                + " "
                + r);
        ok = false;
      }
      radial[r.radial_num] = r.recno + 1;

      sum += r.getElevation();
      sum2 += r.getElevation() * r.getElevation();
      // System.out.println("  elev="+r.getElevation()+" azi="+r.getAzimuth());
    }

    for (int i = 1; i < radial.length; i++) {
      if (0 == radial[i]) {
        if (n != (i - 1)) {
          log.warn(" missing radial(s)");
          ok = false;
        }
        break;
      }
    }

    double avg = sum / n;
    double sd = Math.sqrt((n * sum2 - sum * sum) / (n * (n - 1)));
    // System.out.println(" avg elev="+avg+" std.dev="+sd);

    return ok;
  }

  // do we have same characteristics for all groups in a variable?
  private boolean testVariable(String name, List scans) {
    int datatype = name.equals("reflect") ? Level2Record.REFLECTIVITY : Level2Record.VELOCITY_HI;
    if (scans.size() == 0) {
      log.warn(" No data for = " + name);
      return false;
    }

    boolean ok = true;
    List firstScan = (List) scans.get(0);
    Level2Record firstRecord = (Level2Record) firstScan.get(0);
    dopplarResolution = firstRecord.resolution;

    if (debugGroups2)
      System.out.println(
          "Group "
              + Level2Record.getDatatypeName(datatype)
              + " ngates = "
              + firstRecord.getGateCount(datatype)
              + " start = "
              + firstRecord.getGateStart(datatype)
              + " size = "
              + firstRecord.getGateSize(datatype));

    for (int i = 1; i < scans.size(); i++) {
      List scan = (List) scans.get(i);
      Level2Record record = (Level2Record) scan.get(0);

      if ((datatype == Level2Record.VELOCITY_HI)
          && (record.resolution
              != firstRecord.resolution)) { // do all velocity resolutions match ??
        log.warn(
            name
                + " scan "
                + i
                + " diff resolutions = "
                + record.resolution
                + ", "
                + firstRecord.resolution
                + " elev= "
                + record.elevation_num
                + " "
                + record.getElevation());
        ok = false;
        hasDifferentDopplarResolutions = true;
      }

      if (record.getGateSize(datatype) != firstRecord.getGateSize(datatype)) {
        log.warn(
            name
                + " scan "
                + i
                + " diff gates size = "
                + record.getGateSize(datatype)
                + " "
                + firstRecord.getGateSize(datatype)
                + " elev= "
                + record.elevation_num
                + " "
                + record.getElevation());
        ok = false;

      } else if (debugGroups2)
        System.out.println(
            " ok gates size elev= " + record.elevation_num + " " + record.getElevation());

      if (record.getGateStart(datatype) != firstRecord.getGateStart(datatype)) {
        log.warn(
            name
                + " scan "
                + i
                + " diff gates start = "
                + record.getGateStart(datatype)
                + " "
                + firstRecord.getGateStart(datatype)
                + " elev= "
                + record.elevation_num
                + " "
                + record.getElevation());
        ok = false;

      } else if (debugGroups2)
        System.out.println(
            " ok gates start elev= " + record.elevation_num + " " + record.getElevation());

      if (record.message_type == 31) {
        hasHighResolutionData = true;
        // each data type
        if (record.hasHighResREFData) hasHighResolutionREF = true;
        if (record.hasHighResVELData) hasHighResolutionVEL = true;
        if (record.hasHighResSWData) hasHighResolutionSW = true;
        if (record.hasHighResZDRData) hasHighResolutionZDR = true;
        if (record.hasHighResPHIData) hasHighResolutionPHI = true;
        if (record.hasHighResRHOData) hasHighResolutionRHO = true;
      }
    }

    return ok;
  }

  /**
   * Get Reflectivity Groups Groups are all the records for a variable and elevation_num;
   *
   * @return List of type List of type Level2Record
   */
  public List getReflectivityGroups() {
    return reflectivityGroups;
  }

  /**
   * Get Velocity Groups Groups are all the records for a variable and elevation_num;
   *
   * @return List of type List of type Level2Record
   */
  public List getVelocityGroups() {
    return dopplerGroups;
  }

  public List getHighResVelocityGroups() {
    return velocityHighResGroups;
  }

  public List getHighResReflectivityGroups() {
    return reflectivityHighResGroups;
  }

  public List getHighResSpectrumGroups() {
    return spectrumHighResGroups;
  }

  public List getHighResDiffReflectGroups() {
    return diffReflectHighResGroups;
  }

  public List getHighResDiffPhaseGroups() {
    return diffPhaseHighResGroups;
  }

  public List getHighResCoeffocientGroups() {
    return coefficientHighResGroups;
  }

  private class GroupComparator implements Comparator<List<Level2Record>> {

    public int compare(List<Level2Record> group1, List<Level2Record> group2) {
      Level2Record record1 = group1.get(0);
      Level2Record record2 = group2.get(0);

      // if (record1.elevation_num != record2.elevation_num)
      return record1.elevation_num - record2.elevation_num;
      // return record1.cut - record2.cut;
    }
  }

  /**
   * Get data format (ARCHIVE2, AR2V0001) for this file.
   *
   * @return data format (ARCHIVE2, AR2V0001) for this file.
   */
  public String getDataFormat() {
    return dataFormat;
  }

  /**
   * Get the starting Julian day for this volume
   *
   * @return days since 1/1/70.
   */
  public int getTitleJulianDays() {
    return title_julianDay;
  }

  /**
   * Get the starting time in seconds since midnight.
   *
   * @return Generation time of data in milliseconds of day past midnight (UTC).
   */
  public int getTitleMsecs() {
    return title_msecs;
  }

  /**
   * Get the Volume Coverage Pattern number for this data.
   *
   * @return VCP
   * @see Level2Record#getVolumeCoveragePatternName
   */
  public int getVCP() {
    return vcp;
  }

  /**
   * Get the 4-char station ID for this data
   *
   * @return station ID (may be null)
   */
  public String getStationId() {
    return stationId;
  }

  public String getStationName() {
    return station == null ? "unknown" : station.name;
  }

  public double getStationLatitude() {
    return station == null ? 0.0 : station.lat;
  }

  public double getStationLongitude() {
    return station == null ? 0.0 : station.lon;
  }

  public double getStationElevation() {
    return station == null ? 0.0 : station.elev;
  }

  public Date getStartDate() {
    return first.getDate();
  }

  public Date getEndDate() {
    return last.getDate();
  }

  /**
   * Write equivilent uncompressed version of the file.
   *
   * @param inputRaf file to uncompress
   * @param ufilename write to this file
   * @return raf of uncompressed file
   * @throws IOException on read error
   */
  private RandomAccessFile uncompress(RandomAccessFile inputRaf, String ufilename)
      throws IOException {
    RandomAccessFile outputRaf = new RandomAccessFile(ufilename, "rw");
    FileLock lock = null;

    while (true) { // loop waiting for the lock
      try {
        lock = outputRaf.getRandomAccessFile().getChannel().lock(0, 1, false);
        break;

      } catch (OverlappingFileLockException oe) { // not sure why lock() doesnt block
        try {
          Thread.sleep(100); // msecs
        } catch (InterruptedException e1) {
        }
      }
    }

    try {
      inputRaf.seek(0);
      byte[] header = new byte[Level2Record.FILE_HEADER_SIZE];
      inputRaf.read(header);
      outputRaf.write(header);

      boolean eof = false;
      int numCompBytes;
      byte[] ubuff = new byte[40000];
      byte[] obuff = new byte[40000];
      try {
        CBZip2InputStream cbzip2 = new CBZip2InputStream();
        while (!eof) {
          try {
            numCompBytes = inputRaf.readInt();
            if (numCompBytes == -1) {
              if (log.isDebugEnabled()) log.debug("  done: numCompBytes=-1 ");
              break;
            }
          } catch (EOFException ee) {
            log.warn("  got EOFException ");
            break; // assume this is ok
          }

          if (log.isDebugEnabled()) {
            log.debug(
                "reading compressed bytes "
                    + numCompBytes
                    + " input starts at "
                    + inputRaf.getFilePointer()
                    + "; output starts at "
                    + outputRaf.getFilePointer());
          }
          /*
           * For some stupid reason, the last block seems to
           * have the number of bytes negated.  So, we just
           * assume that any negative number (other than -1)
           * is the last block and go on our merry little way.
           */
          if (numCompBytes < 0) {
            if (log.isDebugEnabled()) log.debug("last block?" + numCompBytes);
            numCompBytes = -numCompBytes;
            eof = true;
          }
          byte[] buf = new byte[numCompBytes];
          inputRaf.readFully(buf);
          ByteArrayInputStream bis = new ByteArrayInputStream(buf, 2, numCompBytes - 2);

          // CBZip2InputStream cbzip2 = new CBZip2InputStream(bis);
          cbzip2.setStream(bis);
          int total = 0;
          int nread;
          /*
          while ((nread = cbzip2.read(ubuff)) != -1) {
            dout2.write(ubuff, 0, nread);
            total += nread;
          }
          */
          try {
            while ((nread = cbzip2.read(ubuff)) != -1) {
              if (total + nread > obuff.length) {
                byte[] temp = obuff;
                obuff = new byte[temp.length * 2];
                System.arraycopy(temp, 0, obuff, 0, temp.length);
              }
              System.arraycopy(ubuff, 0, obuff, total, nread);
              total += nread;
            }
            if (obuff.length >= 0) outputRaf.write(obuff, 0, total);
          } catch (BZip2ReadException ioe) {
            log.warn("Nexrad2IOSP.uncompress ", ioe);
          }
          float nrecords = (float) (total / 2432.0);
          if (log.isDebugEnabled())
            log.debug(
                "  unpacked "
                    + total
                    + " num bytes "
                    + nrecords
                    + " records; ouput ends at "
                    + outputRaf.getFilePointer());
        }

      } catch (Exception e) {
        if (outputRaf != null) outputRaf.close();
        outputRaf = null;

        // dont leave bad files around
        File ufile = new File(ufilename);
        if (ufile.exists()) {
          if (!ufile.delete())
            log.warn("failed to delete uncompressed file (IOException)" + ufilename);
        }

        if (e instanceof IOException) throw (IOException) e;
        else throw new RuntimeException(e);
      }

    } finally {
      if (null != outputRaf) outputRaf.flush();
      if (lock != null) lock.release();
    }

    return outputRaf;
  }

  // debugging

  static void bdiff(String filename) throws IOException {

    InputStream in1 = new FileInputStream(filename + ".tmp");
    InputStream in2 = new FileInputStream(filename + ".tmp2");

    int count = 0;
    int bad = 0;
    while (true) {
      int b1 = in1.read();
      int b2 = in2.read();
      if (b1 < 0) break;
      if (b2 < 0) break;

      if (b1 != b2) {
        System.out.println(count + " in1=" + b1 + " in2= " + b2);
        bad++;
        if (bad > 130) break;
      }
      count++;
    }
    System.out.println("total read = " + count);
  }

  // check if compressed file seems ok
  public static long testValid(String ufilename) throws IOException {
    boolean lookForHeader = false;

    // gotta make it
    RandomAccessFile raf = new RandomAccessFile(ufilename, "r");
    raf.order(RandomAccessFile.BIG_ENDIAN);
    raf.seek(0);
    byte[] b = new byte[8];
    raf.read(b);
    String test = new String(b);
    if (test.equals(Level2VolumeScan.ARCHIVE2) || test.equals(Level2VolumeScan.AR2V0001)) {
      System.out.println("--Good header= " + test);
      raf.seek(24);
    } else {
      System.out.println("--No header ");
      lookForHeader = true;
      raf.seek(0);
    }

    boolean eof = false;
    int numCompBytes;
    try {

      while (!eof) {

        if (lookForHeader) {
          raf.read(b);
          test = new String(b);
          if (test.equals(Level2VolumeScan.ARCHIVE2) || test.equals(Level2VolumeScan.AR2V0001)) {
            System.out.println("  found header= " + test);
            raf.skipBytes(16);
            lookForHeader = false;
          } else {
            raf.skipBytes(-8);
          }
        }

        try {
          numCompBytes = raf.readInt();
          if (numCompBytes == -1) {
            System.out.println("\n--done: numCompBytes=-1 ");
            break;
          }
        } catch (EOFException ee) {
          System.out.println("\n--got EOFException ");
          break; // assume this is ok
        }

        System.out.print(" " + numCompBytes + ",");
        if (numCompBytes < 0) {
          System.out.println("\n--last block " + numCompBytes);
          numCompBytes = -numCompBytes;
          if (!lookForHeader) eof = true;
        }

        raf.skipBytes(numCompBytes);
      }
    } catch (EOFException e) {
      e.printStackTrace();
    }

    return raf.getFilePointer();
  }

  /** test */
  public static void main2(String[] args) throws IOException {
    File testDir = new File("C:/data/bad/radar2/");

    File[] files = testDir.listFiles();
    for (int i = 0; i < files.length; i++) {
      File file = files[i];
      if (!file.getPath().endsWith(".ar2v")) continue;
      System.out.println(file.getPath() + " " + file.length());
      long pos = testValid(file.getPath());
      if (pos == file.length()) {
        System.out.println("OK");
        try {
          NetcdfFile.open(file.getPath());
        } catch (Throwable t) {
          System.out.println("ERROR=  " + t);
        }
      } else System.out.println("NOT pos=" + pos);

      System.out.println();
    }
  }

  public static void main(String args[]) throws IOException {
    NexradStationDB.init();

    RandomAccessFile raf =
        new RandomAccessFile(
            "/upc/share/testdata/radar/nexrad/level2/Level2_KFTG_20060818_1814.ar2v.uncompress.missingradials",
            "r");
    // RandomAccessFile raf = new
    // RandomAccessFile("R:/testdata2/radar/nexrad/level2/problem/KCCX_20060627_1701", "r");
    new Level2VolumeScan(raf, null);
  }
}
/** Handles the Ensemble coordinate dimension. Assumes GribGridRecord */
public class GridEnsembleCoord {
  private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GridEnsembleCoord.class);

  private List<EnsCoord> ensCoords;
  private int seq = 0;

  /**
   * Create a new GridEnsembleCoord with the list of records
   *
   * @param records records to use
   */
  GridEnsembleCoord(List<GridRecord> records) {
    Map<Integer, EnsCoord> map = new HashMap<Integer, EnsCoord>();

    for (GridRecord record : records) {
      GribGridRecord ggr = (GribGridRecord) record;
      int ensNumber = ggr.getPds().getPerturbationNumber();
      int ensType = ggr.getPds().getPerturbationType();
      int h = 1000 * ensNumber + ensType; // unique perturbation number and type
      map.put(h, new EnsCoord(ensNumber, ensType));
    }

    ensCoords = new ArrayList<EnsCoord>(map.values());
    Collections.sort(ensCoords);
  }

  private class EnsCoord implements Comparable<EnsCoord> {
    int number, type;

    private EnsCoord(int number, int type) {
      this.number = number;
      this.type = type;
    }

    @Override
    public int compareTo(EnsCoord o) {
      int r = number - o.number;
      return (r == 0) ? type - o.type : r;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      EnsCoord ensCoord = (EnsCoord) o;

      if (number != ensCoord.number) return false;
      if (type != ensCoord.type) return false;

      return true;
    }

    @Override
    public int hashCode() {
      return 1000 * number + type;
    }

    @Override
    public String toString() {
      return "number=" + number + ", type=" + type;
    }
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    GridEnsembleCoord that = (GridEnsembleCoord) o;
    return ensCoords.equals(that.ensCoords);
  }

  @Override
  public int hashCode() {
    return ensCoords.hashCode();
  }

  /**
   * Set the sequence number
   *
   * @param seq the sequence number
   */
  void setSequence(int seq) {
    this.seq = seq;
  }

  /**
   * Get the name
   *
   * @return the name
   */
  String getName() {
    return (seq == 0) ? "ens" : "ens" + seq;
  }

  /**
   * Add this as a dimension to a netCDF file
   *
   * @param ncfile the netCDF file
   * @param g the group in the file
   */
  void addDimensionsToNetcdfFile(NetcdfFile ncfile, Group g) {
    ncfile.addDimension(g, new Dimension(getName(), getNEnsembles(), true));
  }

  /**
   * Add this as a variable to the netCDF file
   *
   * @param ncfile the netCDF file
   * @param g the group in the file
   */
  void addToNetcdfFile(NetcdfFile ncfile, Group g) {
    Variable v = new Variable(ncfile, g, null, getName());
    v.setDimensions(v.getShortName());
    v.setDataType(DataType.STRING);
    v.addAttribute(new Attribute("long_name", "ensemble"));
    v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Ensemble.toString()));

    String[] data = new String[getNEnsembles()];

    for (int i = 0; i < getNEnsembles(); i++) {
      EnsCoord ec = ensCoords.get(i);
      data[i] = Grib2Tables.codeTable4_6(ec.type) + " " + ec.number;
    }
    Array dataArray = Array.factory(DataType.STRING, new int[] {getNEnsembles()}, data);
    v.setCachedData(dataArray, false);

    ncfile.addVariable(g, v);
  }

  /**
   * Get the index of a GridRecord
   *
   * @param ggr the grib record
   * @return the index or -1 if not found
   */
  int getIndex(GribGridRecord ggr) {
    int ensNumber = ggr.getPds().getPerturbationNumber();
    int ensType = ggr.getPds().getPerturbationType();

    int count = 0;
    for (EnsCoord coord : ensCoords) {
      if ((coord.number == ensNumber) && (coord.type == ensType)) return count;
      count++;
    }
    return -1;
  }

  /**
   * Get the number of Ensembles
   *
   * @return the number of Ensembles
   */
  int getNEnsembles() {
    return ensCoords.size();
  }
}
public class DashboardFilterHandler extends UIComponentHandlerFactoryElement {
  private static transient org.slf4j.Logger log =
      org.slf4j.LoggerFactory.getLogger(DashboardFilterHandler.class.getName());

  // Component JSPs
  protected String componentIncludeJSP;
  protected String componentIncludeJSPshow;
  protected String componentIncludeJSPedit;
  protected String componentIncludeJSPproperties;

  protected String componentIncludeJSPshort_show;
  protected String componentIncludeJSPshort_edit;
  protected String componentIncludeJSPshort_properties;

  public static final String I18N_PREFFIX = "dashboardFilter.";
  public static final String PARAM_VALUE = "value";
  public static final String PARAM_VALUE_MIN = "minValue";
  public static final String PARAM_VALUE_MAX = "maxValue";
  public static final String PARAM_CUSTOM_VALUE = "customValue";
  public static final String PARAM_LAST_HOUR = "lastHour";
  public static final String PARAM_LAST_12HOURS = "last12Hours";
  public static final String PARAM_TODAY = "today";
  public static final String PARAM_YESTERDAY = "yesterday";
  public static final String PARAM_LAST_7DAYS = "last7Days";
  public static final String PARAM_THIS_MONTH = "thisMonth";
  public static final String PARAM_LAST_MONTH = "lastMonth";
  public static final String PARAM_THIS_QUARTER = "thisQuarter";
  public static final String PARAM_LAST_QUARTER = "lastQuarter";
  public static final String PARAM_LAST_6MONTHS = "last12Months";
  public static final String PARAM_THIS_YEAR = "thisYear";
  public static final String PARAM_LAST_YEAR = "lastYear";
  public static final String PARAM_NULL_VALUE = "---";
  public static final String PARAM_VISIBLE = "visible";
  public static final String PARAM_DRILLDOWN_DISABLED = "drillDownDisabled";
  public static final String PARAM_SECTION = "section";
  public static final String PARAM_SHOW_REFRESH_BUTTON = "showRefreshButton";
  public static final String PARAM_SHOW_APPLY_BUTTON = "showApplyButton";
  public static final String PARAM_SHOW_CLEAR_BUTTON = "showClearButton";
  public static final String PARAM_SHOW_PROPERTY_NAMES = "showPropertyNames";
  public static final String PARAM_SHOW_SUBMIT_ON_CHANGE = "showSubmitOnChange";
  public static final String PARAM_SHOW_AUTO_REFRESH = "showAutoRefresh";
  public static final String PARAM_SHORT_MODE = "shortMode";
  public static final String PARAM_SHOW_LEGEND = "showLegend";

  protected DashboardHandler dashboardHandler;
  protected DashboardFilterRequestProcessor requestProcessor;

  protected String serializedProperties;

  // Component options.
  protected boolean isShowMode;
  protected boolean isEditMode;
  protected boolean isShortMode;
  protected boolean showPropertyNames;
  protected boolean showRefreshButton;
  protected boolean showApplyButton;
  protected boolean showClearButton;
  protected boolean showSubmitOnChange;
  protected boolean showLegend;
  protected boolean showAutoRefresh;

  // Handle component properties.
  protected List
      properties; // Properties selected in edit mode. Filter can be executed with these properties.
  protected List
      notAllowedProperties; // Properties that cannot be displayed because there is another property
                            // visible with same property identifier.
  protected Set filterPropertyErrors; // Errors when data input to filter cannot be parsed.
  protected boolean
      panelDuplicated; // Flag which indicate if a panel instance is duplicated in different
                       // sections.

  // Auto-Refresh control
  protected int autoRefreshTimeout;
  protected boolean refreshEnabled;

  public DashboardFilterHandler() {
    serializedProperties = null;
    isShowMode = true;
    isEditMode = false;
    isShortMode = true;
    properties = new ArrayList();
    filterPropertyErrors = new HashSet();
    notAllowedProperties = new ArrayList();
    showPropertyNames = true;
    showRefreshButton = true;
    showApplyButton = true;
    showClearButton = true;
    showSubmitOnChange = true;
    showLegend = true;
    showAutoRefresh = false;
    autoRefreshTimeout = 15;
    panelDuplicated = false;
  }

  // -------------- START GETTERS AND SETTERS --------------------- //

  public boolean isPanelDuplicated() {
    return panelDuplicated;
  }

  public void setPanelDuplicated(boolean panelDuplicated) {
    this.panelDuplicated = panelDuplicated;
  }

  public boolean isRefreshEnabled() {
    return refreshEnabled;
  }

  public void setRefreshEnabled(boolean refreshEnabled) {
    this.refreshEnabled = refreshEnabled;
  }

  public int getAutoRefreshTimeout() {
    return autoRefreshTimeout;
  }

  public void setAutoRefreshTimeout(int autoRefreshTimeout) {
    this.autoRefreshTimeout = autoRefreshTimeout;
  }

  public boolean isShowAutoRefresh() {
    return showAutoRefresh;
  }

  public void setShowAutoRefresh(boolean showAutoRefresh) {
    this.showAutoRefresh = showAutoRefresh;
  }

  public List getNotAllowedProperties() {
    return notAllowedProperties;
  }

  public void setNotAllowedProperties(List notAllowedProperties) {
    this.notAllowedProperties = notAllowedProperties;
  }

  public boolean isShowSubmitOnChange() {
    return showSubmitOnChange;
  }

  public void setShowSubmitOnChange(boolean showSubmitOnChange) {
    this.showSubmitOnChange = showSubmitOnChange;
  }

  public boolean isShowLegend() {
    return showLegend;
  }

  public void setShowLegend(boolean showLegend) {
    this.showLegend = showLegend;
  }

  public DashboardFilterRequestProcessor getRequestProcessor() {
    return requestProcessor;
  }

  public void setRequestProcessor(DashboardFilterRequestProcessor requestProcessor) {
    this.requestProcessor = requestProcessor;
  }

  public boolean isShowApplyButton() {
    return showApplyButton;
  }

  public void setShowApplyButton(boolean showApplyButton) {
    this.showApplyButton = showApplyButton;
  }

  public boolean isShowClearButton() {
    return showClearButton;
  }

  public void setShowClearButton(boolean showClearButton) {
    this.showClearButton = showClearButton;
  }

  public boolean isShowRefreshButton() {
    return showRefreshButton;
  }

  public void setShowRefreshButton(boolean showRefreshButton) {
    this.showRefreshButton = showRefreshButton;
  }

  public boolean isShowPropertyNames() {
    return showPropertyNames;
  }

  public void setShowPropertyNames(boolean showPropertyNames) {
    this.showPropertyNames = showPropertyNames;
  }

  public List getProperties() {
    return properties;
  }

  public void setProperties(List properties) {
    this.properties = properties;
  }

  public DashboardHandler getDashboardHandler() {
    return dashboardHandler;
  }

  public void setDashboardHandler(DashboardHandler dashboardHandler) {
    this.dashboardHandler = dashboardHandler;
  }

  public boolean isShortMode() {
    return isShortMode;
  }

  public void setShortMode(boolean shortMode) {
    isShortMode = shortMode;
  }

  public boolean isEditMode() {
    return isEditMode;
  }

  public boolean isShowMode() {
    return isShowMode;
  }

  public String getComponentIncludeJSPshort_edit() {
    return componentIncludeJSPshort_edit;
  }

  public void setComponentIncludeJSPshort_edit(String componentIncludeJSPshort_edit) {
    this.componentIncludeJSPshort_edit = componentIncludeJSPshort_edit;
  }

  public String getComponentIncludeJSPshort_properties() {
    return componentIncludeJSPshort_properties;
  }

  public void setComponentIncludeJSPshort_properties(String componentIncludeJSPshort_properties) {
    this.componentIncludeJSPshort_properties = componentIncludeJSPshort_properties;
  }

  public String getComponentIncludeJSPshort_show() {
    return componentIncludeJSPshort_show;
  }

  public void setComponentIncludeJSPshort_show(String componentIncludeJSPshort_show) {
    this.componentIncludeJSPshort_show = componentIncludeJSPshort_show;
  }

  public String getComponentIncludeJSPproperties() {
    return componentIncludeJSPproperties;
  }

  public String getJSPForShowMode() {
    if (isShortMode) return componentIncludeJSPshort_show;
    else return componentIncludeJSPshow;
  }

  public String getJSPForEditMode() {
    return componentIncludeJSPedit;
  }

  public String getJSPForProperties() {
    if (!isShortMode) return componentIncludeJSPproperties;
    else return componentIncludeJSPshort_properties;
  }

  public String getJSP() {
    if (isEditMode) return getJSPForEditMode();
    return getJSPForShowMode();
  }

  public void setComponentIncludeJSPproperties(String componentIncludeJSPproperties) {
    this.componentIncludeJSPproperties = componentIncludeJSPproperties;
  }

  public String getComponentIncludeJSPedit() {
    return componentIncludeJSPedit;
  }

  public void setComponentIncludeJSPedit(String componentIncludeJSPedit) {
    this.componentIncludeJSPedit = componentIncludeJSPedit;
  }

  public String getComponentIncludeJSPshow() {
    return componentIncludeJSPshow;
  }

  public void setComponentIncludeJSPshow(String componentIncludeJSPshow) {
    this.componentIncludeJSPshow = componentIncludeJSPshow;
  }

  public String getComponentIncludeJSP() {
    return getJSP();
  }

  public void setComponentIncludeJSP(String componentIncludeJSP) {
    this.componentIncludeJSP = componentIncludeJSP;
  }

  // -------------- END GETTERS AND SETTERS --------------------- //

  // -------------- START HANDLING METHODS ---------------------- //

  public static DashboardFilterHandler lookup(String code) {
    DashboardFilterHandler handler = null;

    // Get handler for code;
    if (!StringUtils.isBlank(code))
      handler =
          (DashboardFilterHandler)
              Factory.lookup("org.jboss.dashboard.ui.components.DashboardFilterHandler_" + code);
    if (handler == null)
      handler =
          (DashboardFilterHandler)
              Factory.lookup("org.jboss.dashboard.ui.components.DashboardFilterHandler");
    return handler;
  }

  public String getComponentPath() {
    return getComponentName();
  }

  public Dashboard getDashboard() {
    return dashboardHandler.getCurrentDashboard();
  }

  public DashboardFilter getFilter() {
    return getDashboard().getDashboardFilter();
  }

  public void enableEditMode() {
    isEditMode = true;
    isShowMode = false;
  }

  public void enableShowMode() {
    isShowMode = true;
    isEditMode = false;

    // Enable or disable autorefresh on show mode.
    if (showAutoRefresh) setRefreshEnabled(true);
    else setRefreshEnabled(false);
  }

  public String getSerializedProperties() {
    return serializedProperties;
  }

  public void setSerializedProperties(String serializedProperties) {
    this.serializedProperties = serializedProperties;
  }

  public DashboardFilterProperty getDashboardFilterPropertyForCurrentFilter(
      String dataProviderCode, String propertyId) {
    Iterator it = properties.iterator();
    while (it.hasNext()) {
      DashboardFilterProperty dashboardFilterProperty = (DashboardFilterProperty) it.next();
      if (dataProviderCode.equals(dashboardFilterProperty.getDataProviderCode())
          && propertyId.equals(dashboardFilterProperty.getPropertyId()))
        return dashboardFilterProperty;
    }
    return null;
  }

  public DashboardFilterProperty getDashboardFilterProperty(String propertyId) {
    Iterator it = properties.iterator();
    while (it.hasNext()) {
      DashboardFilterProperty dashboardFilterProperty = (DashboardFilterProperty) it.next();
      if (propertyId.equals(dashboardFilterProperty.getPropertyId()))
        return dashboardFilterProperty;
    }
    return null;
  }

  // Calls dashboard filter to get static  properties but keep properties instance configuration. Is
  // propety is found on this instance properties List thsi instance is returned.
  public DashboardFilterProperty[] getStaticPropertiesForCurrentFilter() {
    DashboardFilterProperty[] staticProperties = getFilter().getStaticProperties();
    if (staticProperties == null) return null;
    DashboardFilterProperty[] results = new DashboardFilterProperty[staticProperties.length];
    for (int i = 0; i < staticProperties.length; i++) {
      DashboardFilterProperty staticProperty = staticProperties[i];
      DashboardFilterProperty property =
          getDashboardFilterPropertyForCurrentFilter(
              staticProperty.getDataProviderCode(), staticProperty.getPropertyId());
      if (property != null) results[i] = property;
      else results[i] = staticProperty;
    }
    return results;
  }

  public DashboardFilterProperty[] getAllPropertiesForCurrentFilter() {
    List results = new ArrayList();
    try {
      // Static properties.
      DashboardFilterProperty[] staticProps = getStaticPropertiesForCurrentFilter();
      if (staticProps != null) results.addAll(Arrays.asList(staticProps));

      // Dynamic properties.
      Iterator it = getDashboard().getDataProviders().iterator();
      while (it.hasNext()) {
        DataProvider dataProvider = (DataProvider) it.next();
        DataProperty[] allProperties = dataProvider.getDataSet().getProperties();
        for (int i = 0; i < allProperties.length; i++) {
          DataProperty property = allProperties[i];
          DashboardFilterProperty prop =
              getDashboardFilterPropertyForCurrentFilter(
                  dataProvider.getCode(), property.getPropertyId());
          if (prop == null)
            prop =
                new DashboardFilterProperty(
                    dataProvider.getCode(), property.getPropertyId(), getFilter(), null, false);
          results.add(prop);
        }
      }
    } catch (Exception e) {
      log.error("Cannot get data provider results.", e);
    }
    return (DashboardFilterProperty[]) results.toArray(new DashboardFilterProperty[results.size()]);
  }

  public DashboardFilterProperty[] getBeingFilteredProperties() {
    List results = new ArrayList();
    Iterator it = properties.iterator();
    while (it.hasNext()) {
      DashboardFilterProperty dashboardFilterProperty = (DashboardFilterProperty) it.next();
      if (dashboardFilterProperty.isBeingFiltered()) results.add(dashboardFilterProperty);
    }

    return (DashboardFilterProperty[]) results.toArray(new DashboardFilterProperty[results.size()]);
  }

  public List<DashboardFilterProperty> getVisibleProperties() {
    List<DashboardFilterProperty> results = new ArrayList<DashboardFilterProperty>();
    Iterator it = properties.iterator();
    while (it.hasNext()) {
      DashboardFilterProperty dashboardFilterProperty = (DashboardFilterProperty) it.next();
      if (dashboardFilterProperty.isVisible()) results.add(dashboardFilterProperty);
    }
    return results;
  }

  public boolean hasError(String propertyId) {
    return filterPropertyErrors.contains(propertyId);
  }

  // --------------- END HANDLING METHODS ------------------------ //

  // --------------- START ACTIONS ------------------------------- //

  public void actionStore(CommandRequest request) {
    Map parameters = request.getRequestObject().getParameterMap();

    // Initialize parameters and properties to default.
    showPropertyNames = false;
    showRefreshButton = false;
    showApplyButton = false;
    showClearButton = false;
    showSubmitOnChange = false;
    isShortMode = false;
    showLegend = false;
    showAutoRefresh = false;
    properties.clear();
    notAllowedProperties.clear();

    // Component options.
    if (parameters.containsKey(PARAM_SHOW_REFRESH_BUTTON)) showRefreshButton = true;
    if (parameters.containsKey(PARAM_SHOW_PROPERTY_NAMES)) showPropertyNames = true;
    if (parameters.containsKey(PARAM_SHOW_CLEAR_BUTTON)) showClearButton = true;
    if (parameters.containsKey(PARAM_SHOW_APPLY_BUTTON)) showApplyButton = true;
    if (parameters.containsKey(PARAM_SHOW_SUBMIT_ON_CHANGE)) showSubmitOnChange = true;
    if (parameters.containsKey(PARAM_SHORT_MODE)) isShortMode = true;
    if (parameters.containsKey(PARAM_SHOW_LEGEND)) showLegend = true;
    if (parameters.containsKey(PARAM_SHOW_AUTO_REFRESH)) showAutoRefresh = true;

    // Component properties.
    DashboardFilterProperty[] allProperties = getAllPropertiesForCurrentFilter();
    for (int i = 0; i < allProperties.length; i++) {
      DashboardFilterProperty property = allProperties[i];
      String dataProviderCode = property.getDataProviderCode();
      String propertyId = property.getPropertyId();

      String visibleParamKey =
          new StringBuffer()
              .append(PARAM_VISIBLE)
              .append("/")
              .append(dataProviderCode)
              .append("/")
              .append(propertyId)
              .toString();
      String drillDownParamKey =
          new StringBuffer()
              .append(PARAM_SECTION)
              .append("/")
              .append(dataProviderCode)
              .append("/")
              .append(propertyId)
              .toString();
      boolean isVisible = parameters.containsKey(visibleParamKey);
      Long sectionId = null;
      if (parameters.containsKey(drillDownParamKey)) {
        String sectionIdStr = ((String[]) parameters.get(drillDownParamKey))[0];
        if (!PARAM_DRILLDOWN_DISABLED.equals(sectionIdStr)) sectionId = Long.decode(sectionIdStr);
      }

      if (!isVisible && sectionId == null) continue;

      // Property must be added?
      DashboardFilterProperty prop =
          getDashboardFilterPropertyForCurrentFilter(dataProviderCode, propertyId);
      if (prop == null)
        prop = new DashboardFilterProperty(dataProviderCode, propertyId, getFilter(), null, false);

      // Check if another property with same identifier.
      if (getDashboardFilterProperty(propertyId) != null) {
        // Another property with same id is already set to the filter.
        // Filter cannot use two properties with same property id., so show warning.
        notAllowedProperties.add(prop);
        continue;
      }

      // Add property to this component.
      properties.add(prop);

      // Set property parameters
      prop.setBeignFiltered(false);
      prop.setVisible(isVisible);
      prop.setSectionId(sectionId);
    }
  }

  public CommandResponse actionFilter(CommandRequest request) {
    try {
      // Init attributes for start applying the filter.
      filterPropertyErrors.clear();

      // Parse parameters and set the filter.
      Iterator visiblePropertiesIt = properties.iterator();
      while (visiblePropertiesIt.hasNext()) {
        DashboardFilterProperty dashboardFilterProperty =
            (DashboardFilterProperty) visiblePropertiesIt.next();

        // Is property already in the dashboard filter?. Then is not possible to filter by this
        // property, it's already added to dashboard filter.
        if (dashboardFilterProperty.isBeingFiltered()) continue;
        if (!dashboardFilterProperty.isPropertyAlive()) {
          log.warn(
              "Trying to filter by "
                  + dashboardFilterProperty.getPropertyId()
                  + ". This property is not in any dataset.");
          continue;
        }
        if (!dashboardFilterProperty.isVisible()) continue;

        Object[] result;
        try {
          result =
              requestProcessor.parseDashboardProperty(
                  request.getRequestObject(), dashboardFilterProperty);
        } catch (Exception e) {
          log.error("Error parsing property " + dashboardFilterProperty.getPropertyId() + ".", e);
          continue;
        }

        if (result.length != 3) {
          log.error(
              "Error parsing property: '"
                  + dashboardFilterProperty.getPropertyId()
                  + "' for dataProvider: '"
                  + dashboardFilterProperty.getDataProviderCode()
                  + "'");
          continue;
        }

        Collection allowedValues = (Collection) result[0];
        Object minValue = result[1];
        Object maxValue = result[2];
        if (allowedValues == null && minValue == null && maxValue == null) continue;

        // Set the filter with this property.
        Dashboard currentDashboard = DashboardHandler.lookup().getCurrentDashboard();
        if (currentDashboard.filter(
            dashboardFilterProperty.getPropertyId(),
            minValue,
            true,
            maxValue,
            true,
            allowedValues,
            FilterByCriteria.ALLOW_ANY)) {
          return new ShowCurrentScreenResponse();
        }
      }
    } catch (Exception e) {
      log.error("Error trying to filter properties for dashboard", e);
    }
    return null;
  }

  public void actionRefresh(CommandRequest request) {
    try {
      String timeOutValue = request.getRequestObject().getParameter("refreshTimeOut");
      if (!StringUtils.isBlank(timeOutValue)) {
        try {
          autoRefreshTimeout = Integer.decode(timeOutValue).intValue();
        } catch (NumberFormatException e) {
          log.warn("Cannot parse auto refresh value as a number.");
        }
      }
      getDashboard().refresh();
    } catch (Exception e) {
      log.error("Cannot refresh dashboard.", e);
    }
  }

  public CommandResponse actionClear(CommandRequest request) {
    try {
      Dashboard dashboard = getDashboard();
      if (dashboard.unfilter()) {
        return new ShowCurrentScreenResponse();
      }
    } catch (Exception e) {
      log.error("Cannot refresh dashboard.", e);
    }
    return null;
  }

  public CommandResponse actionDeleteFilteredProperty(CommandRequest request) {
    String propertyToDelete = request.getRequestObject().getParameter("filteredPropertyToDelete");
    if (propertyToDelete == null || propertyToDelete.trim().length() == 0) return null;

    try {
      Dashboard dashboard = getDashboard();
      if (dashboard.unfilter(propertyToDelete)) {
        return new ShowCurrentScreenResponse();
      }
    } catch (Exception e) {
      log.error("Cannot remove filter property.", e);
    }
    return null;
  }

  public void actionPlay(CommandRequest request) {
    setRefreshEnabled(true);
  }

  public void actionStop(CommandRequest request) {
    setRefreshEnabled(false);
  }

  // --------- END ACTIONS ---------------------------------- //

  // --------- SERIALIZATION / DESERIALIZATION -------------- //

  public String serializeComponentData() throws Exception {
    // Serialize visible properties and options.
    StringWriter sw = new StringWriter();
    PrintWriter out = new PrintWriter(sw);
    int indent = 0;
    printIndent(out, indent);
    out.println("<dashboard_filter>");
    Iterator it = properties.iterator();
    while (it.hasNext()) {
      DashboardFilterProperty dashboardFilterProperty = (DashboardFilterProperty) it.next();
      printIndent(out, indent + 1);
      out.println(
          "<property id=\""
              + StringEscapeUtils.escapeXml(dashboardFilterProperty.getPropertyId())
              + "\" providerCode =\""
              + StringEscapeUtils.escapeXml(dashboardFilterProperty.getDataProviderCode())
              + "\">");
      printIndent(out, indent + 2);
      out.println("<visible>" + dashboardFilterProperty.isVisible() + "</visible>");
      if (dashboardFilterProperty.getSectionId() != null) {
        printIndent(out, indent + 2);
        out.println("<section>" + dashboardFilterProperty.getSectionId() + "</section>");
      }
      printIndent(out, indent + 1);
      out.println("</property>");
    }

    // Serialize options.
    printIndent(out, indent + 1);
    out.println("<options>");
    printIndent(out, indent + 2);
    out.println("<shortViewMode>" + isShortMode + "</shortViewMode>");
    printIndent(out, indent + 2);
    out.println("<showLegend>" + showLegend + "</showLegend>");
    printIndent(out, indent + 2);
    out.println("<showRefreshButton>" + showRefreshButton + "</showRefreshButton>");
    printIndent(out, indent + 2);
    out.println("<showApplyhButton>" + showApplyButton + "</showApplyhButton>");
    printIndent(out, indent + 2);
    out.println("<showClearButton>" + showClearButton + "</showClearButton>");
    printIndent(out, indent + 2);
    out.println("<showPropertyNames>" + showPropertyNames + "</showPropertyNames>");
    printIndent(out, indent + 2);
    out.println("<showSubmitOnChange>" + showSubmitOnChange + "</showSubmitOnChange>");
    printIndent(out, indent + 1);
    out.println("<showAutoRefresh>" + showAutoRefresh + "</showAutoRefresh>");
    printIndent(out, indent + 1);
    out.println("</options>");
    printIndent(out, indent);

    out.println("</dashboard_filter>");
    serializedProperties = sw.toString();
    return sw.toString();
  }

  protected void printIndent(PrintWriter out, int indent) {
    for (int i = 0; i < indent; i++) {
      out.print("  ");
    }
  }

  // Return if is needed to serialize and save properties after this call because properties that
  // does not exist on current filter or data providers must be deleted from persistence.
  // return: must clear serialized trash properties after deserialize process saving this data.
  public boolean deserializeComponentData(String serializedData) throws Exception {
    // Load options and visible properties
    if (serializedData == null || serializedData.trim().length() == 0) {
      log.info("No data to deserialize.");
      return false;
    }

    DOMParser parser = new DOMParser();
    parser.parse(new InputSource(new StringReader(serializedData)));
    Document doc = parser.getDocument();
    NodeList nodes = doc.getElementsByTagName("dashboard_filter");
    if (nodes.getLength() > 1) {
      log.error("Each dashboard filter component just can parse one <dashboard_filter>");
      return false;
    }
    if (nodes.getLength() == 0) {
      log.info("No data to deserialize.");
      return false;
    }

    boolean needsToSerializeAfter = false;
    serializedProperties = serializedData;
    Node rootNode = nodes.item(0);
    nodes = rootNode.getChildNodes();
    for (int x = 0; x < nodes.getLength(); x++) {
      Node node = nodes.item(x);
      if (node.getNodeName().equals("property")) {
        // Parse visible properties.
        String dataProviderCode = node.getAttributes().getNamedItem("providerCode").getNodeValue();
        String propertyId = node.getAttributes().getNamedItem("id").getNodeValue();
        String sectionId = null;
        boolean visible = false;
        NodeList subnodes = node.getChildNodes();
        for (int i = 0; i < subnodes.getLength(); i++) {
          Node subnode = subnodes.item(i);
          if (subnode.getNodeName().equals("section")) {
            sectionId = subnode.getFirstChild().getNodeValue();
          }
          if (subnode.getNodeName().equals("visible")) {
            visible = Boolean.valueOf(subnode.getFirstChild().getNodeValue()).booleanValue();
          }
        }
        Long lSectionId = sectionId != null ? Long.decode(sectionId) : null;
        DashboardFilterProperty filterProp =
            new DashboardFilterProperty(
                dataProviderCode, propertyId, getFilter(), lSectionId, true);
        filterProp.setVisible(visible);
        if (filterProp.isPropertyAlive()) properties.add(filterProp);
        else needsToSerializeAfter = true;
      } else if (node.getNodeName().equals("options")) {
        // Parse component options.
        NodeList options = node.getChildNodes();
        String showRefreshButton = null;
        String showPropertyNames = null;
        String showClearButton = null;
        String showApplyButton = null;
        String showSubmitOnChange = null;
        String showShortViewMode = null;
        String showLegend = null;
        String showAutoRefresh = null;
        for (int i = 0; i < options.getLength(); i++) {
          Node option = options.item(i);
          if (option.getNodeName().equals("showRefreshButton"))
            showRefreshButton = option.getFirstChild().getNodeValue();
          if (option.getNodeName().equals("showPropertyNames"))
            showPropertyNames = option.getFirstChild().getNodeValue();
          if (option.getNodeName().equals("showClearButton"))
            showClearButton = option.getFirstChild().getNodeValue();
          if (option.getNodeName().equals("showApplyhButton"))
            showApplyButton = option.getFirstChild().getNodeValue();
          if (option.getNodeName().equals("showSubmitOnChange"))
            showSubmitOnChange = option.getFirstChild().getNodeValue();
          if (option.getNodeName().equals("shortViewMode"))
            showShortViewMode = option.getFirstChild().getNodeValue();
          if (option.getNodeName().equals("showLegend"))
            showLegend = option.getFirstChild().getNodeValue();
          if (option.getNodeName().equals("showAutoRefresh"))
            showAutoRefresh = option.getFirstChild().getNodeValue();
        }
        this.showPropertyNames = Boolean.valueOf(showPropertyNames).booleanValue();
        this.showRefreshButton = Boolean.valueOf(showRefreshButton).booleanValue();
        this.showApplyButton = Boolean.valueOf(showApplyButton).booleanValue();
        this.showClearButton = Boolean.valueOf(showClearButton).booleanValue();
        this.showSubmitOnChange = Boolean.valueOf(showSubmitOnChange).booleanValue();
        this.isShortMode = Boolean.valueOf(showShortViewMode).booleanValue();
        this.showLegend = Boolean.valueOf(showLegend).booleanValue();
        this.showAutoRefresh = Boolean.valueOf(showAutoRefresh).booleanValue();

        // Enable auto-refresh if necessary on start.
        if (this.showAutoRefresh) setRefreshEnabled(true);
      }
    }
    return needsToSerializeAfter;
  }

  public void beforeRenderComponent() {
    super.beforeRenderComponent();

    // Initialize the dashboard (loads all its kpi panels)
    DashboardHandler.lookup().getCurrentDashboard();

    // Get the filter.
    DashboardFilter filter = getFilter();

    // Check all visible properties exist.
    Iterator props = properties.iterator();
    while (props.hasNext()) {
      DashboardFilterProperty dashboardFilterProperty = (DashboardFilterProperty) props.next();
      if (!dashboardFilterProperty.isPropertyAlive()) props.remove();
    }

    // Check if filtered properties for this filter component are already in dashboard filter.
    DashboardFilterProperty[] beingFilteredProps = getBeingFilteredProperties();
    for (int i = 0; i < beingFilteredProps.length; i++) {
      List dfProperties = Arrays.asList(filter.getPropertyIds());
      DashboardFilterProperty beignFilteredProp = beingFilteredProps[i];
      if (!dfProperties.contains(beignFilteredProp.getPropertyId()))
        beignFilteredProp.setBeignFiltered(false);
    }

    // Check filtered properties and hide from available filter properties (set property not
    // visible)
    String[] propIds = filter.getPropertyIds();
    for (int i = 0; i < propIds.length; i++) {
      String propId = propIds[i];
      DashboardFilterProperty prop = getDashboardFilterProperty(propId);
      if (prop == null) {
        DashboardFilterProperty parentProperty = filter.getPropertyInParentDashboards(propId);
        if (parentProperty != null) {
          prop =
              new DashboardFilterProperty(
                  parentProperty.getDataProviderCode(), propId, getFilter(), null, true);
          properties.add(prop);
        }
      } else {
        prop.setBeignFiltered(true);
      }
    }
  }
}
/**
 * Helper class for using the netcdf-3 record dimension.
 *
 * @author caron
 * @since Feb 29, 2008
 */
public class RecordDatasetHelper {
  private static org.slf4j.Logger log =
      org.slf4j.LoggerFactory.getLogger(RecordDatasetHelper.class);

  protected NetcdfDataset ncfile;
  protected String obsTimeVName, nomTimeVName;
  protected String latVName, lonVName, zcoordVName, zcoordUnits;
  protected String stnIdVName, stnIndexVName, stnDescVName;
  protected StationHelper stationHelper;
  protected DataType stationIdType;

  protected StructureDS recordVar;
  protected Dimension obsDim;

  protected LatLonRect boundingBox;
  protected double minDate, maxDate;
  protected DateUnit timeUnit;

  protected double altScaleFactor = 1.0;

  protected Formatter errs = null;
  protected boolean showErrors = true;

  /**
   * Constructor.
   *
   * @param ncfile the netccdf file
   * @param typedDataVariables list of data variables; all record variables will be added to this
   *     list, except . You can remove extra
   * @param obsTimeVName observation time variable name (required)
   * @param nomTimeVName nominal time variable name (may be null)
   * @throws IllegalArgumentException if ncfile has no unlimited dimension and recDimName is null.
   */
  public RecordDatasetHelper(
      NetcdfDataset ncfile,
      String obsTimeVName,
      String nomTimeVName,
      List<VariableSimpleIF> typedDataVariables,
      String recDimName,
      Formatter errBuffer) {
    this.ncfile = ncfile;
    this.obsTimeVName = obsTimeVName;
    this.nomTimeVName = nomTimeVName;
    this.errs = errBuffer;

    // check if we already have a structure vs if we have to add it.

    if (this.ncfile.hasUnlimitedDimension()) {
      this.ncfile.sendIospMessage(NetcdfFile.IOSP_MESSAGE_ADD_RECORD_STRUCTURE);
      this.recordVar = (StructureDS) this.ncfile.getRootGroup().findVariable("record");
      this.obsDim = ncfile.getUnlimitedDimension();

    } else {
      if (recDimName == null)
        throw new IllegalArgumentException(
            "File <"
                + this.ncfile.getLocation()
                + "> has no unlimited dimension, specify psuedo record dimension with observationDimension global attribute.");
      this.obsDim = this.ncfile.getRootGroup().findDimension(recDimName);
      this.recordVar = new StructurePseudoDS(this.ncfile, null, "record", null, obsDim);
    }

    // create member variables
    List<Variable> recordMembers = ncfile.getVariables();
    for (Variable v : recordMembers) {
      if (v == recordVar) continue;
      if (v.isScalar()) continue;
      if (v.getDimension(0) == this.obsDim) typedDataVariables.add(v);
    }

    // need the time units
    Variable timeVar = ncfile.findVariable(obsTimeVName);
    String timeUnitString =
        ncfile.findAttValueIgnoreCase(timeVar, CDM.UNITS, "seconds since 1970-01-01");
    try {
      timeUnit = new DateUnit(timeUnitString);
    } catch (Exception e) {
      if (null != errs) errs.format("Error on string = %s == %s%n", timeUnitString, e.getMessage());
      try {
        timeUnit = new DateUnit("seconds since 1970-01-01");
      } catch (Exception e1) {
        // cant happen
      }
    }
  }

  /**
   * Set extra information used by station obs datasets. Use stnIdVName or stnIndexVName.
   *
   * @param stnIdVName the obs variable that is used to find the station in the stnHash; may be type
   *     int or a String (char).
   * @param stnDescVName optional station var containing station description
   */
  public void setStationInfo(
      String stnIdVName, String stnDescVName, String stnIndexVName, StationHelper stationHelper) {
    this.stnIdVName = stnIdVName;
    this.stnDescVName = stnDescVName;
    this.stnIndexVName = stnIndexVName;
    this.stationHelper = stationHelper;

    if (stnIdVName != null) {
      Variable stationVar = ncfile.findVariable(stnIdVName);
      stationIdType = stationVar.getDataType();
    }
  }

  public void setLocationInfo(String latVName, String lonVName, String zcoordVName) {
    this.latVName = latVName;
    this.lonVName = lonVName;
    this.zcoordVName = zcoordVName;

    // check for meter conversion
    if (zcoordVName != null) {
      Variable v = ncfile.findVariable(zcoordVName);
      zcoordUnits = ncfile.findAttValueIgnoreCase(v, CDM.UNITS, null);
      if (zcoordUnits != null)
        try {
          altScaleFactor = getMetersConversionFactor(zcoordUnits);
        } catch (Exception e) {
          if (errs != null) errs.format("%s", e.getMessage());
        }
    }
  }

  // make structure variable names to shortNames so StructureData sdata can
  // access it members
  public void setShortNames(
      String latVName, String lonVName, String altVName, String obsTimeVName, String nomTimeVName) {
    this.latVName = latVName;
    this.lonVName = lonVName;
    this.zcoordVName = altVName;
    this.obsTimeVName = obsTimeVName;
    this.nomTimeVName = nomTimeVName;
  }

  protected static double getMetersConversionFactor(String unitsString) throws Exception {
    SimpleUnit unit = SimpleUnit.factoryWithExceptions(unitsString);
    return unit.convertTo(1.0, SimpleUnit.meterUnit);
  }

  public Structure getRecordVar() {
    return (this.recordVar);
  }

  public int getRecordCount() {
    Dimension unlimitedDim = ncfile.getUnlimitedDimension();
    return unlimitedDim.getLength();
  }

  public void setTimeUnit(DateUnit timeUnit) {
    this.timeUnit = timeUnit;
  }

  public DateUnit getTimeUnit() {
    return this.timeUnit;
  }

  public LatLonPoint getLocation(StructureData sdata) {
    StructureMembers members = sdata.getStructureMembers();
    double lat = sdata.convertScalarDouble(members.findMember(latVName));
    double lon = sdata.convertScalarDouble(members.findMember(lonVName));
    return new LatLonPointImpl(lat, lon);
  }

  public double getLatitude(StructureData sdata) {
    StructureMembers members = sdata.getStructureMembers();
    return sdata.convertScalarDouble(members.findMember(latVName));
  }

  public double getLongitude(StructureData sdata) {
    StructureMembers members = sdata.getStructureMembers();
    return sdata.convertScalarDouble(members.findMember(lonVName));
  }

  public double getZcoordinate(StructureData sdata) {
    StructureMembers members = sdata.getStructureMembers();
    return (zcoordVName == null)
        ? Double.NaN
        : sdata.convertScalarDouble(members.findMember(zcoordVName));
  }

  public String getZcoordUnits() {
    return zcoordUnits;
  }

  public Date getObservationTimeAsDate(StructureData sdata) {
    return timeUnit.makeDate(getObservationTime(sdata));
  }

  public double getObservationTime(StructureData sdata) {
    return getTime(sdata.findMember(obsTimeVName), sdata);
  }

  private double getTime(StructureMembers.Member timeVar, StructureData sdata) {
    if (timeVar == null) return 0.0;

    if ((timeVar.getDataType() == DataType.CHAR) || (timeVar.getDataType() == DataType.STRING)) {
      String time = sdata.getScalarString(timeVar);
      CalendarDate date = CalendarDateFormatter.isoStringToCalendarDate(null, time);
      if (date == null) {
        log.error("Cant parse date - not ISO formatted, = " + time);
        return 0.0;
      }
      return date.getMillis() / 1000.0;

    } else {
      return sdata.convertScalarDouble(timeVar);
    }
  }

  /*
   * This reads through all the records in the dataset, and constructs a list of
   * RecordPointObs or RecordStationObs. It does not cache the data.
   * <p>If stnIdVName is not null, its a StationDataset, then construct a Station HashMap of StationImpl
   * objects. Add the RecordStationObs into the list of obs for that station.
   *
   * @param cancel allow user to cancel
   * @return List of RecordPointObs or RecordStationObs
   * @throws IOException on read error
   *
  public List<RecordPointObs> readAllCreateObs(CancelTask cancel) throws IOException {

    // see if its a station or point dataset
    boolean hasStations = stnIdVName != null;
    if (hasStations)
      stnHash = new HashMap<Object, Station>();

    // get min and max date and lat,lon
    double minDate = Double.MAX_VALUE;
    double maxDate = -Double.MAX_VALUE;

    double minLat = Double.MAX_VALUE;
    double maxLat = -Double.MAX_VALUE;

    double minLon = Double.MAX_VALUE;
    double maxLon = -Double.MAX_VALUE;

    // read all the data, create a RecordObs
    StructureMembers members = null;
    List<RecordPointObs> records = new ArrayList<RecordPointObs>();
    int recno = 0;
    Structure.Iterator ii = recordVar.getStructureIterator();
    while (ii.hasNext()) {
      StructureData sdata = ii.next();
      if (members == null)
        members = sdata.getStructureMembers();

      Object stationId = null;
      if (hasStations) {
        if (stationIdType == DataType.INT) {
          stationId = sdata.getScalarInt(stnIdVName);
        } else
          stationId = sdata.getScalarString(stnIdVName).trim();
      }

      String desc = (stnDescVName == null) ? null : sdata.getScalarString(stnDescVName);
      double lat = sdata.getScalarDouble(latVName);
      double lon = sdata.getScalarDouble(lonVName);
      double alt = (altVName == null) ? 0.0 : altScaleFactor * sdata.getScalarDouble(altVName);
      double obsTime = sdata.convertScalarDouble(members.findMember(obsTimeVName));
      double nomTime = (nomTimeVName == null) ? obsTime : sdata.convertScalarDouble(members.findMember(nomTimeVName));

      //double obsTime = sdata.convertScalarDouble( members.findMember( obsTimeVName) );
      //double nomTime = (nomTimeVName == null) ? obsTime : sdata.convertScalarDouble( members.findMember( nomTimeVName));

      if (hasStations) {
        Station stn = stnHash.get(stationId);
        if (stn == null) {
          stn = new Station(stationId.toString(), desc, lat, lon, alt);
          stnHash.put(stationId, stn);
        }
        RecordStationObs stnObs = new RecordStationObs(stn, obsTime, nomTime, timeUnit, recno);
        records.add(stnObs);
        //stn.addObs( stnObs);

      } else {
        records.add(new RecordPointObs(new EarthLocation(lat, lon, alt), obsTime, nomTime, timeUnit, recno));
      }

      // track date range and bounding box
      minDate = Math.min(minDate, obsTime);
      maxDate = Math.max(maxDate, obsTime);

      minLat = Math.min(minLat, lat);
      maxLat = Math.max(maxLat, lat);
      minLon = Math.min(minLon, lon);
      maxLon = Math.max(maxLon, lon);

      recno++;
      if ((cancel != null) && cancel.isCancel()) return null;
    }
    boundingBox = new LatLonRect(new LatLonPointImpl(minLat, minLon), new LatLonPointImpl(maxLat, maxLon));

    return records;
  }

  /* private boolean debugBB = false;
  public List getData(ArrayList records, LatLonRect boundingBox, CancelTask cancel) throws IOException {
    if (debugBB) System.out.println("Want bb= "+boundingBox);
    ArrayList result = new ArrayList();
    for (int i = 0; i < records.size(); i++) {
      RecordDatasetHelper.RecordPointObs r =  (RecordDatasetHelper.RecordPointObs) records.get(i);
      if (boundingBox.contains(r.getLatLon())) {
        if (debugBB) System.out.println(" ok latlon= "+r.getLatLon());
        result.add( r);
      }
      if ((cancel != null) && cancel.isCancel()) return null;
    }
    return result;
  }

  // return List<PointObsDatatype>
  public List getData(ArrayList records, LatLonRect boundingBox, double startTime, double endTime, CancelTask cancel) throws IOException {
    if (debugBB) System.out.println("Want bb= "+boundingBox);
    ArrayList result = new ArrayList();
    for (int i = 0; i < records.size(); i++) {
      RecordDatasetHelper.RecordPointObs r =  (RecordDatasetHelper.RecordPointObs) records.get(i);
      if (boundingBox.contains(r.getLatLon())) {
        if (debugBB) System.out.println(" ok latlon= "+r.getLatLon());
        double timeValue = r.getObservationTime();
        if ((timeValue >= startTime) && (timeValue <= endTime))
          result.add( r);
      }
      if ((cancel != null) && cancel.isCancel()) return null;
    }
    return result;
  }  */

  //////////////////////////////////////////////////////////////////////////////////////
  public PointFeature factory(StationImpl s, StructureData sdata, int recno) {
    if (s == null) return new RecordPointObs(sdata, recno);
    else return new RecordStationObs(s, sdata, recno);
  }

  class RecordPointObs extends PointFeatureImpl {
    protected int recno;
    protected StructureData sdata;

    RecordPointObs(int recno) {
      super(RecordDatasetHelper.this.timeUnit);
      this.recno = recno;
    }

    // Constructor for the case where you keep track of the location, time of each record, but not
    // the data.
    protected RecordPointObs(
        EarthLocation location, double obsTime, double nomTime, DateUnit timeUnit, int recno) {
      super(location, obsTime, nomTime, timeUnit);
      this.recno = recno;
    }

    // Constructor for when you already have the StructureData and want to wrap it in a
    // StationObsDatatype
    protected RecordPointObs(StructureData sdata, int recno) {
      super(RecordDatasetHelper.this.timeUnit);
      this.sdata = sdata;
      this.recno = recno;

      StructureMembers members = sdata.getStructureMembers();
      obsTime = getTime(members.findMember(obsTimeVName), sdata);
      nomTime = (nomTimeVName == null) ? obsTime : getTime(members.findMember(nomTimeVName), sdata);

      // this assumes the lat/lon/alt is stored in the obs record
      double lat = sdata.convertScalarDouble(members.findMember(latVName));
      double lon = sdata.convertScalarDouble(members.findMember(lonVName));
      double alt =
          (zcoordVName == null)
              ? 0.0
              : altScaleFactor * sdata.convertScalarDouble(members.findMember(zcoordVName));
      location = new EarthLocationImpl(lat, lon, alt);
    }

    public String getId() {
      return Integer.toString(recno);
    }

    public LatLonPoint getLatLon() {
      return new LatLonPointImpl(location.getLatitude(), location.getLongitude());
    }

    public StructureData getFeatureData() throws IOException {
      if (null == sdata) {
        try {
          // deal with files that are updating // LOOK kludge?
          if (recno > getRecordCount()) {
            int n = getRecordCount();
            ncfile.syncExtend();
            log.info(
                "RecordPointObs.getData recno="
                    + recno
                    + " > "
                    + n
                    + "; after sync= "
                    + getRecordCount());
          }

          sdata = recordVar.readStructure(recno);
        } catch (ucar.ma2.InvalidRangeException e) {
          e.printStackTrace();
          throw new IOException(e.getMessage());
        }
      }
      return sdata;
    }

    public ucar.ma2.StructureData getDataAll() throws java.io.IOException {
      return getFeatureData();
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////

  // a PointObs with the location info stored as a Station
  class RecordStationObs extends RecordPointObs {
    private Station station;

    /**
     * Constructor for the case where you keep track of the station, time of each record, but the
     * data reading is deferred.
     *
     * @param station data is for this Station
     * @param obsTime observation time
     * @param nomTime nominal time (may be NaN)
     * @param recno data is at this record number
     */
    protected RecordStationObs(
        Station station, double obsTime, double nomTime, DateUnit timeUnit, int recno) {
      super(station, obsTime, nomTime, timeUnit, recno);
      this.station = station;
    }

    // Constructor for when you have everything
    protected RecordStationObs(
        Station station, double obsTime, double nomTime, StructureData sdata, int recno) {
      super(recno);
      this.station = station;
      this.location = station;
      this.obsTime = obsTime;
      this.nomTime = nomTime;
      this.sdata = sdata;
    }

    // Constructor for when you already have the StructureData and Station, and calculate times
    protected RecordStationObs(Station station, StructureData sdata, int recno) {
      super(recno);
      this.station = station;
      this.location = station;
      this.sdata = sdata;

      StructureMembers members = sdata.getStructureMembers();
      obsTime = getTime(members.findMember(obsTimeVName), sdata);
      nomTime = (nomTimeVName == null) ? obsTime : getTime(members.findMember(nomTimeVName), sdata);
    }

    // Constructor for when you already have the StructureData, and need to find Station and times
    protected RecordStationObs(StructureData sdata, int recno, boolean useId) {
      super(recno);
      this.recno = recno;
      this.sdata = sdata;
      this.timeUnit = RecordDatasetHelper.this.timeUnit;

      StructureMembers members = sdata.getStructureMembers();
      obsTime = getTime(members.findMember(obsTimeVName), sdata);
      nomTime = (nomTimeVName == null) ? obsTime : getTime(members.findMember(nomTimeVName), sdata);

      if (useId) {
        // this assumes the station id/name is stored in the obs record
        String stationId;
        if (stationIdType == DataType.INT) {
          stationId = Integer.toString(sdata.getScalarInt(stnIdVName));
        } else stationId = sdata.getScalarString(stnIdVName).trim();
        station = stationHelper.getStation(stationId);
        if (null != errs)
          errs.format(" cant find station id = <%s> when reading record %d%n", stationId, recno);
        log.error(" cant find station id = <" + stationId + "> when reading record " + recno);

      } else {
        // use a station index
        List<Station> stations = stationHelper.getStations();
        int stationIndex = sdata.getScalarInt(stnIndexVName);
        if (stationIndex < 0 || stationIndex >= stations.size()) {
          if (null != errs)
            errs.format(
                " cant find station at index =%d when reading record %d%n", stationIndex, recno);
          log.error(
              "cant find station at index = " + stationIndex + " when reading record " + recno);
        } else station = stations.get(stationIndex);
      }

      location = station;
    }
  }
}
/**
 * Build a GribCollection object. Rectilyse and manage grib collection index. Covers
 * GribCollectionProto.
 *
 * @author caron
 * @since 4/6/11
 */
public class Grib2CollectionBuilder {
  private static final org.slf4j.Logger logger =
      org.slf4j.LoggerFactory.getLogger(GribCollection.class);

  public static final String MAGIC_START = "Grib2CollectionIndex";
  protected static final int version = 5;
  private static final boolean debug = false;

  // from a single file, read in the index, create if it doesnt exist
  public static GribCollection createFromSingleFile(
      File file, CollectionManager.Force force, Formatter f) throws IOException {
    Grib2CollectionBuilder builder = new Grib2CollectionBuilder(file, f);
    builder.readOrCreateIndex(force, f);
    return builder.gc;
  }

  // from a collection, read in the index, create if it doesnt exist or is out of date
  // assume that the CollectionManager is up to date, eg doesnt need to be scanned
  public static GribCollection factory(
      CollectionManager dcm, CollectionManager.Force force, Formatter f) throws IOException {
    Grib2CollectionBuilder builder = new Grib2CollectionBuilder(dcm);
    builder.readOrCreateIndex(force, f);
    return builder.gc;
  }

  // read in the index, index raf already open
  public static GribCollection createFromIndex(String name, File directory, RandomAccessFile raf)
      throws IOException {
    Grib2CollectionBuilder builder = new Grib2CollectionBuilder(name, directory);
    if (builder.readIndex(raf)) return builder.gc;
    throw new IOException("Reading index failed");
  }

  // this writes the index always
  public static boolean writeIndexFile(File indexFile, CollectionManager dcm, Formatter f)
      throws IOException {
    Grib2CollectionBuilder builder = new Grib2CollectionBuilder(dcm);
    return builder.createIndex(indexFile, CollectionManager.Force.always, f);
  }

  ////////////////////////////////////////////////////////////////

  private final List<CollectionManager> collections = new ArrayList<CollectionManager>();
  protected GribCollection gc;

  // single file
  private Grib2CollectionBuilder(File file, Formatter f) throws IOException {
    try {
      // String spec = StringUtil2.substitute(file.getPath(), "\\", "/");
      CollectionManager dcm = new DatasetCollectionSingleFile(file);
      this.collections.add(dcm);
      this.gc = new Grib2Collection(file.getName(), new File(dcm.getRoot()));

    } catch (Exception e) {
      ByteArrayOutputStream bos = new ByteArrayOutputStream(10000);
      e.printStackTrace(new PrintStream(bos));
      f.format("%s", bos.toString());
      throw new IOException(e);
    }
  }

  private Grib2CollectionBuilder(CollectionManager dcm) {
    this.collections.add(dcm);
    this.gc = new Grib2Collection(dcm.getCollectionName(), new File(dcm.getRoot()));
  }

  private Grib2CollectionBuilder(String name, File directory) {
    this.gc = new Grib2Collection(name, directory);
  }

  protected Grib2CollectionBuilder() {
    this.gc = null;
  }

  protected int getVersion() {
    return version;
  }

  // read or create index
  private void readOrCreateIndex(CollectionManager.Force ff, Formatter f) throws IOException {

    // force new index or test for new index needed
    boolean force =
        ((ff == CollectionManager.Force.always)
            || (ff == CollectionManager.Force.test && needsUpdate()));

    // otherwise, we're good as long as the index file exists
    File idx = gc.getIndexFile();
    if (force || !idx.exists() || !readIndex(idx.getPath())) {
      logger.info("GribCollection {}: createIndex {}", gc.getName(), idx.getPath());
      createIndex(idx, ff, f); // write out index
      gc.rafLocation = idx.getPath();
      gc.setRaf(new RandomAccessFile(idx.getPath(), "r"));
      readIndex(gc.getRaf()); // read back in index
    }
  }

  public boolean needsUpdate() {
    File idx = gc.getIndexFile();
    return !idx.exists() || needsUpdate(idx.lastModified());
  }

  private boolean needsUpdate(long idxLastModified) {
    CollectionManager.ChangeChecker cc = Grib2Index.getChangeChecker();
    for (CollectionManager dcm : collections) {
      for (MFile mfile : dcm.getFiles()) {
        if (cc.hasChangedSince(mfile, idxLastModified)) return true;
      }
    }
    return false;
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////////
  // reading

  public boolean readIndex(String filename) throws IOException {
    return readIndex(new RandomAccessFile(filename, "r"));
  }

  public boolean readIndex(RandomAccessFile raf) {
    gc.setRaf(raf); // LOOK leaving the raf open in the GribCollection
    try {
      raf.order(RandomAccessFile.BIG_ENDIAN);
      raf.seek(0);

      //// header message
      if (!NcStream.readAndTest(raf, MAGIC_START.getBytes())) {
        logger.error("GribCollection {}: invalid index", gc.getName());
        return false;
      }

      int v = raf.readInt();
      if (v != getVersion()) {
        logger.warn(
            "GribCollection {}: index found version={}, want version= {} on file {}",
            new Object[] {gc.getName(), v, version, raf.getLocation()});
        return false;
      }

      long skip = raf.readLong();
      raf.skipBytes(skip);

      int size = NcStream.readVInt(raf);
      if ((size < 0) || (size > 100 * 1000 * 1000)) {
        logger.warn("GribCollection {}: invalid index ", gc.getName());
        return false;
      }

      byte[] m = new byte[size];
      raf.readFully(m);

      GribCollectionProto.GribCollectionIndex proto =
          GribCollectionProto.GribCollectionIndex.parseFrom(m);

      gc.center = proto.getCenter();
      gc.subcenter = proto.getSubcenter();
      gc.master = proto.getMaster();
      gc.local = proto.getLocal();
      gc.genProcessType = proto.getGenProcessType();
      gc.genProcessId = proto.getGenProcessId();
      gc.backProcessId = proto.getBackProcessId();
      gc.local = proto.getLocal();
      // gc.tables = Grib2Tables.factory(gc.center, gc.subcenter, gc.master, gc.local);

      gc.filenames = new ArrayList<String>(proto.getFilesCount());
      for (int i = 0; i < proto.getFilesCount(); i++) gc.filenames.add(proto.getFiles(i));

      // error condition on a GribCollection Index
      if ((proto.getFilesCount() == 0) && !(this instanceof TimePartitionBuilder)) {
        logger.warn("GribCollection {}: has no files, force recreate ", gc.getName());
        return false;
      }

      gc.groups = new ArrayList<GribCollection.GroupHcs>(proto.getGroupsCount());
      for (int i = 0; i < proto.getGroupsCount(); i++)
        gc.groups.add(readGroup(proto.getGroups(i), gc.makeGroup()));
      Collections.sort(gc.groups);

      gc.params = new ArrayList<Parameter>(proto.getParamsCount());
      for (int i = 0; i < proto.getParamsCount(); i++) gc.params.add(readParam(proto.getParams(i)));

      if (!readPartitions(proto)) {
        logger.warn("TimePartition {}: has no partitions, force recreate ", gc.getName());
        return false;
      }

      return true;

    } catch (Throwable t) {
      logger.error("Error reading index " + raf.getLocation(), t);
      return false;
    }
  }

  protected boolean readPartitions(GribCollectionProto.GribCollectionIndex proto) {
    return true;
  }

  protected void readTimePartitions(
      GribCollection.GroupHcs group, GribCollectionProto.Group proto) {
    // NOOP
  }

  GribCollection.GroupHcs readGroup(GribCollectionProto.Group p, GribCollection.GroupHcs group)
      throws IOException {

    Grib2SectionGridDefinition gdss = new Grib2SectionGridDefinition(p.getGds().toByteArray());
    Grib2Gds gds = gdss.getGDS();
    group.setHorizCoordSystem(gds.makeHorizCoordSys());

    group.varIndex = new ArrayList<GribCollection.VariableIndex>();
    for (int i = 0; i < p.getVariablesCount(); i++)
      group.varIndex.add(readVariable(p.getVariables(i), group));
    Collections.sort(group.varIndex);

    group.timeCoords = new ArrayList<TimeCoord>(p.getTimeCoordsCount());
    for (int i = 0; i < p.getTimeCoordsCount(); i++)
      group.timeCoords.add(readTimeCoord(p.getTimeCoords(i)));

    group.vertCoords = new ArrayList<VertCoord>(p.getVertCoordsCount());
    for (int i = 0; i < p.getVertCoordsCount(); i++)
      group.vertCoords.add(readVertCoord(p.getVertCoords(i)));

    group.ensCoords = new ArrayList<EnsCoord>(p.getEnsCoordsCount());
    for (int i = 0; i < p.getEnsCoordsCount(); i++)
      group.ensCoords.add(readEnsCoord(p.getEnsCoords(i)));

    group.filenose = new int[p.getFilenoCount()];
    for (int i = 0; i < p.getFilenoCount(); i++) group.filenose[i] = p.getFileno(i);

    readTimePartitions(group, p);

    // finish
    for (GribCollection.VariableIndex vi : group.varIndex) {
      TimeCoord tc = group.timeCoords.get(vi.timeIdx);
      vi.ntimes = tc.getSize();
      VertCoord vc = (vi.vertIdx < 0) ? null : group.vertCoords.get(vi.vertIdx);
      vi.nverts = (vc == null) ? 0 : vc.getSize();
      EnsCoord ec = (vi.ensIdx < 0) ? null : group.ensCoords.get(vi.ensIdx);
      vi.nens = (ec == null) ? 0 : ec.getSize();
    }

    // group.assignVertNames();

    return group;
  }

  private Parameter readParam(GribCollectionProto.Parameter pp) throws IOException {
    if (pp.hasSdata()) return new Parameter(pp.getName(), pp.getSdata());

    int count = 0;
    double[] vals = new double[pp.getDataCount()];
    for (double val : pp.getDataList()) vals[count++] = val;

    return new Parameter(pp.getName(), vals);
  }

  private TimeCoord readTimeCoord(GribCollectionProto.Coord pc) throws IOException {
    if (pc.getBoundCount() > 0) { // its an interval
      List<TimeCoord.Tinv> coords = new ArrayList<TimeCoord.Tinv>(pc.getValuesCount());
      for (int i = 0; i < pc.getValuesCount(); i++)
        coords.add(new TimeCoord.Tinv((int) pc.getValues(i), (int) pc.getBound(i)));
      return new TimeCoord(pc.getCode(), pc.getUnit(), coords);
    } else {
      List<Integer> coords = new ArrayList<Integer>(pc.getValuesCount());
      for (float value : pc.getValuesList()) coords.add((int) value);
      return new TimeCoord(pc.getCode(), pc.getUnit(), coords);
    }
  }

  private VertCoord readVertCoord(GribCollectionProto.Coord pc) throws IOException {
    boolean isLayer = (pc.getBoundCount() > 0);
    List<VertCoord.Level> coords = new ArrayList<VertCoord.Level>(pc.getValuesCount());
    for (int i = 0; i < pc.getValuesCount(); i++)
      coords.add(new VertCoord.Level(pc.getValues(i), isLayer ? pc.getBound(i) : 0));
    return new VertCoord(pc.getCode(), coords, isLayer);
  }

  private EnsCoord readEnsCoord(GribCollectionProto.Coord pc) throws IOException {
    List<EnsCoord.Coord> coords = new ArrayList<EnsCoord.Coord>(pc.getValuesCount());
    for (int i = 0; i < pc.getValuesCount(); i += 2)
      coords.add(new EnsCoord.Coord((int) pc.getValues(i), (int) pc.getValues(i + 1)));
    return new EnsCoord(coords);
  }

  protected GribCollection.VariableIndex readVariable(
      GribCollectionProto.Variable pv, GribCollection.GroupHcs group) {
    int discipline = pv.getDiscipline();
    int category = pv.getCategory();
    int param = pv.getParameter();
    int levelType = pv.getLevelType();
    int intvType = pv.getIntervalType();
    boolean isLayer = pv.getIsLayer();
    int ensDerivedType = pv.getEnsDerivedType();
    int probType = pv.getProbabilityType();
    String probabilityName = pv.getProbabilityName();
    int cdmHash = pv.getCdmHash();
    long recordsPos = pv.getRecordsPos();
    int recordsLen = pv.getRecordsLen();
    int timeIdx = pv.getTimeIdx();
    int vertIdx = pv.getVertIdx();
    int ensIdx = pv.getEnsIdx();
    int tableVersion = pv.getTableVersion();

    return gc.makeVariableIndex(
        group,
        tableVersion,
        discipline,
        category,
        param,
        levelType,
        isLayer,
        intvType,
        ensDerivedType,
        probType,
        probabilityName,
        cdmHash,
        timeIdx,
        vertIdx,
        ensIdx,
        recordsPos,
        recordsLen);
  }

  ///////////////////////////////////////////////////////////////////////////////////
  // writing

  private class Group {
    public Grib2SectionGridDefinition gdss;
    public int gdsHash; // may have been modified
    public Grib2Rectilyser rect;
    public List<Grib2Record> records = new ArrayList<Grib2Record>();
    public String name;
    public Set<Integer>
        fileSet; // this is so we can show just the component files that are in this group

    private Group(Grib2SectionGridDefinition gdss, int gdsHash) {
      this.gdss = gdss;
      this.gdsHash = gdsHash;
      Grib2Gds gds = gdss.getGDS();
      name = gds.getNameShort() + "-" + gds.ny + "X" + gds.nx;
    }
  }

  ///////////////////////////////////////////////////
  // create the index

  private boolean createIndex(File indexFile, CollectionManager.Force ff, Formatter f)
      throws IOException {
    long start = System.currentTimeMillis();

    ArrayList<String> filenames = new ArrayList<String>();
    List<Group> groups = makeAggregatedGroups(filenames, ff, f);
    createIndex(indexFile, groups, filenames, f);

    long took = System.currentTimeMillis() - start;
    f.format("That took %d msecs%n", took);
    return true;
  }

  // read all records in all files,
  // divide into groups based on GDS hash
  // each group has an arraylist of all records that belong to it.
  // for each group, run rectlizer to derive the coordinates and variables
  public List<Group> makeAggregatedGroups(
      ArrayList<String> filenames, CollectionManager.Force force, Formatter f) throws IOException {
    Map<Integer, Group> gdsMap = new HashMap<Integer, Group>();

    f.format("GribCollection %s: makeAggregatedGroups%n", gc.getName());
    int total = 0;
    int fileno = 0;
    for (CollectionManager dcm : collections) {
      // dcm.scanIfNeeded(); // LOOK ??
      f.format(" dcm= %s%n", dcm);
      Map<Integer, Integer> gdsConvert = (Map<Integer, Integer>) dcm.getAuxInfo("gdsHash");

      for (MFile mfile : dcm.getFiles()) {
        // f.format("%3d: %s%n", fileno, mfile.getPath());
        filenames.add(mfile.getPath());

        Grib2Index index = new Grib2Index();
        try {
          if (!index.readIndex(
              mfile.getPath(),
              mfile.getLastModified(),
              force)) { // heres where the index date is checked against the data file
            index.makeIndex(mfile.getPath(), f);
            f.format(
                "  Index written: %s == %d records %n",
                mfile.getName() + Grib2Index.IDX_EXT, index.getRecords().size());
          } else if (debug) {
            f.format(
                "  Index read: %s == %d records %n",
                mfile.getName() + Grib2Index.IDX_EXT, index.getRecords().size());
          }
        } catch (IOException ioe) {
          f.format(
              "GribCollectionBuilder: reading/Creating gbx9 index failed err=%s%n  skipping %s%n",
              ioe.getMessage(), mfile.getPath() + Grib2Index.IDX_EXT);
          continue;
        }

        for (Grib2Record gr : index.getRecords()) {
          gr.setFile(fileno); // each record tracks which file it belongs to
          int gdsHash =
              gr.getGDSsection().getGDS().hashCode(); // use GDS hash code to group records
          if (gdsConvert != null
              && gdsConvert.get(gdsHash)
                  != null) { // allow external config to muck with gdsHash. Why? because of error in
            // encoding
            gdsHash = (Integer) gdsConvert.get(gdsHash); // and we need exact hash matching
          }

          Group g = gdsMap.get(gdsHash);
          if (g == null) {
            g = new Group(gr.getGDSsection(), gdsHash);
            gdsMap.put(gdsHash, g);
          }
          g.records.add(gr);
          total++;
        }
        fileno++;
      }
    }
    f.format(" total grib records= %d%n", total);

    Grib2Rectilyser.Counter c = new Grib2Rectilyser.Counter();
    List<Group> result = new ArrayList<Group>(gdsMap.values());
    for (Group g : result) {
      g.rect = new Grib2Rectilyser(g.records, g.gdsHash);
      f.format(" GDS hash %d == ", g.gdsHash);
      g.rect.make(f, c);
    }
    f.format(
        " Rectilyser: nvars=%d records unique=%d total=%d dups=%d (%f) %n",
        c.vars, c.recordsUnique, c.records, c.dups, ((float) c.dups) / c.records);

    return result;
  }

  /*
  MAGIC_START
  version
  sizeRecords
  VariableRecords (sizeRecords bytes)
  sizeIndex
  GribCollectionIndex (sizeIndex bytes)
  */

  private void createIndex(
      File indexFile, List<Group> groups, ArrayList<String> filenames, Formatter f)
      throws IOException {
    Grib2Record first = null; // take global metadata from here

    if (indexFile.exists()) indexFile.delete(); // replace it
    f.format(" createIndex for %s%n", indexFile.getPath());

    RandomAccessFile raf = new RandomAccessFile(indexFile.getPath(), "rw");
    raf.order(RandomAccessFile.BIG_ENDIAN);
    try {
      //// header message
      raf.write(MAGIC_START.getBytes("UTF-8"));
      raf.writeInt(version);
      long lenPos = raf.getFilePointer();
      raf.writeLong(0); // save space to write the length of the record section
      long countBytes = 0;
      int countRecords = 0;
      for (Group g : groups) {
        g.fileSet = new HashSet<Integer>();
        for (Grib2Rectilyser.VariableBag vb : g.rect.getGribvars()) {
          if (first == null) first = vb.first;
          GribCollectionProto.VariableRecords vr = writeRecordsProto(vb, g.fileSet);
          byte[] b = vr.toByteArray();
          vb.pos = raf.getFilePointer();
          vb.length = b.length;
          raf.write(b);
          countBytes += b.length;
          countRecords += vb.recordMap.length;
        }
      }
      long bytesPerRecord = countBytes / ((countRecords == 0) ? 1 : countRecords);
      f.format(
          "  write RecordMaps: bytes = %d record = %d bytesPerRecord=%d%n",
          countBytes, countRecords, bytesPerRecord);

      if (first == null) {
        logger.error("GribCollection {}: has no files\n{}", gc.getName(), f.toString());
        throw new IllegalArgumentException("GribCollection " + gc.getName() + " has no files");
      }

      long pos = raf.getFilePointer();
      raf.seek(lenPos);
      raf.writeLong(countBytes);
      raf.seek(pos); // back to the output.

      GribCollectionProto.GribCollectionIndex.Builder indexBuilder =
          GribCollectionProto.GribCollectionIndex.newBuilder();
      indexBuilder.setName(gc.getName());

      for (String fn : filenames) indexBuilder.addFiles(fn);

      for (Group g : groups) indexBuilder.addGroups(writeGroupProto(g));

      /* int count = 0;
      for (DatasetCollectionManager dcm : collections) {
        indexBuilder.addParams(makeParamProto(new Parameter("spec" + count, dcm.())));
        count++;
      } */

      // what about just storing first ??
      Grib2SectionIdentification ids = first.getId();
      indexBuilder.setCenter(ids.getCenter_id());
      indexBuilder.setSubcenter(ids.getSubcenter_id());
      indexBuilder.setMaster(ids.getMaster_table_version());
      indexBuilder.setLocal(ids.getLocal_table_version());

      Grib2Pds pds = first.getPDS();
      indexBuilder.setGenProcessType(pds.getGenProcessType());
      indexBuilder.setGenProcessId(pds.getGenProcessId());
      indexBuilder.setBackProcessId(pds.getBackProcessId());

      GribCollectionProto.GribCollectionIndex index = indexBuilder.build();
      byte[] b = index.toByteArray();
      NcStream.writeVInt(raf, b.length); // message size
      raf.write(b); // message  - all in one gulp
      f.format("  write GribCollectionIndex= %d bytes%n", b.length);

    } finally {
      f.format("  file size =  %d bytes%n", raf.length());
      raf.close();
      if (raf != null) raf.close();
    }
  }

  /* private void createIndexForGroup(Group group, ArrayList<String> filenames) throws IOException {
    Grib2Record first = null; // take global metadata from here

    File file = new File(gc.getDirectory(), group.name + GribCollection.IDX_EXT);
    if (file.exists()) file.delete(); // replace it

    RandomAccessFile raf = new RandomAccessFile(file.getPath(), "rw");
    raf.order(RandomAccessFile.BIG_ENDIAN);
    try {
      //// header message
      String magic = gc.getMagicBytes();
      raf.write(magic.getBytes("UTF-8"));
      raf.writeInt(version);
      long lenPos = raf.getFilePointer();
      raf.writeLong(0); // save space to write the length of the record section
      long countBytes = 0;
      int countRecords = 0;
      group.fileSet = new HashSet<Integer>();

      for (Rectilyser.VariableBag vb : group.rect.getGribvars()) {
        if (first == null) first = vb.first;
        GribCollectionProto.VariableRecords vr = makeRecordsProto(vb, group.fileSet);
        byte[] b = vr.toByteArray();
        vb.pos = raf.getFilePointer();
        vb.length = b.length;
        raf.write(b);
        countBytes += b.length;
      }
      countRecords += group.records.size();
      if (countRecords == 0) countRecords = 1;
      long bytesPerRecord = countBytes / countRecords;
      logger.debug("VariableRecords: bytes = {} record = {} bytesPerRecord={}", new Object[] {countBytes, countRecords, bytesPerRecord});

      long pos = raf.getFilePointer();
      raf.seek(lenPos);
      raf.writeLong(countBytes);
      raf.seek(pos); // back to the output.

      GribCollectionProto.GribCollectionIndex.Builder indexBuilder = GribCollectionProto.GribCollectionIndex.newBuilder();
      indexBuilder.setName(group.name);

      for (String fn : filenames)
        indexBuilder.addFiles(fn);

      indexBuilder.addGroups(makeGroupProto(group));

      int count = 0;
      for (CollectionManager dcm : collections) {
        indexBuilder.addParams(makeParamProto(new Parameter("spec" + count, dcm.toString())));
        count++;
      }

      Grib2SectionIdentification ids = first.getId();
      indexBuilder.setCenter(ids.getCenter_id());
      indexBuilder.setSubcenter(ids.getSubcenter_id());
      indexBuilder.setMaster(ids.getMaster_table_version());
      indexBuilder.setLocal(ids.getLocal_table_version());

      GribCollectionProto.GribCollectionIndex index = indexBuilder.build();
      byte[] b = index.toByteArray();
      NcStream.writeVInt(raf, b.length); // message size
      raf.write(b);  // message  - all in one gulp
      logger.debug("GribCollectionIndex= {} bytes%n", b.length);

    } finally {
      logger.debug("file size =  {} bytes%n", raf.length());
      raf.close();
      if (raf != null) raf.close();
    }
  } */

  private GribCollectionProto.VariableRecords writeRecordsProto(
      Grib2Rectilyser.VariableBag vb, Set<Integer> fileSet) throws IOException {
    GribCollectionProto.VariableRecords.Builder b =
        GribCollectionProto.VariableRecords.newBuilder();
    b.setCdmHash(vb.first.cdmVariableHash(0));
    for (Grib2Rectilyser.Record ar : vb.recordMap) {
      GribCollectionProto.Record.Builder br = GribCollectionProto.Record.newBuilder();

      if (ar == null || ar.gr == null) {
        br.setFileno(0);
        br.setPos(0); // missing : ok to use 0 since drsPos > 0

      } else {
        br.setFileno(ar.gr.getFile());
        fileSet.add(ar.gr.getFile());
        Grib2SectionDataRepresentation drs = ar.gr.getDataRepresentationSection();
        br.setPos(drs.getStartingPosition());
      }
      b.addRecords(br);
    }
    return b.build();
  }

  private GribCollectionProto.Group writeGroupProto(Group g) throws IOException {
    GribCollectionProto.Group.Builder b = GribCollectionProto.Group.newBuilder();

    b.setGds(ByteString.copyFrom(g.gdss.getRawBytes()));

    for (Grib2Rectilyser.VariableBag vb : g.rect.getGribvars())
      b.addVariables(writeVariableProto(vb));

    List<TimeCoord> timeCoords = g.rect.getTimeCoords();
    for (int i = 0; i < timeCoords.size(); i++)
      b.addTimeCoords(writeCoordProto(timeCoords.get(i), i));

    List<VertCoord> vertCoords = g.rect.getVertCoords();
    for (int i = 0; i < vertCoords.size(); i++)
      b.addVertCoords(writeCoordProto(vertCoords.get(i), i));

    List<EnsCoord> ensCoords = g.rect.getEnsCoords();
    for (int i = 0; i < ensCoords.size(); i++) b.addEnsCoords(writeCoordProto(ensCoords.get(i), i));

    for (Integer aFileSet : g.fileSet) b.addFileno(aFileSet);

    return b.build();
  }

  private GribCollectionProto.Variable writeVariableProto(Grib2Rectilyser.VariableBag vb)
      throws IOException {
    GribCollectionProto.Variable.Builder b = GribCollectionProto.Variable.newBuilder();

    b.setDiscipline(vb.first.getDiscipline());
    Grib2Pds pds = vb.first.getPDS();
    b.setCategory(pds.getParameterCategory());
    b.setParameter(pds.getParameterNumber());
    b.setLevelType(pds.getLevelType1());
    b.setIsLayer(Grib2Utils.isLayer(vb.first));
    b.setIntervalType(pds.getStatisticalProcessType());
    b.setCdmHash(vb.first.cdmVariableHash(0));
    b.setRecordsPos(vb.pos);
    b.setRecordsLen(vb.length);
    b.setTimeIdx(vb.timeCoordIndex);
    if (vb.vertCoordIndex >= 0) b.setVertIdx(vb.vertCoordIndex);
    if (vb.ensCoordIndex >= 0) b.setEnsIdx(vb.ensCoordIndex);

    if (pds.isEnsembleDerived()) {
      Grib2Pds.PdsEnsembleDerived pdsDerived = (Grib2Pds.PdsEnsembleDerived) pds;
      b.setEnsDerivedType(pdsDerived.getDerivedForecastType()); // derived type (table 4.7)
    }

    if (pds.isProbability()) {
      Grib2Pds.PdsProbability pdsProb = (Grib2Pds.PdsProbability) pds;
      b.setProbabilityName(pdsProb.getProbabilityName());
      b.setProbabilityType(pdsProb.getProbabilityType());
    }

    return b.build();
  }

  protected GribCollectionProto.Parameter writeParamProto(Parameter param) throws IOException {
    GribCollectionProto.Parameter.Builder b = GribCollectionProto.Parameter.newBuilder();

    b.setName(param.getName());
    if (param.isString()) b.setSdata(param.getStringValue());
    else {
      for (int i = 0; i < param.getLength(); i++) b.addData(param.getNumericValue(i));
    }

    return b.build();
  }

  protected GribCollectionProto.Coord writeCoordProto(TimeCoord tc, int index) throws IOException {
    GribCollectionProto.Coord.Builder b = GribCollectionProto.Coord.newBuilder();
    b.setCode(index);
    b.setUnit(tc.getUnits());
    float scale =
        (float) tc.getTimeUnitScale(); // deal with, eg, "6 hours" by multiplying values by 6
    if (tc.isInterval()) {
      for (TimeCoord.Tinv tinv : tc.getIntervals()) {
        b.addValues(tinv.getBounds1() * scale);
        b.addBound(tinv.getBounds2() * scale);
      }
    } else {
      for (int value : tc.getCoords()) b.addValues(value * scale);
    }
    return b.build();
  }

  protected GribCollectionProto.Coord writeCoordProto(VertCoord vc, int index) throws IOException {
    GribCollectionProto.Coord.Builder b = GribCollectionProto.Coord.newBuilder();
    b.setCode(vc.getCode());
    b.setUnit(vc.getUnits());
    for (VertCoord.Level coord : vc.getCoords()) {
      if (vc.isLayer()) {
        b.addValues((float) coord.getValue1());
        b.addBound((float) coord.getValue2());
      } else {
        b.addValues((float) coord.getValue1());
      }
    }
    return b.build();
  }

  protected GribCollectionProto.Coord writeCoordProto(EnsCoord ec, int index) throws IOException {
    GribCollectionProto.Coord.Builder b = GribCollectionProto.Coord.newBuilder();
    b.setCode(0);
    b.setUnit("");
    for (EnsCoord.Coord coord : ec.getCoords()) {
      b.addValues((float) coord.getCode());
      b.addValues((float) coord.getEnsMember());
    }
    return b.build();
  }
}
/*
 * Processes Queries for the RadarServer  Spring Framework
 */
public class QueryRadarServerController extends AbstractController {

  private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(getClass());

  private TdsContext tdsContext;
  private HtmlWriter htmlWriter;
  private boolean htmlView;
  private boolean releaseDataset = false;

  public void setTdsContext(TdsContext tdsContext) {
    this.tdsContext = tdsContext;
  }

  public void setHtmlWriter(HtmlWriter htmlWriter) {
    this.htmlWriter = htmlWriter;
  }

  public boolean isHtmlView() {
    return htmlView;
  }

  public void setHtmlView(boolean htmlView) {
    this.htmlView = htmlView;
  }

  public boolean isReleaseDataset() {
    return releaseDataset;
  }

  public void setReleaseDataset(boolean releaseDataset) {
    this.releaseDataset = releaseDataset;
  }

  /** The view to forward to in case a bad query. */
  private static final String CREATE_VIEW = "forward:badquery.htm";

  /** The model key used to retrieve the message from the model. */
  private static final String MODEL_KEY = "message";

  /** The unique key for retrieving the text associated with this message. */
  private static final String MSG_CODE = "message.bad.query";

  /*
   * why calculate over and over again  1970-01-01T00:00:00
   */
  private static DateType epicDateType;

  static {
    try {
      epicDateType = new DateType(RadarServerUtil.epic, null, null);
    } catch (java.text.ParseException e) {
    }
  }

  static SimpleDateFormat dateFormat;

  static {
    dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.US);
    dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
  }

  /**
   * Query RadarServer controller for Spring Framework
   *
   * @param request HttpServletRequest
   * @param response HttpServletResponse
   * @return ModelAndView
   * @throws Exception
   */
  protected ModelAndView handleRequestInternal(
      HttpServletRequest request, HttpServletResponse response) throws Exception {
    try {
      // Gather diagnostics for logging request.
      log.info("handleRequestInternal(): " + UsageLog.setupRequestContext(request));
      // catch rogue invalid request here
      if (request.getQueryString() == null) {
        log.info("Invalid dataset url reference " + request.getPathInfo());
        throw new RadarServerException("Invalid dataset url reference " + request.getPathInfo());
      }
      // Query results in model
      Map<String, Object> model = new HashMap<String, Object>();
      radarQuery(request, response, model);
      if (model == null || model.size() == 0) {
        ModelAndView mav = new ModelAndView(CREATE_VIEW);
        mav.addObject(MODEL_KEY, MSG_CODE);
        return mav;
      } else {
        return new ModelAndView("queryXml", model);
      }
    } catch (RadarServerException e) {
      throw e; // pass it onto Spring exceptionResolver
    } catch (Throwable e) {
      log.error("handleRequestInternal(): Problem handling request.", e);
      log.info(
          "handleRequestInternal(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, -1));
      throw new RadarServerException("handleRequestInternal(): Problem handling request.");
    }
  }

  // get/check/process query
  public void radarQuery(HttpServletRequest req, HttpServletResponse res, Map<String, Object> model)
      throws ServletException, IOException, RadarServerException {

    //      long  startms = System.currentTimeMillis();
    //      long  endms;
    DatasetRepository.RadarType radarType = DatasetRepository.RadarType.nexrad;
    // need to extract data according to the (dataset) given
    String pathInfo = req.getPathInfo();
    if (pathInfo == null) pathInfo = "";
    if (pathInfo.startsWith("/")) pathInfo = pathInfo.substring(1);
    try {
      String rt = pathInfo.substring(0, pathInfo.indexOf('/', 1));
      radarType = DatasetRepository.RadarType.valueOf(rt);
    } catch (Exception e) {
      log.info("Invalid dataset url reference " + pathInfo);
      throw new RadarServerException("Invalid dataset url reference " + pathInfo);
    }
    Boolean level2 = pathInfo.contains("level2");

    // parse the input
    QueryParams qp = new QueryParams();
    if (!qp.parseQuery(
        req,
        res,
        new String[] {QueryParams.XML, QueryParams.HTML, QueryParams.RAW, QueryParams.NETCDF})) {
      // log.error( "parseQuery Failed "+ qp.errs.toString() + req.getQueryString() );
      // throw new RadarServerException( qp.errs.toString() );//+ req.getQueryString() );
      return; // TODO: uncomment above 2 lines when QueryParams exception is fixed
    }
    //      endms = System.currentTimeMillis();
    //      System.out.println( "after QueryParams "+ (endms - startms));
    //      startms = System.currentTimeMillis();
    // check Query Params
    if (!checkQueryParms(radarType, qp, level2)) {
      log.error("checkQueryParms Failed " + qp.errs.toString() + req.getQueryString());
      throw new RadarServerException(qp.errs.toString()); // + req.getQueryString() );
    }
    //      endms = System.currentTimeMillis();
    //      System.out.println( "after checkQueryParms "+ (endms - startms));
    //      startms = System.currentTimeMillis();

    // check type of output wanted XML html
    qp.acceptType = qp.acceptType.replaceFirst(".*/", "");

    // creates first part of catalog
    if (!createHeader(radarType, qp, pathInfo, model)) {
      log.error("Write Header Failed " + qp.errs.toString() + req.getQueryString());
      throw new RadarServerException(qp.errs.toString()); // req.getQueryString() );
    }
    //      endms = System.currentTimeMillis();
    //      System.out.println( "after writeHeader "+ (endms - startms));
    //      startms = System.currentTimeMillis();
    // gets products according to stations, time, and variables
    boolean dataFound = false;
    List<DatasetEntry> entries = new ArrayList<DatasetEntry>();
    if (qp.vars == null) {
      dataFound = processQuery(pathInfo, qp, null, entries);
      if (releaseDataset) DatasetRepository.removeRadarDatasetCollection(pathInfo, null);
    } else {
      int count = 0;
      for (String var : qp.vars) {
        dataFound = processQuery(pathInfo, qp, var, entries);
        if (dataFound) count++;
        if (releaseDataset) DatasetRepository.removeRadarDatasetCollection(pathInfo, var);
      }
      if (count > 0) dataFound = true;
    }
    // save entries
    model.put("datasets", entries);
    if (dataFound) {
      model.put("documentation", Integer.toString(entries.size()) + " datasets found for query");
    } else if (qp.errs.length() > 0) {
      model.put("documentation", qp.errs.toString());
    } else {
      model.put("documentation", "No data available for station(s) and time range");
    }

    //      endms = System.currentTimeMillis();
    //      System.out.println( "after radarQuery "+ (endms - startms));
    //      startms = System.currentTimeMillis();

  } // end radarNexradQuery

  // check that parms have valid stations, vars and times
  private Boolean checkQueryParms(
      DatasetRepository.RadarType radarType, QueryParams qp, Boolean level2) throws IOException {
    if (qp.hasBB) {
      if (radarType.equals(DatasetRepository.RadarType.nexrad))
        qp.stns = RadarServerUtil.getStationNames(qp.getBB(), DatasetRepository.nexradList);
      else qp.stns = RadarServerUtil.getStationNames(qp.getBB(), DatasetRepository.terminalList);

      if (qp.stns.size() == 0) {
        qp.errs.append("Bounding Box contains no stations ");
        return false;
      }
      if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns);
    }

    if (qp.hasStns) {
      if (RadarServerUtil.isStationListEmpty(qp.stns, radarType)) {
        qp.errs.append("No valid stations specified, need 1 ");
        return false;
      } else if (level2) {
        for (String stn : qp.stns) {
          if (stn.length() == 3) {
            qp.errs.append("Need 4 character station names ");
            return false;
          }
        }
      } else if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns);
    }

    if (qp.hasLatlonPoint) {
      qp.stns = new ArrayList<String>();
      if (radarType.equals(DatasetRepository.RadarType.nexrad))
        qp.stns.add(
            RadarServerUtil.findClosestStation(qp.lat, qp.lon, DatasetRepository.nexradList));
      else
        qp.stns.add(
            RadarServerUtil.findClosestStation(qp.lat, qp.lon, DatasetRepository.terminalList));
      if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns);
    } else if (qp.fatal) {
      qp.errs.append("No valid stations specified 2 ");
      return false;
    }

    if (qp.stns == null || qp.stns.size() == 0) {
      qp.errs.append("No valid stations specified, need 1 ");
      return false;
    }
    boolean useAllStations = (qp.stns.get(0).toUpperCase().equals("ALL"));
    if (useAllStations) {
      if (radarType.equals(DatasetRepository.RadarType.nexrad))
        qp.stns =
            RadarServerUtil.getStationNames(DatasetRepository.nexradList); // need station names
      else
        qp.stns =
            RadarServerUtil.getStationNames(DatasetRepository.terminalList); // need station names
      if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns);
    }

    if (qp.hasTimePoint) {
      if (qp.time.isPresent()) {
        try {
          qp.time_end = new DateType("present", null, null);
          qp.time_start = epicDateType;
        } catch (java.text.ParseException e) {
          qp.errs.append("Illegal param= 'time' must be valid ISO Duration");
          return false;
        }
      } else {
        qp.time_end = qp.time;
        qp.time_start = qp.time;
      }
    } else if (qp.hasDateRange) {
      DateRange dr = qp.getCalendarDateRange().toDateRange();
      qp.time_start = dr.getStart();
      qp.time_end = dr.getEnd();
    } else { // get all times
      qp.time_latest = 1;
      // qp.hasDateRange = true;
      try {
        qp.time = new DateType("present", null, null);
        qp.time_end = new DateType("present", null, null);
        qp.time_start = epicDateType;
      } catch (java.text.ParseException e) {
        qp.errs.append("Illegal param= 'time' must be valid ISO Duration ");
        return false;
      }
    }

    if (level2) {
      qp.vars = null; // level2 can't select vars
    } else if (qp.vars == null) { // level 3 with no vars
      qp.errs.append("No vars selected ");
      return false;
    } else if (qp.vars.get(0).contains("/")) { // remove desc from vars
      ArrayList<String> tmp = new ArrayList<String>();
      for (String var : qp.vars) {
        tmp.add(var.replaceFirst("/.*", ""));
      }
      qp.vars = tmp;
    }
    return true;
  }

  // create catalog Header
  private Boolean createHeader(
      DatasetRepository.RadarType radarType,
      QueryParams qp,
      String pathInfo,
      Map<String, Object> model)
      throws IOException {

    Boolean level2 = pathInfo.contains("level2");
    int level = (level2) ? 2 : 3;
    StringBuffer str = new StringBuffer();
    str.append("Radar Level").append(level).append(" datasets in near real time");
    model.put("name", str.toString());
    str.setLength(0);
    str.append("/thredds/dodsC/").append(pathInfo).append("/");
    model.put("base", str.toString());
    str.setLength(0);
    str.append("RadarLevel").append(level).append(" datasets for available stations and times");
    model.put("dname", str.toString());
    str.setLength(0);
    str.append("accept=").append(qp.acceptType).append("&");
    if (!level2 && qp.vars != null) { // add vars
      str.append("var=");
      for (int i = 0; i < qp.vars.size(); i++) {
        str.append(qp.vars.get(i));
        if (i < qp.vars.size() - 1) {
          str.append(",");
        }
      }
      str.append("&");
    }
    // use all stations
    if (qp.stns.get(0).toUpperCase().equals("ALL")) {
      str.append("stn=ALL&");
    } else if (qp.hasStns) {
      for (String station : qp.stns) {
        str.append("stn=").append(station).append("&");
      }
    } else if (qp.hasBB) {
      str.append("south=").append(qp.south).append("&north=").append(qp.north).append("&");
      str.append("west=").append(qp.west).append("&east=").append(qp.east).append("&");
    }

    // no time given
    if (qp.time_latest == 1) {
      // str.deleteCharAt( str.length() -1);
      str.append("time=present");
    } else if (qp.hasDateRange) {
      if (qp.time_start.getDate() == null
          || qp.time_start.isBlank()
          || qp.time_end.getDate() == null
          || qp.time_end.isBlank()) {
        str.append("time_start=").append(qp.time_start.toString());
        str.append("&time_end=").append(qp.time_end.toString());
        qp.errs.append("need ISO time format ");
        return false;
      } else {
        str.append("time_start=").append(qp.time_start.toDateTimeStringISO());
        str.append("&time_end=").append(qp.time_end.toDateTimeStringISO());
      }
    } else if (qp.time.isPresent()) {
      str.append("time=present");
    } else if (qp.hasTimePoint) {
      if (qp.time.getDate() == null || qp.time.isBlank()) {
        str.append("time=").append(qp.time.toString());
        qp.errs.append("need ISO time format ");
        return false;
      } else {
        str.append("time=").append(qp.time.toDateTimeStringISO());
      }
    }
    model.put("ID", str.toString());

    if (level2) {
      model.put("type", "NEXRAD2");
    } else if (radarType.equals(DatasetRepository.RadarType.nexrad)) {
      model.put("type", "NIDS");
    } else {
      model.put("type", "TDWR");
    }

    // at this point must have stations
    if (RadarServerUtil.isStationListEmpty(qp.stns, radarType)) {
      qp.errs.append("No station(s) meet query criteria ");
      return false;
    }
    return true;
  }

  /*
      Final Output format, save information in DatasetEntry de
       <dataset name="Level2_KFTG_20100121_0000.ar2v" ID="735519521"
          urlPath="KFTG/20100121/Level2_KFTG_20100121_0000.ar2v">
          <date type="start of ob">2010-01-21T00:00:00</date>
        </dataset>
  */
  private Boolean processQuery(
      String dataset, QueryParams qp, String var, List<DatasetEntry> entries)
      throws RadarServerException {

    Boolean getAllTimes = true;
    String yyyymmddStart = null;
    String yyyymmddEnd = null;
    String dateStart = null;
    String dateEnd = null;
    try {
      if (!qp.time_start.equals(epicDateType)) {
        getAllTimes = false;
        yyyymmddStart = qp.time_start.toDateString();
        yyyymmddStart = yyyymmddStart.replace("-", "");
        yyyymmddEnd = qp.time_end.toDateString();
        yyyymmddEnd = yyyymmddEnd.replace("-", "");
        dateStart = yyyymmddStart + "_" + RadarServerUtil.hhmm(qp.time_start.toDateTimeString());
        dateEnd = yyyymmddEnd + "_" + RadarServerUtil.hhmm(qp.time_end.toDateTimeString());
      }

      RadarDatasetCollection rdc = DatasetRepository.getRadarDatasetCollection(dataset, var);
      if (rdc == null) {
        qp.errs.append("Invalid dataset =").append(dataset);
        qp.errs.append(" or var =").append(var);
        return false;
      }
      StringBuffer time = new StringBuffer();
      StringBuffer product = new StringBuffer();
      StringBuffer url = new StringBuffer();
      boolean isLevel2 = dataset.contains("level2");
      String type = (isLevel2 ? "Level2" : "Level3");
      String suffix = (isLevel2 ? ".ar2v" : ".nids");
      Calendar cal = Calendar.getInstance(java.util.TimeZone.getTimeZone("GMT"));
      Date now = cal.getTime();
      String currentDay = dateFormat.format(now);

      for (String stn : qp.stns) {
        RadarStationCollection rsc = rdc.queryStation(stn, currentDay);
        if (rsc == null) continue;
        for (String day : rsc.getDays()) {
          // check for valid day
          if (!getAllTimes && !RadarServerUtil.isValidDay(day, yyyymmddStart, yyyymmddEnd))
            continue;
          ArrayList<String> tal;
          if (rdc.isCaseStudy()) { //
            tal = rsc.getHourMinute("all");
            for (String prod : tal) {
              // check times
              if (!getAllTimes && !RadarServerUtil.isValidDate(prod, dateStart, dateEnd)) continue;
              // save this entry
              DatasetEntry de = new DatasetEntry();
              int idx = prod.indexOf('/');
              if (idx > 0) {
                de.setName(prod.substring(idx + 1));
              } else {
                de.setName(prod);
              }
              de.setID(Integer.toString(prod.hashCode()));
              url.setLength(0);
              url.append(stn).append("/");
              if (var != null) {
                url.append(var).append("/");
              }
              url.append(prod);
              de.setUrlPath(url.toString());
              de.setDate(RadarServerUtil.getObTimeISO(prod));
              entries.add(de);
            }
            continue;
          } else {
            tal = rsc.getHourMinute(day);
          }
          if (tal == null) continue;
          for (String hm : tal) {
            time.setLength(0);
            time.append(day).append("_").append(hm);
            if (!getAllTimes && !RadarServerUtil.isValidDate(time.toString(), dateStart, dateEnd))
              continue;

            // save this entry
            DatasetEntry de = new DatasetEntry();

            product.setLength(0);
            product.append(type).append("_").append(rsc.getStnName()).append("_");
            if (!isLevel2) product.append(var).append("_");
            product.append(day).append("_").append(hm).append(suffix);

            de.setName(product.toString());
            de.setID(Integer.toString(product.toString().hashCode()));
            url.setLength(0);
            if (!isLevel2) {
              url.append(var).append("/");
            }
            url.append(rsc.getStnName())
                .append("/")
                .append(day)
                .append("/")
                .append(product.toString());
            de.setUrlPath(url.toString());
            de.setDate(RadarServerUtil.getObTimeISO(product.toString()));
            entries.add(de);
            if (qp.hasTimePoint) break;
          }
          if (qp.hasTimePoint) break;
        }
      }
      return true;
    } catch (Throwable e) {
      log.error("Invalid dataset =" + dataset + " or var =" + var, e);
      log.info(
          "handleRequestInternal(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, -1));
      throw new RadarServerException("Invalid dataset =" + dataset + " or var =" + var);
    }
  }

  /*
   * Used to store the information about a dataset
   */
  public class DatasetEntry {

    private String name;

    private String ID;

    private String urlPath;

    private String date;

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }

    public String getID() {
      return ID;
    }

    public void setID(String ID) {
      this.ID = ID;
    }

    public String getUrlPath() {
      return urlPath;
    }

    public void setUrlPath(String urlPath) {
      this.urlPath = urlPath;
    }

    public String getDate() {
      return date;
    }

    public void setDate(String date) {
      this.date = date;
    }
  }
}
public class ServletUtil {

  public static final String CONTENT_TEXT = "text/plain; charset=utf-8";

  // bogus status returns for our logging
  public static final int STATUS_CLIENT_ABORT = 1000;
  public static final int STATUS_FORWARDED = 1001;
  public static final int STATUS_FORWARD_FAILURE = 1002;

  private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ServletUtil.class);
  private static boolean isDebugInit = false;

  private static String contextPath = null;
  private static String rootPath = null;
  private static String contentPath = null;

  /**
   * @param context the Servlet context.
   * @deprecated Now handled in TdsContext.init().
   */
  public static void initContext(ServletContext context) {
    //    setContextPath(context);
    if (contextPath == null) {
      // Servlet 2.5 allows the following.
      // contextPath = servletContext.getContextPath();
      String tmpContextPath =
          context.getInitParameter("ContextPath"); // cannot be overridden in the ThreddsConfig file
      if (tmpContextPath == null) tmpContextPath = "thredds";
      contextPath = "/" + tmpContextPath;
    }
    //    setRootPath(context);
    if (rootPath == null) {
      rootPath = context.getRealPath("/");
      rootPath = rootPath.replace('\\', '/');
    }

    //    setContentPath();
    if (contentPath == null) {
      String tmpContentPath = "../../content" + getContextPath() + "/";
      File cf = new File(getRootPath(), tmpContentPath);
      try {
        contentPath = cf.getCanonicalPath() + "/";
        contentPath = contentPath.replace('\\', '/');
      } catch (IOException e) {
        throw new RuntimeException(e.getMessage());
      }
    }

    //    initDebugging(context);
    initDebugging(context);
  }

  public static void setContextPath(String newContextPath) {
    contextPath = newContextPath;
  }

  public static void setRootPath(String newRootPath) {
    rootPath = newRootPath;
  }

  public static void setContentPath(String newContentPath) {
    contentPath = newContentPath;
    if (!contentPath.endsWith("/")) contentPath = contentPath + "/";
  }

  public static void initDebugging(ServletContext webapp) {
    if (isDebugInit) return;
    isDebugInit = true;

    String debugOn = webapp.getInitParameter("DebugOn");
    if (debugOn != null) {
      StringTokenizer toker = new StringTokenizer(debugOn);
      while (toker.hasMoreTokens()) Debug.set(toker.nextToken(), true);
    }
  }

  /**
   * Return the real path on the servers file system that corresponds to the root document ("/") on
   * the given servlet.
   *
   * @return the real path on the servers file system that corresponds to the root document ("/") on
   *     the given servlet.
   */
  public static String getRootPath() {
    return rootPath;
  }

  /**
   * Return the context path for the given servlet. Note - ToDo: Why not just use
   * ServletContext.getServletContextName()?
   *
   * @return the context path for the given servlet.
   */
  public static String getContextPath() {
    return contextPath;
  }

  /**
   * Return the content path for the given servlet.
   *
   * @return the content path for the given servlet.
   */
  public static String getContentPath() {
    return contentPath;
  }

  /**
   * Return the default/initial content path for the given servlet. The content of which is copied
   * to the content path when the web app is first installed.
   *
   * @return the default/initial content path for the given servlet.
   */
  public static String getInitialContentPath() {
    return getRootPath() + "/WEB-INF/altContent/startup/";
  }

  /**
   * Return the file path dealing with leading and trailing path seperators (which must be a slash
   * ("/")) for the given directory and file paths.
   *
   * <p>Note: Dealing with path strings is fragile. ToDo: Switch from using path strings to
   * java.io.Files.
   *
   * @param dirPath the directory path.
   * @param filePath the file path.
   * @return a full file path with the given directory and file paths.
   */
  public static String formFilename(String dirPath, String filePath) {
    if ((dirPath == null) || (filePath == null)) return null;

    if (filePath.startsWith("/")) filePath = filePath.substring(1);

    return dirPath.endsWith("/") ? dirPath + filePath : dirPath + "/" + filePath;
  }

  /**
   * Handle a request for a raw/static file (i.e., not a catalog or dataset request).
   *
   * <p>Look in the content (user) directory then the root (distribution) directory for a file that
   * matches the given path and, if found, return it as the content of the HttpServletResponse. If
   * the file is forbidden (i.e., the path contains a "..", "WEB-INF", or "META-INF" directory),
   * send a HttpServletResponse.SC_FORBIDDEN response. If no file matches the request (including an
   * "index.html" file if the path ends in "/"), send an HttpServletResponse.SC_NOT_FOUND..
   *
   * <p>
   *
   * <ol>
   *   <li>Make sure the path does not contain ".." directories.
   *   <li>Make sure the path does not contain "WEB-INF" or "META-INF".
   *   <li>Check for requested file in the content directory (if the path is a directory, make sure
   *       the path ends with "/" and check for an "index.html" file).
   *   <li>Check for requested file in the root directory (if the path is a directory, make sure the
   *       path ends with "/" and check for an "index.html" file). </ol
   *
   * @param path the requested path
   * @param servlet the servlet handling the request
   * @param req the HttpServletRequest
   * @param res the HttpServletResponse
   * @throws IOException if can't complete request due to IO problems.
   */
  public static void handleRequestForRawFile(
      String path, HttpServlet servlet, HttpServletRequest req, HttpServletResponse res)
      throws IOException {
    // Don't allow ".." directories in path.
    if (path.indexOf("/../") != -1
        || path.equals("..")
        || path.startsWith("../")
        || path.endsWith("/..")) {
      res.sendError(HttpServletResponse.SC_FORBIDDEN, "Path cannot contain \"..\" directory.");
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_FORBIDDEN, -1));
      return;
    }

    // Don't allow access to WEB-INF or META-INF directories.
    String upper = path.toUpperCase();
    if (upper.indexOf("WEB-INF") != -1 || upper.indexOf("META-INF") != -1) {
      res.sendError(
          HttpServletResponse.SC_FORBIDDEN, "Path cannot contain \"WEB-INF\" or \"META-INF\".");
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_FORBIDDEN, -1));
      return;
    }

    // Find a regular file
    File regFile = null;
    // Look in content directory for regular file.
    File cFile = new File(ServletUtil.formFilename(getContentPath(), path));
    if (cFile.exists()) {
      if (cFile.isDirectory()) {
        if (!path.endsWith("/")) {
          String newPath = req.getRequestURL().append("/").toString();
          ServletUtil.sendPermanentRedirect(newPath, req, res);
        }
        // If request path is a directory, check for index.html file.
        cFile = new File(cFile, "index.html");
        if (cFile.exists() && !cFile.isDirectory()) regFile = cFile;
      }
      // If not a directory, use this file.
      else regFile = cFile;
    }

    if (regFile == null) {
      // Look in root directory.
      File rFile = new File(ServletUtil.formFilename(getRootPath(), path));
      if (rFile.exists()) {
        if (rFile.isDirectory()) {
          if (!path.endsWith("/")) {
            String newPath = req.getRequestURL().append("/").toString();
            ServletUtil.sendPermanentRedirect(newPath, req, res);
          }
          rFile = new File(rFile, "index.html");
          if (rFile.exists() && !rFile.isDirectory()) regFile = rFile;
        } else regFile = rFile;
      }
    }

    if (regFile == null) {
      res.sendError(HttpServletResponse.SC_NOT_FOUND); // 404
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, -1));
      return;
    }

    ServletUtil.returnFile(servlet, req, res, regFile, null);
  }

  /**
   * Handle an explicit request for a content directory file (path must start with "/content/".
   *
   * <p>Note: As these requests will show the configuration files for the server, these requests
   * should be covered by security constraints.
   *
   * <p>
   *
   * <ol>
   *   <li>Make sure the path does not contain ".." directories.
   *   <li>Check for the requested file in the content directory. </ol
   *
   * @param path the requested path (must start with "/content/")
   * @param servlet the servlet handling the request
   * @param req the HttpServletRequest
   * @param res the HttpServletResponse
   * @throws IOException if can't complete request due to IO problems.
   */
  public static void handleRequestForContentFile(
      String path, HttpServlet servlet, HttpServletRequest req, HttpServletResponse res)
      throws IOException {
    handleRequestForContentOrRootFile("/content/", path, servlet, req, res);
  }

  /**
   * Handle an explicit request for a root directory file (path must start with "/root/".
   *
   * <p>Note: As these requests will show the configuration files for the server, these requests
   * should be covered by security constraints.
   *
   * <p>
   *
   * <ol>
   *   <li>Make sure the path does not contain ".." directories.
   *   <li>Check for the requested file in the root directory. </ol
   *
   * @param path the requested path (must start with "/root/")
   * @param servlet the servlet handling the request
   * @param req the HttpServletRequest
   * @param res the HttpServletResponse
   * @throws IOException if can't complete request due to IO problems.
   */
  public static void handleRequestForRootFile(
      String path, HttpServlet servlet, HttpServletRequest req, HttpServletResponse res)
      throws IOException {
    handleRequestForContentOrRootFile("/root/", path, servlet, req, res);
  }

  /**
   * Convenience routine used by handleRequestForContentFile() and handleRequestForRootFile().
   *
   * @param pathPrefix
   * @param path
   * @param servlet
   * @param req request
   * @param res response
   * @throws IOException on IO error
   */
  private static void handleRequestForContentOrRootFile(
      String pathPrefix,
      String path,
      HttpServlet servlet,
      HttpServletRequest req,
      HttpServletResponse res)
      throws IOException {
    if (!pathPrefix.equals("/content/") && !pathPrefix.equals("/root/")) {
      log.error(
          "handleRequestForContentFile(): The path prefix <"
              + pathPrefix
              + "> must be \"/content/\" or \"/root/\".");
      throw new IllegalArgumentException("Path prefix must be \"/content/\" or \"/root/\".");
    }

    if (!path.startsWith(pathPrefix)) {
      log.error(
          "handleRequestForContentFile(): path <"
              + path
              + "> must start with \""
              + pathPrefix
              + "\".");
      throw new IllegalArgumentException("Path must start with \"" + pathPrefix + "\".");
    }

    // Don't allow ".." directories in path.
    if (path.indexOf("/../") != -1
        || path.equals("..")
        || path.startsWith("../")
        || path.endsWith("/..")) {
      res.sendError(HttpServletResponse.SC_FORBIDDEN, "Path cannot contain \"..\" directory.");
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_FORBIDDEN, -1));
      return;
    }

    // Find the requested file.
    File file =
        new File(
            ServletUtil.formFilename(getContentPath(), path.substring(pathPrefix.length() - 1)));
    if (file.exists()) {
      // Do not allow request for a directory.
      if (file.isDirectory()) {
        if (!path.endsWith("/")) {
          String redirectPath = req.getRequestURL().append("/").toString();
          ServletUtil.sendPermanentRedirect(redirectPath, req, res);
          return;
        }

        int i = HtmlWriter.getInstance().writeDirectory(res, file, path);
        int status = i == 0 ? HttpServletResponse.SC_NOT_FOUND : HttpServletResponse.SC_OK;
        log.info(UsageLog.closingMessageForRequestContext(status, i));

        return;
      }

      // Return the requested file.
      ServletUtil.returnFile(servlet, req, res, file, null);
    } else {
      // Requested file not found.
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, -1));
      res.sendError(HttpServletResponse.SC_NOT_FOUND); // 404
    }
  }

  /**
   * Send a permanent redirect (HTTP status 301 "Moved Permanently") response with the given target
   * path.
   *
   * <p>The given target path may be relative or absolute. If it is relative, it will be resolved
   * against the request URL.
   *
   * @param targetPath the path to which the client is redirected.
   * @param req the HttpServletRequest
   * @param res the HttpServletResponse
   * @throws IOException if can't write the response.
   */
  public static void sendPermanentRedirect(
      String targetPath, HttpServletRequest req, HttpServletResponse res) throws IOException {
    // Absolute URL needed so resolve the target path against the request URL.
    URI uri;
    try {
      uri = new URI(req.getRequestURL().toString());
    } catch (URISyntaxException e) {
      log.error(
          "sendPermanentRedirect(): Bad syntax on request URL <" + req.getRequestURL() + ">.", e);
      log.info(
          "sendPermanentRedirect(): "
              + UsageLog.closingMessageForRequestContext(
                  HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 0));
      if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      return;
    }
    String absolutePath = uri.resolve(targetPath).toString();
    absolutePath = res.encodeRedirectURL(absolutePath);

    res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
    res.addHeader("Location", absolutePath);

    String title = "Permanently Moved - 301";
    String body =
        new StringBuilder()
            .append("<p>")
            .append("The requested URL <")
            .append(req.getRequestURL())
            .append("> has been permanently moved (HTTP status code 301).")
            .append(" Instead, please use the following URL: <a href=\"")
            .append(absolutePath)
            .append("\">")
            .append(absolutePath)
            .append("</a>.")
            .append("</p>")
            .toString();
    String htmlResp =
        new StringBuilder()
            .append(HtmlWriter.getInstance().getHtmlDoctypeAndOpenTag())
            .append("<head><title>")
            .append(title)
            .append("</title></head><body>")
            .append("<h1>")
            .append(title)
            .append("</h1>")
            .append(body)
            .append("</body></html>")
            .toString();

    log.info("sendPermanentRedirect(): redirect to " + absolutePath);
    log.info(
        "sendPermanentRedirect(): "
            + UsageLog.closingMessageForRequestContext(
                HttpServletResponse.SC_MOVED_PERMANENTLY, htmlResp.length()));

    // Write the catalog out.
    PrintWriter out = res.getWriter();
    res.setContentType("text/html");
    out.print(htmlResp);
    out.flush();
  }

  /**
   * Write a file to the response stream.
   *
   * @param servlet called from here
   * @param contentPath file root path
   * @param path file path reletive to the root
   * @param req the request
   * @param res the response
   * @param contentType content type, or null
   * @throws IOException on write error
   */
  public static void returnFile(
      HttpServlet servlet,
      String contentPath,
      String path,
      HttpServletRequest req,
      HttpServletResponse res,
      String contentType)
      throws IOException {

    String filename = ServletUtil.formFilename(contentPath, path);

    log.debug("returnFile(): returning file <" + filename + ">.");
    // No file, nothing to view
    if (filename == null) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      res.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
    }

    // dontallow ..
    if (filename.indexOf("..") != -1) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_FORBIDDEN, 0));
      res.sendError(HttpServletResponse.SC_FORBIDDEN);
      return;
    }

    // dont allow access to WEB-INF or META-INF
    String upper = filename.toUpperCase();
    if (upper.indexOf("WEB-INF") != -1 || upper.indexOf("META-INF") != -1) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_FORBIDDEN, 0));
      res.sendError(HttpServletResponse.SC_FORBIDDEN);
      return;
    }

    returnFile(servlet, req, res, new File(filename), contentType);
  }

  private static FileCacheRaf fileCacheRaf;

  public static void setFileCache(FileCacheRaf fileCache) {
    fileCacheRaf = fileCache;
  }

  public static FileCacheRaf getFileCache() {
    return fileCacheRaf;
  }

  /**
   * Write a file to the response stream. Handles Range requests.
   *
   * @param servlet called from here
   * @param req the request
   * @param res the response
   * @param file to serve
   * @param contentType content type, if null, will try to guess
   * @throws IOException on write error
   */
  public static void returnFile(
      HttpServlet servlet,
      HttpServletRequest req,
      HttpServletResponse res,
      File file,
      String contentType)
      throws IOException {

    // No file, nothing to view
    if (file == null) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      res.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
    }

    // check that it exists
    if (!file.exists()) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      res.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
    }

    // not a directory
    if (!file.isFile()) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, 0));
      res.sendError(HttpServletResponse.SC_BAD_REQUEST);
      return;
    }

    // Set the type of the file
    String filename = file.getPath();
    if (null == contentType) {
      if (filename.endsWith(".html")) contentType = "text/html; charset=iso-8859-1";
      else if (filename.endsWith(".xml")) contentType = "text/xml; charset=iso-8859-1";
      else if (filename.endsWith(".txt") || (filename.endsWith(".log"))) contentType = CONTENT_TEXT;
      else if (filename.indexOf(".log.") > 0) contentType = CONTENT_TEXT;
      else if (filename.endsWith(".nc")) contentType = "application/x-netcdf";
      else contentType = servlet.getServletContext().getMimeType(filename);

      if (contentType == null) contentType = "application/octet-stream";
    }

    returnFile(req, res, file, contentType);
  }

  /**
   * Write a file to the response stream. Handles Range requests.
   *
   * @param req request
   * @param res response
   * @param file must exists and not be a directory
   * @param contentType must not be null
   * @throws IOException or error
   */
  public static void returnFile(
      HttpServletRequest req, HttpServletResponse res, File file, String contentType)
      throws IOException {
    res.setContentType(contentType);

    // see if its a Range Request
    boolean isRangeRequest = false;
    long startPos = 0, endPos = Long.MAX_VALUE;
    String rangeRequest = req.getHeader("Range");
    if (rangeRequest != null) { // bytes=12-34 or bytes=12-
      int pos = rangeRequest.indexOf("=");
      if (pos > 0) {
        int pos2 = rangeRequest.indexOf("-");
        if (pos2 > 0) {
          String startString = rangeRequest.substring(pos + 1, pos2);
          String endString = rangeRequest.substring(pos2 + 1);
          startPos = Long.parseLong(startString);
          if (endString.length() > 0) endPos = Long.parseLong(endString) + 1;
          isRangeRequest = true;
        }
      }
    }

    // set content length
    long fileSize = file.length();
    long contentLength = fileSize;
    if (isRangeRequest) {
      endPos = Math.min(endPos, fileSize);
      contentLength = endPos - startPos;
    }

    if (contentLength > Integer.MAX_VALUE)
      res.addHeader(
          "Content-Length", Long.toString(contentLength)); // allow content length > MAX_INT
    else res.setContentLength((int) contentLength); // note HEAD only allows this

    String filename = file.getPath();
    boolean debugRequest = Debug.isSet("returnFile");
    if (debugRequest)
      log.debug(
          "returnFile(): filename = "
              + filename
              + " contentType = "
              + contentType
              + " contentLength = "
              + contentLength);

    // indicate we allow Range Requests
    res.addHeader("Accept-Ranges", "bytes");

    if (req.getMethod().equals("HEAD")) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, 0));
      return;
    }

    try {

      if (isRangeRequest) {
        // set before content is sent
        res.addHeader("Content-Range", "bytes " + startPos + "-" + (endPos - 1) + "/" + fileSize);
        res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

        FileCacheRaf.Raf craf = null;
        try {
          craf = fileCacheRaf.acquire(filename);
          IO.copyRafB(
              craf.getRaf(), startPos, contentLength, res.getOutputStream(), new byte[60000]);
          log.info(
              "returnFile(): "
                  + UsageLog.closingMessageForRequestContext(
                      HttpServletResponse.SC_PARTIAL_CONTENT, contentLength));
          return;
        } finally {
          if (craf != null) fileCacheRaf.release(craf);
        }
      }

      // Return the file
      ServletOutputStream out = res.getOutputStream();
      IO.copyFileB(file, out, 60000);
      res.flushBuffer();
      out.close();
      if (debugRequest) log.debug("returnFile(): returnFile ok = " + filename);
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, contentLength));
    }

    // @todo Split up this exception handling: those from file access vs those from dealing with
    // response
    //       File access: catch and res.sendError()
    //       response: don't catch (let bubble up out of doGet() etc)
    catch (FileNotFoundException e) {
      log.error("returnFile(): FileNotFoundException= " + filename);
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_NOT_FOUND);
    } catch (java.net.SocketException e) {
      log.info("returnFile(): SocketException sending file: " + filename + " " + e.getMessage());
      log.info("returnFile(): " + UsageLog.closingMessageForRequestContext(STATUS_CLIENT_ABORT, 0));
    } catch (IOException e) {
      String eName =
          e.getClass().getName(); // dont want compile time dependency on ClientAbortException
      if (eName.equals("org.apache.catalina.connector.ClientAbortException")) {
        log.info(
            "returnFile(): ClientAbortException while sending file: "
                + filename
                + " "
                + e.getMessage());
        log.info(
            "returnFile(): " + UsageLog.closingMessageForRequestContext(STATUS_CLIENT_ABORT, 0));
        return;
      }

      log.error("returnFile(): IOException (" + e.getClass().getName() + ") sending file ", e);
      log.error(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      if (!res.isCommitted())
        res.sendError(HttpServletResponse.SC_NOT_FOUND, "Problem sending file: " + e.getMessage());
    }
  }

  /**
   * Send given content string as the HTTP response.
   *
   * @param contents the string to return as the HTTP response.
   * @param res the HttpServletResponse
   * @throws IOException if an I/O error occurs while writing the response.
   */
  public static void returnString(String contents, HttpServletResponse res) throws IOException {

    try {
      ServletOutputStream out = res.getOutputStream();
      IO.copy(new ByteArrayInputStream(contents.getBytes()), out);
      log.info(
          UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, contents.length()));
    } catch (IOException e) {
      log.error(" IOException sending string: ", e);
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      res.sendError(HttpServletResponse.SC_NOT_FOUND, "Problem sending string: " + e.getMessage());
    }
  }

  /**
   * Return the request URL relative to the server (i.e., starting with the context path).
   *
   * @param req request
   * @return URL relative to the server
   */
  public static String getReletiveURL(HttpServletRequest req) {
    return req.getContextPath() + req.getServletPath() + req.getPathInfo();
  }

  /**
   * Forward this request to the CatalogServices servlet ("/catalog.html").
   *
   * @param req request
   * @param res response
   * @throws IOException on IO error
   * @throws ServletException other error
   */
  public static void forwardToCatalogServices(HttpServletRequest req, HttpServletResponse res)
      throws IOException, ServletException {

    String reqs = "catalog=" + getReletiveURL(req);
    String query = req.getQueryString();
    if (query != null) reqs = reqs + "&" + query;
    log.info("forwardToCatalogServices(): request string = \"/catalog.html?" + reqs + "\"");

    // dispatch to CatalogHtml servlet
    RequestForwardUtils.forwardRequestRelativeToCurrentContext("/catalog.html?" + reqs, req, res);
  }

  public static boolean saveFile(
      HttpServlet servlet,
      String contentPath,
      String path,
      HttpServletRequest req,
      HttpServletResponse res) {

    // @todo Need to use logServerAccess() below here.
    boolean debugRequest = Debug.isSet("SaveFile");
    if (debugRequest) log.debug(" saveFile(): path= " + path);

    String filename = contentPath + path; // absolute path
    File want = new File(filename);

    // backup current version if it exists
    int version = getBackupVersion(want.getParent(), want.getName());
    String fileSave = filename + "~" + version;
    File file = new File(filename);
    if (file.exists()) {
      try {
        IO.copyFile(filename, fileSave);
      } catch (IOException e) {
        log.error(
            "saveFile(): Unable to save copy of file "
                + filename
                + " to "
                + fileSave
                + "\n"
                + e.getMessage());
        return false;
      }
    }

    // save new file
    try {
      OutputStream out = new BufferedOutputStream(new FileOutputStream(filename));
      IO.copy(req.getInputStream(), out);
      out.close();
      if (debugRequest) log.debug("saveFile(): ok= " + filename);
      res.setStatus(HttpServletResponse.SC_CREATED);
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_CREATED, -1));
      return true;
    } catch (IOException e) {
      log.error(
          "saveFile(): Unable to PUT file " + filename + " to " + fileSave + "\n" + e.getMessage());
      return false;
    }
  }

  private static int getBackupVersion(String dirName, String fileName) {
    int maxN = 0;
    File dir = new File(dirName);
    if (!dir.exists()) return -1;

    String[] files = dir.list();
    if (null == files) return -1;

    for (String name : files) {
      if (name.indexOf(fileName) < 0) continue;
      int pos = name.indexOf('~');
      if (pos < 0) continue;
      String ver = name.substring(pos + 1);
      int n = 0;
      try {
        n = Integer.parseInt(ver);
      } catch (NumberFormatException e) {
        log.error("Format Integer error on backup filename= " + ver);
      }
      maxN = Math.max(n, maxN);
    }
    return maxN + 1;
  }

  public static boolean copyDir(String fromDir, String toDir) throws IOException {
    File contentFile = new File(toDir + ".INIT");
    if (!contentFile.exists()) {
      IO.copyDirTree(fromDir, toDir);
      contentFile.createNewFile();
      return true;
    }
    return false;
  }

  /**
   * ************************************************************************ Sends an error to the
   * client.
   *
   * @param t The exception that caused the problem.
   * @param res The <code>HttpServletResponse</code> for the client.
   */
  public static void handleException(Throwable t, HttpServletResponse res) {
    try {
      String message = t.getMessage();
      if (message == null) message = "NULL message " + t.getClass().getName();
      if (Debug.isSet("trustedMode")) { // security issue: only show stack if trusted
        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(bs);
        t.printStackTrace(ps);
        message = new String(bs.toByteArray());
      }
      log.info(
          UsageLog.closingMessageForRequestContext(
              HttpServletResponse.SC_BAD_REQUEST, message.length()));
      log.error("handleException", t);
      t.printStackTrace(); // debugging - log.error not showing stack trace !!
      if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_BAD_REQUEST, message);

    } catch (Throwable e) {
      log.error("handleException() had problem reporting Exception", e);
      t.printStackTrace();
    }
  }

  public static void showServerInfo(PrintStream out) {
    out.println("Server Info");
    out.println(
        " getDocumentBuilderFactoryVersion(): "
            + XMLEntityResolver.getDocumentBuilderFactoryVersion());
    out.println();

    Properties sysp = System.getProperties();
    Enumeration e = sysp.propertyNames();
    List<String> list = Collections.list(e);
    Collections.sort(list);

    out.println("System Properties:");
    for (String name : list) {
      String value = System.getProperty(name);
      out.println("  " + name + " = " + value);
    }
    out.println();
  }

  public static void showServletInfo(HttpServlet servlet, PrintStream out) {
    out.println("Servlet Info");
    out.println(" getServletName(): " + servlet.getServletName());
    out.println(" getRootPath(): " + getRootPath());
    out.println(" Init Parameters:");
    Enumeration params = servlet.getInitParameterNames();
    while (params.hasMoreElements()) {
      String name = (String) params.nextElement();
      out.println("  " + name + ": " + servlet.getInitParameter(name));
    }
    out.println();

    ServletContext context = servlet.getServletContext();
    out.println("Context Info");

    try {
      out.println(" context.getResource('/'): " + context.getResource("/"));
    } catch (java.net.MalformedURLException e) {
    } // cant happen
    out.println(" context.getServerInfo(): " + context.getServerInfo());
    out.println("  name: " + getServerInfoName(context.getServerInfo()));
    out.println("  version: " + getServerInfoVersion(context.getServerInfo()));

    out.println(" context.getInitParameterNames():");
    params = context.getInitParameterNames();
    while (params.hasMoreElements()) {
      String name = (String) params.nextElement();
      out.println("  " + name + ": " + context.getInitParameter(name));
    }

    out.println(" context.getAttributeNames():");
    params = context.getAttributeNames();
    while (params.hasMoreElements()) {
      String name = (String) params.nextElement();
      out.println("  context.getAttribute(\"" + name + "\"): " + context.getAttribute(name));
    }

    out.println();
  }

  /**
   * Show the pieces of the request, for debugging
   *
   * @param req the HttpServletRequest
   * @return parsed request
   */
  public static String getRequestParsed(HttpServletRequest req) {
    return req.getRequestURI()
        + " = "
        + req.getContextPath()
        + "(context), "
        + req.getServletPath()
        + "(servletPath), "
        + req.getPathInfo()
        + "(pathInfo), "
        + req.getQueryString()
        + "(query)";
  }

  /**
   * This is the server part, eg http://motherlode:8080
   *
   * @param req the HttpServletRequest
   * @return request server
   */
  public static String getRequestServer(HttpServletRequest req) {
    return req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort();
  }

  /**
   * This is everything except the query string
   *
   * @param req the HttpServletRequest
   * @return parsed request base
   */
  public static String getRequestBase(HttpServletRequest req) {
    // return "http://"+req.getServerName()+":"+ req.getServerPort()+req.getRequestURI();
    return req.getRequestURL().toString();
  }

  /**
   * The request base as a URI
   *
   * @param req the HttpServletRequest
   * @return parsed request as a URI
   */
  public static URI getRequestURI(HttpServletRequest req) {
    try {
      return new URI(getRequestBase(req));
    } catch (URISyntaxException e) {
      e.printStackTrace();
      return null;
    }
  }

  /**
   * servletPath + pathInfo
   *
   * @param req the HttpServletRequest
   * @return parsed request servletPath + pathInfo
   */
  public static String getRequestPath(HttpServletRequest req) {
    StringBuffer buff = new StringBuffer();
    if (req.getServletPath() != null) buff.append(req.getServletPath());
    if (req.getPathInfo() != null) buff.append(req.getPathInfo());
    return buff.toString();
  }

  /**
   * The entire request including query string
   *
   * @param req the HttpServletRequest
   * @return entire parsed request
   */
  public static String getRequest(HttpServletRequest req) {
    String query = req.getQueryString();
    return getRequestBase(req) + (query == null ? "" : "?" + query);
  }

  /**
   * Return the value of the given parameter for the given request. Should only be used if the
   * parameter is known to only have one value. If used on a multi-valued parameter, the first value
   * is returned.
   *
   * @param req the HttpServletRequest
   * @param paramName the name of the parameter to find.
   * @return the value of the given parameter for the given request.
   */
  public static String getParameterIgnoreCase(HttpServletRequest req, String paramName) {
    Enumeration e = req.getParameterNames();
    while (e.hasMoreElements()) {
      String s = (String) e.nextElement();
      if (s.equalsIgnoreCase(paramName)) return req.getParameter(s);
    }
    return null;
  }

  /**
   * Return the values of the given parameter (ignoring case) for the given request.
   *
   * @param req the HttpServletRequest
   * @param paramName the name of the parameter to find.
   * @return the values of the given parameter for the given request.
   */
  public static String[] getParameterValuesIgnoreCase(HttpServletRequest req, String paramName) {
    Enumeration e = req.getParameterNames();
    while (e.hasMoreElements()) {
      String s = (String) e.nextElement();
      if (s.equalsIgnoreCase(paramName)) return req.getParameterValues(s);
    }
    return null;
  }

  public static String getFileURL(String filename) {
    filename = filename.replace('\\', '/');
    filename = StringUtil.replace(filename, ' ', "+");
    return "file:" + filename;
  }

  /**
   * Show details about the request
   *
   * @param servlet used to get teh servlet context, may be null
   * @param req the request
   * @return string showing the details of the request.
   */
  public static String showRequestDetail(HttpServlet servlet, HttpServletRequest req) {
    StringBuilder sbuff = new StringBuilder();

    sbuff.append("Request Info\n");
    sbuff.append(" req.getServerName(): ").append(req.getServerName()).append("\n");
    sbuff.append(" req.getServerPort(): ").append(req.getServerPort()).append("\n");
    sbuff.append(" req.getContextPath:").append(req.getContextPath()).append("\n");
    sbuff.append(" req.getServletPath:").append(req.getServletPath()).append("\n");
    sbuff.append(" req.getPathInfo:").append(req.getPathInfo()).append("\n");
    sbuff.append(" req.getQueryString:").append(req.getQueryString()).append("\n");
    sbuff
        .append(" getQueryStringDecoded:")
        .append(EscapeStrings.urlDecode(req.getQueryString()))
        .append("\n");
    /*try {
      sbuff.append(" getQueryStringDecoded:").append(URLDecoder.decode(req.getQueryString(), "UTF-8")).append("\n");
    } catch (UnsupportedEncodingException e1) {
      e1.printStackTrace();
    }*/
    sbuff.append(" req.getRequestURI:").append(req.getRequestURI()).append("\n");
    sbuff.append(" getRequestBase:").append(getRequestBase(req)).append("\n");
    sbuff.append(" getRequestServer:").append(getRequestServer(req)).append("\n");
    sbuff.append(" getRequest:").append(getRequest(req)).append("\n");
    sbuff.append("\n");

    sbuff.append(" req.getPathTranslated:").append(req.getPathTranslated()).append("\n");
    String path = req.getPathTranslated();
    if ((path != null) && (servlet != null)) {
      ServletContext context = servlet.getServletContext();
      sbuff.append(" getMimeType:").append(context.getMimeType(path)).append("\n");
    }
    sbuff.append("\n");
    sbuff.append(" req.getScheme:").append(req.getScheme()).append("\n");
    sbuff.append(" req.getProtocol:").append(req.getProtocol()).append("\n");
    sbuff.append(" req.getMethod:").append(req.getMethod()).append("\n");
    sbuff.append("\n");
    sbuff.append(" req.getContentType:").append(req.getContentType()).append("\n");
    sbuff.append(" req.getContentLength:").append(req.getContentLength()).append("\n");

    sbuff.append(" req.getRemoteAddr():").append(req.getRemoteAddr());
    try {
      sbuff
          .append(" getRemoteHost():")
          .append(java.net.InetAddress.getByName(req.getRemoteHost()).getHostName())
          .append("\n");
    } catch (java.net.UnknownHostException e) {
      sbuff.append(" getRemoteHost():").append(e.getMessage()).append("\n");
    }
    sbuff.append(" getRemoteUser():").append(req.getRemoteUser()).append("\n");

    sbuff.append("\n");
    sbuff.append("Request Parameters:\n");
    Enumeration params = req.getParameterNames();
    while (params.hasMoreElements()) {
      String name = (String) params.nextElement();
      String values[] = req.getParameterValues(name);
      if (values != null) {
        for (int i = 0; i < values.length; i++) {
          sbuff
              .append("  ")
              .append(name)
              .append("  (")
              .append(i)
              .append("): ")
              .append(values[i])
              .append("\n");
        }
      }
    }
    sbuff.append("\n");

    sbuff.append("Request Headers:\n");
    Enumeration names = req.getHeaderNames();
    while (names.hasMoreElements()) {
      String name = (String) names.nextElement();
      Enumeration values = req.getHeaders(name); // support multiple values
      if (values != null) {
        while (values.hasMoreElements()) {
          String value = (String) values.nextElement();
          sbuff.append("  ").append(name).append(": ").append(value).append("\n");
        }
      }
    }
    sbuff.append(" ------------------\n");

    return sbuff.toString();
  }

  public static String showRequestHeaders(HttpServletRequest req) {
    StringBuilder sbuff = new StringBuilder();
    sbuff.append("Request Headers:\n");
    Enumeration names = req.getHeaderNames();
    while (names.hasMoreElements()) {
      String name = (String) names.nextElement();
      Enumeration values = req.getHeaders(name); // support multiple values
      if (values != null) {
        while (values.hasMoreElements()) {
          String value = (String) values.nextElement();
          sbuff.append("  ").append(name).append(": ").append(value).append("\n");
        }
      }
    }
    return sbuff.toString();
  }

  public static void showSession(HttpServletRequest req, HttpServletResponse res, PrintStream out) {

    // res.setContentType("text/html");

    // Get the current session object, create one if necessary
    HttpSession session = req.getSession();

    // Increment the hit count for this page. The value is saved
    // in this client's session under the name "snoop.count".
    Integer count = (Integer) session.getAttribute("snoop.count");
    if (count == null) {
      count = 1;
    } else count = count + 1;
    session.setAttribute("snoop.count", count);

    out.println(HtmlWriter.getInstance().getHtmlDoctypeAndOpenTag());
    out.println("<HEAD><TITLE>SessionSnoop</TITLE></HEAD>");
    out.println("<BODY><H1>Session Snoop</H1>");

    // Display the hit count for this page
    out.println(
        "You've visited this page " + count + ((!(count.intValue() != 1)) ? " time." : " times."));

    out.println("<P>");

    out.println("<H3>Here is your saved session data:</H3>");
    Enumeration atts = session.getAttributeNames();
    while (atts.hasMoreElements()) {
      String name = (String) atts.nextElement();
      out.println(name + ": " + session.getAttribute(name) + "<BR>");
    }

    out.println("<H3>Here are some vital stats on your session:</H3>");
    out.println("Session id: " + session.getId() + " <I>(keep it secret)</I><BR>");
    out.println("New session: " + session.isNew() + "<BR>");
    out.println("Timeout: " + session.getMaxInactiveInterval());
    out.println("<I>(" + session.getMaxInactiveInterval() / 60 + " minutes)</I><BR>");
    out.println("Creation time: " + session.getCreationTime());
    out.println("<I>(" + new Date(session.getCreationTime()) + ")</I><BR>");
    out.println("Last access time: " + session.getLastAccessedTime());
    out.println("<I>(" + new Date(session.getLastAccessedTime()) + ")</I><BR>");

    out.println(
        "Requested session ID from cookie: " + req.isRequestedSessionIdFromCookie() + "<BR>");
    out.println("Requested session ID from URL: " + req.isRequestedSessionIdFromURL() + "<BR>");
    out.println("Requested session ID valid: " + req.isRequestedSessionIdValid() + "<BR>");

    out.println("<H3>Test URL Rewriting</H3>");
    out.println("Click <A HREF=\"" + res.encodeURL(req.getRequestURI()) + "\">here</A>");
    out.println("to test that session tracking works via URL");
    out.println("rewriting even when cookies aren't supported.");

    out.println("</BODY></HTML>");
  }

  public static void showSession(HttpServletRequest req, PrintStream out) {

    // res.setContentType("text/html");

    // Get the current session object, create one if necessary
    HttpSession session = req.getSession();

    out.println("Session id: " + session.getId());
    out.println(" session.isNew(): " + session.isNew());
    out.println(" session.getMaxInactiveInterval(): " + session.getMaxInactiveInterval() + " secs");
    out.println(
        " session.getCreationTime(): "
            + session.getCreationTime()
            + " ("
            + new Date(session.getCreationTime())
            + ")");
    out.println(
        " session.getLastAccessedTime(): "
            + session.getLastAccessedTime()
            + " ("
            + new Date(session.getLastAccessedTime())
            + ")");
    out.println(" req.isRequestedSessionIdFromCookie: " + req.isRequestedSessionIdFromCookie());
    out.println(" req.isRequestedSessionIdFromURL: " + req.isRequestedSessionIdFromURL());
    out.println(" req.isRequestedSessionIdValid: " + req.isRequestedSessionIdValid());

    out.println("Saved session Attributes:");
    Enumeration atts = session.getAttributeNames();
    while (atts.hasMoreElements()) {
      String name = (String) atts.nextElement();
      out.println(" " + name + ": " + session.getAttribute(name) + "<BR>");
    }
  }

  public static String showSecurity(HttpServletRequest req, String role) {
    StringBuilder sbuff = new StringBuilder();

    sbuff.append("Security Info\n");
    sbuff.append(" req.getRemoteUser(): ").append(req.getRemoteUser()).append("\n");
    sbuff.append(" req.getUserPrincipal(): ").append(req.getUserPrincipal()).append("\n");
    sbuff
        .append(" req.isUserInRole(")
        .append(role)
        .append("):")
        .append(req.isUserInRole(role))
        .append("\n");
    sbuff.append(" ------------------\n");

    return sbuff.toString();
  }

  /* from luca / ageci code, portResolver, portMapper not known
  static public void getSecureRedirect(HttpServletRequest req) {
      String pathInfo = req.getPathInfo();
      String queryString = req.getQueryString();
      String contextPath = req.getContextPath();
      String destination = req.getServletPath() + ((pathInfo ==   null) ? "" : pathInfo)
          + ((queryString == null) ? "" : ("?" + queryString));
      String redirectUrl = contextPath;

      Integer httpPort = new Integer(portResolver.getServerPort(req));
      Integer httpsPort = portMapper.lookupHttpsPort(httpPort);
      if (httpsPort != null) {
          boolean includePort = true;
          if (httpsPort.intValue() == 443) {
              includePort = false;
          }
          redirectUrl = "https://" + req.getServerName() +   ((includePort) ? (":" + httpsPort) : "") + contextPath
              + destination;
      }
  } */

  private static String getServerInfoName(String serverInfo) {
    int slash = serverInfo.indexOf('/');
    if (slash == -1) return serverInfo;
    else return serverInfo.substring(0, slash);
  }

  private static String getServerInfoVersion(String serverInfo) {
    // Version info is everything between the slash and the space
    int slash = serverInfo.indexOf('/');
    if (slash == -1) return null;
    int space = serverInfo.indexOf(' ', slash);
    if (space == -1) space = serverInfo.length();
    return serverInfo.substring(slash + 1, space);
  }

  public static void showThreads(PrintStream pw) {
    Thread current = Thread.currentThread();
    ThreadGroup group = current.getThreadGroup();
    while (true) {
      if (group.getParent() == null) break;
      group = group.getParent();
    }
    showThreads(pw, group, current);
  }

  private static void showThreads(PrintStream pw, ThreadGroup g, Thread current) {
    int nthreads = g.activeCount();
    pw.println("\nThread Group = " + g.getName() + " activeCount= " + nthreads);
    Thread[] tarray = new Thread[nthreads];
    int n = g.enumerate(tarray, false);

    for (int i = 0; i < n; i++) {
      Thread thread = tarray[i];
      ClassLoader loader = thread.getContextClassLoader();
      String loaderName = (loader == null) ? "Default" : loader.getClass().getName();
      Thread.State state = thread.getState();
      long id = thread.getId();
      pw.print("   " + id + " " + thread.getName() + " " + state + " " + loaderName);
      if (thread == current) pw.println(" **** CURRENT ***");
      else pw.println();
    }

    int ngroups = g.activeGroupCount();
    ThreadGroup[] garray = new ThreadGroup[ngroups];
    int ng = g.enumerate(garray, false);
    for (int i = 0; i < ng; i++) {
      ThreadGroup nested = garray[i];
      showThreads(pw, nested, current);
    }
  }
}
/**
 * A class containing static methods which deliver descriptions and names of parameters, levels and
 * units for byte codes from GRIB records.
 *
 * <p>Performs operations related to loading parameter tables stored in files. Through a lookup
 * table (see readParameterTableLookup) all of the supported Parameter Tables are known. An actual
 * table is not loaded until a parameter from that center/subcenter/table is loaded. see <a
 * href="../../../Parameters.txt">Parameters.txt</a>
 *
 * <p>For now, the lookup table name is hard coded to "resources/grib/tables/tablelookup.lst"
 *
 * @author Capt Richard D. Gonzalez modified by Robb Kambic threadsafe 9/25/08 jcaron see
 *     http://www.ibm.com/developerworks/java/library/j-hashmap.html
 */
public final class GribPDSParamTable {
  private static org.slf4j.Logger logger =
      org.slf4j.LoggerFactory.getLogger(GribPDSParamTable.class);

  private static final String RESOURCE_PATH = "resources/grib/tables";
  private static final String TABLE_LIST = "tablelookup.lst";

  private static final Pattern valid = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_@:\\.\\-\\+]*$");
  private static final Pattern numberFirst = Pattern.compile("^[0-9]");

  /**
   * Added by Richard D. Gonzalez static Array with parameter tables used by the GRIB file (should
   * only be one, but not actually limited to that - this allows GRIB files to be read that have
   * more than one center's information in it)
   */
  private static volatile GribPDSParamTable[] paramTables = null;

  private static Object lock = new Object();

  private static boolean debug = false;
  private static int wmoTable;

  /**
   * This is a mapping from (center,subcenter,number)-> Param table for any data that has been
   * loaded
   */
  private static Map<String, GribPDSParamTable> tableMap =
      new ConcurrentHashMap<String, GribPDSParamTable>();

  static {
    try {
      ArrayList<GribPDSParamTable> tables = new ArrayList<GribPDSParamTable>();
      String resourceName = RESOURCE_PATH + "/" + TABLE_LIST;
      readTableEntries(resourceName, tables);
      paramTables = (GribPDSParamTable[]) tables.toArray(new GribPDSParamTable[tables.size()]);

    } catch (IOException ioe) {
      throw new RuntimeException(ioe);
    }
  }

  /**
   * _more_
   *
   * @param aTableList _more_
   * @param aTables _more_
   * @return Was read successful
   * @throws IOException On badness
   */
  private static boolean readTableEntries(String aTableList, ArrayList<GribPDSParamTable> aTables)
      throws IOException {
    InputStream inputStream = GribResourceReader.getInputStream(aTableList);
    if (inputStream == null) {
      logger.debug("Could not open table file:" + aTableList);
      return false;
    }
    return readTableEntries(inputStream, aTableList, aTables);
  }

  /**
   * Read the table list contained in the input stream
   *
   * @param is The input stream
   * @param aTableList The name of the table list file
   * @param aTables The list to add the tables into
   * @return Was successful
   * @throws IOException On badness
   */
  private static boolean readTableEntries(
      InputStream is, String aTableList, ArrayList<GribPDSParamTable> aTables) throws IOException {
    if (is == null) return false;

    InputStreamReader isr = new InputStreamReader(is);
    BufferedReader br = new BufferedReader(isr);

    String line;
    while ((line = br.readLine()) != null) {
      line = line.trim();
      if ((line.length() == 0) || line.startsWith("#")) {
        continue;
      }
      GribPDSParamTable table = new GribPDSParamTable();
      String[] tableDefArr = line.split(":");

      table.center_id = Integer.parseInt(tableDefArr[0].trim());
      table.subcenter_id = Integer.parseInt(tableDefArr[1].trim());
      table.table_number = Integer.parseInt(tableDefArr[2].trim());
      table.filename = tableDefArr[3].trim();
      if (table.filename.startsWith("/")
          || table.filename.startsWith("\\")
          || table.filename.startsWith("file:")
          || table.filename.startsWith("http://")) {
        table.path = table.filename;
      } else if (aTableList != null) {
        table.path = GribResourceReader.getFileRoot(aTableList);
        if (table.path.equals(aTableList)) {
          table.path = table.filename;
        } else {
          table.path = table.path + "/" + table.filename;
        }
        table.path = table.path.replace('\\', '/');
      }
      aTables.add(table);
    }
    is.close();
    return true;
  }

  public static GribPDSParamTable[] getParameterTables() {
    return paramTables;
  }

  /**
   * Reads in the list of tables available and stores them. Does not actually open the parameter
   * tables files, nor store the list of parameters, but just stores the file names of the parameter
   * tables. Parameters for a table are read in when the table is requested (in the
   * getParameterTable method).
   *
   * @param is UserGribTabList as a InputStream
   * @throws IOException or read error
   */
  public static void addParameterUserLookup(InputStream is) throws IOException {

    // leave out of lock since it does IO
    ArrayList<GribPDSParamTable> tables = new ArrayList<GribPDSParamTable>();
    if (!readTableEntries(is, null, tables)) {
      return;
    }

    synchronized (lock) {
      // tmp table stores new user defined tables plus tablelookup.lst table entries
      GribPDSParamTable[] tmp = new GribPDSParamTable[paramTables.length + tables.size()];
      for (int idx = 0; idx < paramTables.length + tables.size(); idx++) {
        if (idx < tables.size()) {
          tmp[idx] = (GribPDSParamTable) tables.get(idx);
          // System.out.println( "usrlookup tables = " + tmp[ idx ].path );
        } else {
          tmp[idx] = paramTables[idx - tables.size()]; // tablelookup.lst entries
        }
      }
      paramTables = tmp; // new copy of the data structure
    }
  }

  /**
   * Reads in the list of tables available and stores them. Does not actually open the parameter
   * tables files, nor store the list of parameters, but just stores the file names of the parameter
   * tables. Parameters for a table are read in when the table is requested (in the
   * getParameterTable method).
   *
   * @param userGribTabList string of userlookup file
   * @throws IOException if file found but read error
   * @return true if read ok, false if file not found
   */
  public static boolean addParameterUserLookup(String userGribTabList) throws IOException {

    // leave out of lock since it does IO
    ArrayList<GribPDSParamTable> tables = new ArrayList<GribPDSParamTable>();
    if (!readTableEntries(userGribTabList, tables)) {
      // logger.error("could not read:" + userGribTabList);
      return false;
    }

    synchronized (lock) {
      // tmp table stores new user defined tables plus tablelookup.lst table entries
      GribPDSParamTable[] tmp = new GribPDSParamTable[paramTables.length + tables.size()];
      for (int idx = 0; idx < paramTables.length + tables.size(); idx++) {
        if (idx < tables.size()) {
          tmp[idx] = tables.get(idx); // new stuff first
        } else {
          tmp[idx] = paramTables[idx - tables.size()]; // old stuff
        }
      }
      paramTables = tmp; // new copy of the data structure
    }

    return true;
  }

  /**
   * Looks for the parameter table which matches the center, subcenter and table version from the
   * tables array. If this is the first time asking for this table, then the parameters for this
   * table have not been read in yet, so this is done as well.
   *
   * @param center - integer from PDS octet 5, representing Center.
   * @param subcenter - integer from PDS octet 26, representing Subcenter
   * @param tableVersion - integer from PDS octet 4, representing Parameter Table Version
   * @return GribPDSParamTable matching center, subcenter, and number
   * @throws NotSupportedException no table found
   */
  public static GribPDSParamTable getParameterTable(int center, int subcenter, int tableVersion)
      throws NotSupportedException {

    String key = center + "_" + subcenter + "_" + tableVersion;
    if (center == -1) { // non existent table
      logger.error("GribPDSParamTable: non existent table for center, subcenter, table = " + key);
      return null;
    }

    GribPDSParamTable table = tableMap.get(key);
    if (table != null) return table;

    table = readParameterTable(center, subcenter, tableVersion, true);

    if (table == null) {
      logger.error("GribPDSParamTable: cannot find table for center, subcenter, table " + key);
      throw new NotSupportedException(
          "Could not find a table entry for GRIB file with center: "
              + center
              + " subCenter: "
              + subcenter
              + " number: "
              + tableVersion);
    }

    tableMap.put(key, table);
    return table;
  }

  /**
   * Looks for the parameter table which matches the center, subcenter and table version from the
   * tables array. If this is the first time asking for this table, then the parameters for this
   * table have not been read in yet, so this is done as well.
   *
   * @param center - integer from PDS octet 5, representing Center.
   * @param subcenter - integer from PDS octet 26, representing Subcenter
   * @param number - integer from PDS octet 4, representing Parameter Table Version
   * @param firstTry - Is this the first call or are we trying the wild cards
   * @return GribPDSParamTable matching center, subcenter, and number
   */
  private static GribPDSParamTable readParameterTable(
      int center, int subcenter, int number, boolean firstTry) {

    if (firstTry) wmoTable = number;

    GribPDSParamTable[] localCopy = paramTables; // thread safe

    for (GribPDSParamTable table : localCopy) {

      if (center == table.center_id) {
        if ((table.subcenter_id == -1) || (subcenter == table.subcenter_id)) {
          if (number == table.table_number) {
            // now that this table is being used, check to see if the
            //   parameters for this table have been read in yet.
            // If not, initialize table and read them in now.
            if (table.parameters == null) {
              if (!firstTry) {
                logger.warn(
                    "GribPDSParamTable: Using default table:"
                        + table.path
                        + " ("
                        + table.center_id
                        + ":"
                        + table.subcenter_id
                        + ":"
                        + table.table_number
                        + ")");
              }
              table.readParameterTable();
              if (table.parameters
                  == null) // failed - maybe theres another entry table in paramTables
              continue;

              // success - initialize other tables parameters with the same name
              for (int j = 0; j < paramTables.length; j++) {
                GribPDSParamTable tab = paramTables[j];
                if (tab.path.equals(table.path)) {
                  tab.parameters = table.parameters;
                }
              }
            }
            return table;
          }
        }
      }
    }

    // Try with the  wild cards
    if (number != -1) {
      return readParameterTable(center, subcenter, -1, false);

    } else if (subcenter != -1) {
      logger.warn(
          "GribPDSParamTable: Could not find table for center:"
              + center
              + " subcenter:"
              + subcenter
              + " number:"
              + wmoTable);
      return readParameterTable(center, -1, -1, false);

    } else if (center != -1) {
      // return readParameterTable(-1, -1, -1, false);
      return readParameterTable(-1, -1, wmoTable, false);
    }

    return null;
  }

  /**
   * Munge a description to make it suitable as variable name
   *
   * @param description start with this
   * @return Valid Description
   */
  private static String makeValidDesc(String description) {
    description = description.replaceAll("\\s+", "_");
    if (valid.matcher(description).find()) return description;
    // else check for special characters
    if (numberFirst.matcher(description).find()) description = "N" + description;
    return description.replaceAll("\\)|\\(|=|,|;|\\[|\\]", "");
  }

  //////////////////////////////////////////////////////////////////////////

  /** Identification of center e.g. 88 for Oslo */
  private int center_id;

  /** Identification of center defined sub-center - not fully implemented yet. */
  private int subcenter_id;

  /** Identification of parameter table version number. */
  private int table_number;

  /** Stores the name of the file containing this table - not opened unless required for lookup. */
  private String filename = null;

  /** path of filename containing this table. Opened if required for lookup. */
  private String path = null;

  /** Map ids to GridParameter objects */
  private Map<String, GridParameter> parameters = null;

  private GribPDSParamTable() {}

  public int getCenter_id() {
    return center_id;
  }

  public int getSubcenter_id() {
    return subcenter_id;
  }

  public int getTable_number() {
    return table_number;
  }

  public String getPath() {
    return path;
  }

  public String getFilename() {
    return filename;
  }

  public Map<String, GridParameter> getParameters() {
    if (parameters == null) readParameterTable();
    return parameters;
  }

  /** Read parameter table. */
  private void readParameterTable() {
    if (path == null) {
      logger.error("GribPDSParamTable: unknown path for " + this);
      return;
    }

    try {
      InputStream is = GribResourceReader.getInputStream(path);
      if (is == null) {
        logger.error("GribPDSParamTable: error getInputStream on " + this);
        return;
      }
      BufferedReader br = new BufferedReader(new InputStreamReader(is));

      // Read first line that has center, subcenter, and version of table
      String line = br.readLine();
      if (debug) System.out.println(line);
      String[] tableDefArr = line.split(":");

      /*  LOOK - why not test values ?
      center    = Integer.parseInt(tableDefArr[1].trim());
      subcenter = Integer.parseInt(tableDefArr[2].trim());
      number    = Integer.parseInt(tableDefArr[3].trim());
      if ((center != center_id) && (subcenter != subcenter_id)
              && (number != table_number)) {
          throw new java.io.IOException(
              "parameter table header values do not "
              + " match values in GRIB file.  Possible error in lookup table.");
      }
      */

      HashMap<String, GridParameter> tmpParameters =
          new HashMap<String, GridParameter>(); // thread safe - temp hash

      // rdg - added the 0 line length check to cover the case of blank lines at
      //       the end of the parameter table file.
      while ((line = br.readLine()) != null) {
        if ((line.length() == 0) || line.startsWith("#")) {
          continue;
        }
        GridParameter parameter = new GridParameter();
        tableDefArr = line.split(":");
        parameter.setNumber(Integer.parseInt(tableDefArr[0].trim()));
        parameter.setName(tableDefArr[1].trim());
        // check to see if unit defined, if not, parameter is undefined
        if (tableDefArr[2].indexOf('[') == -1) {
          // Undefined unit
          parameter.setDescription(tableDefArr[2].trim());
          parameter.setUnit(tableDefArr[2].trim());
        } else {
          String[] arr2 = tableDefArr[2].split("\\[");
          parameter.setDescription(makeValidDesc(arr2[0].trim()));
          // System.out.println( "Desc ="+ parameter.getDescription());
          // Remove "]"
          parameter.setUnit(arr2[1].substring(0, arr2[1].lastIndexOf(']')).trim());
        }
        tmpParameters.put(Integer.toString(parameter.getNumber()), parameter);
        if (debug)
          System.out.println(
              parameter.getNumber() + " " + parameter.getDescription() + " " + parameter.getUnit());
      }

      this.parameters = tmpParameters; // thread safe

    } catch (IOException ioError) {
      logger.warn(
          "An error occurred in GribPDSParamTable while trying to open the parameter table "
              + filename
              + " : "
              + ioError);
    }
  }

  /**
   * Get the parameter with id <tt>id</tt>.
   *
   * @param id the parameter id
   * @return the GridParameter
   */
  public GridParameter getParameter(int id) {
    GridParameter p = parameters.get(Integer.toString(id));
    if (p != null) return p;

    logger.warn(
        "GribPDSParamTable: Could not find parameter "
            + id
            + " for center:"
            + center_id
            + " subcenter:"
            + subcenter_id
            + " number:"
            + table_number
            + " table "
            + filename);
    String unknown = "UnknownParameter_" + Integer.toString(id) + "_table_" + filename;
    return new GridParameter(id, unknown, unknown, "Unknown");
  }

  @Override
  public String toString() {
    return "GribPDSParamTable{"
        + "center_id="
        + center_id
        + ", subcenter_id="
        + subcenter_id
        + ", table_number="
        + table_number
        + ", filename='"
        + filename
        + '\''
        + ", path='"
        + path
        + '\''
        + '}';
  }

  public static void main(String[] args) throws IOException {
    debug = true;
    addParameterUserLookup(
        "C:/dev/tds4.2/thredds/grib/resources/resources/grib/tables/zagreb_221_1.tab");
  }
}
/**
 * A Vertical Coordinate variable for a Grid variable.
 *
 * @author caron
 * @version $Revision: 63 $ $Date: 2006-07-12 15:50:51 -0600 (Wed, 12 Jul 2006) $
 */
public class GridVertCoord implements Comparable {

  /** logger */
  private static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(GridVertCoord.class);

  /** typical record for this vertical coordinate */
  private GridRecord typicalRecord;

  /** level name */
  private String levelName;

  /** lookup table */
  private GridTableLookup lookup;

  /** sequence # */
  private int seq = 0;

  /** coord values */
  private double[] coordValues = null;

  /** uses bounds flag */
  boolean usesBounds = false;

  /** don't use vertical flag */
  boolean dontUseVertical = false;

  /** vertical pressure factors */
  double[] factors = null;

  /** positive direction */
  String positive = "up";

  /** units */
  String units;

  /** levels */
  private List<LevelCoord> levels = new ArrayList<LevelCoord>(); // LevelCoord

  /**
   * Create a new GridVertCoord with the given name
   *
   * @param name name
   */
  GridVertCoord(String name) {
    this.levelName = name;
    dontUseVertical = true;
  }

  /**
   * Create a new GridVertCoord with the appropriate params
   *
   * @param records list of GridRecords that make up this coord
   * @param levelName the name of the level
   * @param lookup the lookup table
   * @param hcs Horizontal coordinate
   */
  GridVertCoord(
      List<GridRecord> records, String levelName, GridTableLookup lookup, GridHorizCoordSys hcs) {
    this.typicalRecord = records.get(0);
    this.levelName = levelName;
    this.lookup = lookup;

    dontUseVertical = !lookup.isVerticalCoordinate(typicalRecord);
    positive = lookup.isPositiveUp(typicalRecord) ? "up" : "down";
    units = lookup.getLevelUnit(typicalRecord);

    usesBounds = lookup.isLayer(this.typicalRecord);
    addLevels(records);

    if (typicalRecord.getLevelType1() == 109 && lookup instanceof Grib1GridTableLookup)
      checkForPressureLevels(records, hcs);

    if (GridServiceProvider.debugVert) {
      System.out.println(
          "GribVertCoord: "
              + getVariableName()
              + "("
              + typicalRecord.getLevelType1()
              + ") useVertical= "
              + (!dontUseVertical)
              + " positive="
              + positive
              + " units="
              + units);
    }
  }

  /**
   * Create a new GridVertCoord for a layer
   *
   * @param record layer record
   * @param levelName name of this level
   * @param lookup lookup table
   * @param level1 level 1
   * @param level2 level 2
   */
  GridVertCoord(
      GridRecord record,
      String levelName,
      GridTableLookup lookup,
      double[] level1,
      double[] level2) {
    this.typicalRecord = record;
    this.levelName = levelName;
    this.lookup = lookup;

    // dontUseVertical    = !lookup.isVerticalCoordinate(record);
    positive = lookup.isPositiveUp(record) ? "up" : "down";
    units = lookup.getLevelUnit(record);
    usesBounds = lookup.isLayer(this.typicalRecord);

    levels = new ArrayList<LevelCoord>(level1.length);
    for (int i = 0; i < level1.length; i++) {
      levels.add(new LevelCoord(level1[i], (level2 == null) ? 0.0 : level2[i]));
    }

    Collections.sort(levels);
    if (positive.equals("down")) {
      Collections.reverse(levels);
    }
    dontUseVertical = (levels.size() == 1);
  }

  /**
   * Set the sequence number
   *
   * @param seq the sequence number
   */
  void setSequence(int seq) {
    this.seq = seq;
  }

  /**
   * Set the level name
   *
   * @return the level name
   */
  String getLevelName() {
    return levelName;
  }

  /**
   * Get the variable name
   *
   * @return the variable name
   */
  String getVariableName() {
    return (seq == 0) ? levelName : levelName + seq; // more than one with same levelName
  }

  /**
   * Get the number of levels
   *
   * @return number of levels
   */
  int getNLevels() {
    return dontUseVertical ? 1 : levels.size();
  }

  /**
   * Add levels
   *
   * @param records GridRecords, one for each level
   */
  void addLevels(List<GridRecord> records) {
    for (int i = 0; i < records.size(); i++) {
      GridRecord record = records.get(i);

      if (coordIndex(record) < 0) {
        levels.add(new LevelCoord(record.getLevel1(), record.getLevel2()));
        if (dontUseVertical && (levels.size() > 1)) {
          if (GridServiceProvider.debugVert) {
            logger.warn(
                "GribCoordSys: unused level coordinate has > 1 levels = "
                    + levelName
                    + " "
                    + record.getLevelType1()
                    + " "
                    + levels.size());
          }
        }
      }
    }
    Collections.sort(levels);
    if (positive.equals("down")) {
      Collections.reverse(levels);
    }
  }

  /**
   * Match levels
   *
   * @param records records to match
   * @return true if they have the same levels
   */
  boolean matchLevels(List<GridRecord> records) {

    // first create a new list
    List<LevelCoord> levelList = new ArrayList<LevelCoord>(records.size());
    for (GridRecord record : records) {
      LevelCoord lc = new LevelCoord(record.getLevel1(), record.getLevel2());
      if (!levelList.contains(lc)) {
        levelList.add(lc);
      }
    }

    Collections.sort(levelList);
    if (positive.equals("down")) {
      Collections.reverse(levelList);
    }

    // gotta equal existing list
    return levelList.equals(levels);
  }

  /** check for Sigma Pressure Levels */
  boolean checkForPressureLevels(List<GridRecord> records, GridHorizCoordSys hcs) {
    GridDefRecord gdr = hcs.getGds();
    Grib1GDSVariables g1dr = (Grib1GDSVariables) gdr.getGdsv();
    if (g1dr == null || !g1dr.hasVerticalPressureLevels()) return false;

    // add hybrid numbers
    coordValues = new double[levels.size()];
    for (int i = 0; i < levels.size(); i++) {
      LevelCoord lc = levels.get(i);
      coordValues[i] = lc.value1;
    }
    int NV = g1dr.getNV();
    // add new variables
    if (NV > 2 && NV < 255) { // Some data doesn't add Pressure Level values
      factors = g1dr.getVerticalPressureLevels();
    }
    return true;
  }

  /**
   * Add this coord as a dimension to the netCDF file
   *
   * @param ncfile file to add to
   * @param g group in the file
   */
  void addDimensionsToNetcdfFile(NetcdfFile ncfile, Group g) {
    if (dontUseVertical) {
      return;
    }
    int nlevs = levels.size();
    if (coordValues != null) nlevs = coordValues.length;
    ncfile.addDimension(g, new Dimension(getVariableName(), nlevs, true));
  }

  /**
   * Add this coord as a variable in the netCDF file
   *
   * @param ncfile netCDF file to add to
   * @param g group in file
   */
  void addToNetcdfFile(NetcdfFile ncfile, Group g) {
    if (dontUseVertical) {
      typicalRecord = null;
      return;
    }

    if (g == null) {
      g = ncfile.getRootGroup();
    }

    // coordinate axis
    Variable v = new Variable(ncfile, g, null, getVariableName());
    v.setDataType(DataType.DOUBLE);

    String desc = lookup.getLevelDescription(typicalRecord);
    if (lookup instanceof Grib2GridTableLookup && usesBounds) {
      desc = "Layer between " + desc;
    }

    v.addAttribute(new Attribute("long_name", desc));
    v.addAttribute(new Attribute("units", lookup.getLevelUnit(typicalRecord)));

    // positive attribute needed for CF-1 Height and Pressure
    if (positive != null) {
      v.addAttribute(new Attribute("positive", positive));
    }

    if (units != null) {
      AxisType axisType;
      if (SimpleUnit.isCompatible("millibar", units)) {
        axisType = AxisType.Pressure;
      } else if (SimpleUnit.isCompatible("m", units)) {
        axisType = AxisType.Height;
      } else {
        axisType = AxisType.GeoZ;
      }

      if (lookup instanceof Grib2GridTableLookup || lookup instanceof Grib1GridTableLookup) {
        v.addAttribute(
            new Attribute("GRIB_level_type", Integer.toString(typicalRecord.getLevelType1())));
      } else {
        v.addAttribute(
            new Attribute("level_type", Integer.toString(typicalRecord.getLevelType1())));
      }
      v.addAttribute(new Attribute(_Coordinate.AxisType, axisType.toString()));
    }

    if (coordValues == null) {
      coordValues = new double[levels.size()];
      for (int i = 0; i < levels.size(); i++) {
        LevelCoord lc = (LevelCoord) levels.get(i);
        coordValues[i] = lc.mid;
      }
    }
    Array dataArray = Array.factory(DataType.DOUBLE, new int[] {coordValues.length}, coordValues);

    v.setDimensions(getVariableName());
    v.setCachedData(dataArray, true);

    ncfile.addVariable(g, v);

    if (usesBounds) {
      String boundsDimName = "bounds_dim";
      if (g.findDimension(boundsDimName) == null) {
        ncfile.addDimension(g, new Dimension(boundsDimName, 2, true));
      }

      String bname = getVariableName() + "_bounds";
      v.addAttribute(new Attribute("bounds", bname));
      v.addAttribute(new Attribute(_Coordinate.ZisLayer, "true"));

      Variable b = new Variable(ncfile, g, null, bname);
      b.setDataType(DataType.DOUBLE);
      b.setDimensions(getVariableName() + " " + boundsDimName);
      b.addAttribute(new Attribute("long_name", "bounds for " + v.getName()));
      b.addAttribute(new Attribute("units", lookup.getLevelUnit(typicalRecord)));

      Array boundsArray = Array.factory(DataType.DOUBLE, new int[] {coordValues.length, 2});
      ucar.ma2.Index ima = boundsArray.getIndex();
      for (int i = 0; i < coordValues.length; i++) {
        LevelCoord lc = (LevelCoord) levels.get(i);
        boundsArray.setDouble(ima.set(i, 0), lc.value1);
        boundsArray.setDouble(ima.set(i, 1), lc.value2);
      }
      b.setCachedData(boundsArray, true);

      ncfile.addVariable(g, b);
    }

    if (factors != null) {
      // check if already created
      if (g == null) {
        g = ncfile.getRootGroup();
      }
      if (g.findVariable("hybrida") != null) return;
      v.addAttribute(new Attribute("standard_name", "atmosphere_hybrid_sigma_pressure_coordinate"));
      v.addAttribute(new Attribute("formula_terms", "ap: hybrida b: hybridb ps: Pressure"));
      // create  hybrid factor variables
      // add hybrida variable
      Variable ha = new Variable(ncfile, g, null, "hybrida");
      ha.setDataType(DataType.DOUBLE);
      ha.addAttribute(new Attribute("long_name", "level_a_factor"));
      ha.addAttribute(new Attribute("units", ""));
      ha.setDimensions(getVariableName());
      // add data
      int middle = factors.length / 2;
      double[] adata;
      double[] bdata;
      if (levels.size() < middle) { // only partial data wanted
        adata = new double[levels.size()];
        bdata = new double[levels.size()];
      } else {
        adata = new double[middle];
        bdata = new double[middle];
      }
      for (int i = 0; i < middle && i < levels.size(); i++) adata[i] = factors[i];
      Array haArray = Array.factory(DataType.DOUBLE, new int[] {adata.length}, adata);
      ha.setCachedData(haArray, true);
      ncfile.addVariable(g, ha);

      // add hybridb variable
      Variable hb = new Variable(ncfile, g, null, "hybridb");
      hb.setDataType(DataType.DOUBLE);
      hb.addAttribute(new Attribute("long_name", "level_b_factor"));
      hb.addAttribute(new Attribute("units", ""));
      hb.setDimensions(getVariableName());
      // add data
      for (int i = 0; i < middle && i < levels.size(); i++) bdata[i] = factors[i + middle];
      Array hbArray = Array.factory(DataType.DOUBLE, new int[] {bdata.length}, bdata);
      hb.setCachedData(hbArray, true);
      ncfile.addVariable(g, hb);

      /*  // TODO: delete next time modifying code
      double[] adata = new double[ middle ];
      for( int i = 0; i < middle; i++ )
        adata[ i ] = factors[ i ];
      Array haArray = Array.factory(DataType.DOUBLE, new int[]{adata.length}, adata);
      ha.setCachedData(haArray, true);
      ncfile.addVariable(g, ha);

      // add hybridb variable
      Variable hb = new Variable(ncfile, g, null, "hybridb");
      hb.setDataType(DataType.DOUBLE);
      hb.addAttribute(new Attribute("long_name",  "level_b_factor" ));
      //hb.addAttribute(new Attribute("standard_name", "atmosphere_hybrid_sigma_pressure_coordinate" ));
      hb.addAttribute(new Attribute("units", ""));
      hb.setDimensions(getVariableName());
      // add data
      double[] bdata = new double[ middle ];
      for( int i = 0; i < middle; i++ )
        bdata[ i ] = factors[ i + middle ];
      Array hbArray = Array.factory(DataType.DOUBLE, new int[]{bdata.length}, bdata);
      hb.setCachedData(hbArray, true);
      ncfile.addVariable(g, hb);
      */
    }
  }

  void empty() {
    // let all references to Index go, to reduce retained size
    typicalRecord = null;
  }

  /**
   * Get the index of the particular record
   *
   * @param record record in question
   * @return the index or -1 if not found
   */
  int getIndex(GridRecord record) {
    if (dontUseVertical) {
      return 0;
    }
    return coordIndex(record);
  }

  /**
   * Compare this to another
   *
   * @param o the other GridVertCoord
   * @return the comparison
   */
  public int compareTo(Object o) {
    GridVertCoord gv = (GridVertCoord) o;
    return getLevelName().compareToIgnoreCase(gv.getLevelName());
  }

  /**
   * A level coordinate
   *
   * @author IDV Development Team
   * @version $Revision: 1.3 $
   */
  private class LevelCoord implements Comparable {

    /** midpoint */
    double mid;

    /** top/bottom values */
    double value1, value2;

    /**
     * Create a new LevelCoord
     *
     * @param value1 top
     * @param value2 bottom
     */
    LevelCoord(double value1, double value2) {
      this.value1 = value1;
      this.value2 = value2;
      if (usesBounds && (value1 > value2)) {
        this.value1 = value2;
        this.value2 = value1;
      }
      mid = usesBounds ? (value1 + value2) / 2 : value1;
    }

    /**
     * Compare to another LevelCoord
     *
     * @param o another LevelCoord
     * @return the comparison
     */
    public int compareTo(Object o) {
      LevelCoord other = (LevelCoord) o;
      // if (closeEnough(value1, other.value1) && closeEnough(value2, other.value2)) return 0;
      if (mid < other.mid) {
        return -1;
      }
      if (mid > other.mid) {
        return 1;
      }
      return 0;
    }

    /**
     * Check for equality
     *
     * @param oo object in question
     * @return true if equal
     */
    public boolean equals(Object oo) {
      if (this == oo) {
        return true;
      }
      if (!(oo instanceof LevelCoord)) {
        return false;
      }
      LevelCoord other = (LevelCoord) oo;
      return (ucar.nc2.util.Misc.closeEnough(value1, other.value1)
          && ucar.nc2.util.Misc.closeEnough(value2, other.value2));
    }

    /**
     * Generate a hashcode
     *
     * @return the hashcode
     */
    public int hashCode() {
      return (int) (value1 * 100000 + value2 * 100);
    }
  }

  /**
   * Get the coordinate index for the record
   *
   * @param record record in question
   * @return index or -1 if not found
   */
  private int coordIndex(GridRecord record) {
    double val = record.getLevel1();
    double val2 = record.getLevel2();
    if (usesBounds && (val > val2)) {
      val = record.getLevel2();
      val2 = record.getLevel1();
    }

    for (int i = 0; i < levels.size(); i++) {
      LevelCoord lc = (LevelCoord) levels.get(i);
      if (usesBounds) {
        if (ucar.nc2.util.Misc.closeEnough(lc.value1, val)
            && ucar.nc2.util.Misc.closeEnough(lc.value2, val2)) {
          return i;
        }
      } else {
        if (ucar.nc2.util.Misc.closeEnough(lc.value1, val)) {
          return i;
        }
      }
    }
    return -1;
  }
}
Exemple #22
0
/**
 * Read and Write Grib1 index (gbx9). Hides Grib1IndexProto
 *
 * <p>sample use:
 *
 * <pre>
 * Grib1Index index = new Grib1Index();
 * if (!index.readIndex(path))
 * index.makeIndex(path);
 *
 * for (Grib1SectionGridDefinition gds : index.getGds()) {
 * if (gdsSet.get(gds.calcCRC()) == null)
 * gdsSet.put(gds.calcCRC(), gds);
 * }
 *
 * for (Grib1Record gr : index.getRecords()) {
 * gr.setFile(fileno);
 *
 * Grib1Pds pds = gr.getPDSsection().getPDS();
 * int discipline = gr.getDiscipline();
 *
 * int id = gr.cdmVariableHash();
 * Grib1ParameterBean bean = pdsSet.get(id);
 * if (bean == null) {
 * bean = new Grib1ParameterBean(gr);
 * pdsSet.put(id, bean);
 * params.add(bean);
 * }
 * bean.addRecord(gr);
 * }
 * </pre>
 *
 * @author John
 * @since 9/5/11
 */
public class Grib1Index extends GribIndex {
  private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Grib1Index.class);

  public static final String MAGIC_START = "Grib1Index";
  private static final int version = 5;
  private static final boolean debug = false;

  ////////////////////////////////////////////////////////////////////////////////////////////////

  private List<Grib1SectionGridDefinition> gdsList;
  private List<Grib1Record> records;

  public List<Grib1SectionGridDefinition> getGds() {
    return gdsList;
  }

  public List<Grib1Record> getRecords() {
    return records;
  }

  @Override
  public int getNRecords() {
    return records.size();
  }

  public boolean readIndex(String filename, long gribLastModified) throws IOException {
    return readIndex(filename, gribLastModified, CollectionManager.Force.test);
  }

  public boolean readIndex(String filename, long gribLastModified, CollectionManager.Force force)
      throws IOException {
    File idxFile = GribCollection.getIndexFile(filename + GBX9_IDX);
    if (!idxFile.exists()) return false;
    long idxModified = idxFile.lastModified();
    if ((force != CollectionManager.Force.nocheck) && (idxModified < gribLastModified))
      return false;
    // force new index if file was updated

    FileInputStream fin =
        new FileInputStream(idxFile); // LOOK need DiskCache for non-writeable directories

    try {
      //// check header is ok
      if (!NcStream.readAndTest(fin, MAGIC_START.getBytes())) {
        log.info("Bad magic number of grib index, on file" + idxFile);
        return false;
      }

      int v = NcStream.readVInt(fin);
      if (v != version) {
        if ((v == 0) || (v > version))
          throw new IOException(
              "Grib1Index found version " + v + ", want version " + version + " on " + filename);
        if (log.isDebugEnabled())
          log.debug(
              "Grib1Index found version " + v + ", want version " + version + " on " + filename);
        return false;
      }

      int size = NcStream.readVInt(fin);
      if (size <= 0 || size > 100 * 1000 * 1000) { // try to catch garbage
        log.warn("Grib1Index bad size = {} for {} ", size, filename);
        return false;
      }

      byte[] m = new byte[size];
      NcStream.readFully(fin, m);

      Grib1IndexProto.Grib1Index proto = Grib1IndexProto.Grib1Index.parseFrom(m);
      String fname = proto.getFilename();
      if (debug) System.out.printf("%s for %s%n", fname, filename);

      gdsList = new ArrayList<Grib1SectionGridDefinition>(proto.getGdsListCount());
      for (Grib1IndexProto.Grib1GdsSection pgds : proto.getGdsListList()) {
        Grib1SectionGridDefinition gds = readGds(pgds);
        gdsList.add(gds);
      }
      if (debug) System.out.printf(" read %d gds%n", gdsList.size());

      records = new ArrayList<Grib1Record>(proto.getRecordsCount());
      for (Grib1IndexProto.Grib1Record precord : proto.getRecordsList()) {
        records.add(readRecord(precord));
      }
      if (debug) System.out.printf(" read %d records%n", records.size());

    } catch (java.lang.NegativeArraySizeException e) {
      log.error("GribIndex failed on " + filename, e);
      return false;

    } catch (IOException e) {
      log.error("GribIndex failed on " + filename, e);
      return false;

    } finally {
      fin.close();
    }

    return true;
  }

  // deserialize the Grib1Record object
  private Grib1Record readRecord(Grib1IndexProto.Grib1Record p) {
    Grib1SectionIndicator is =
        new Grib1SectionIndicator(p.getGribMessageStart(), p.getGribMessageLength());
    Grib1SectionProductDefinition pds = new Grib1SectionProductDefinition(p.getPds().toByteArray());

    Grib1SectionGridDefinition gds =
        pds.gdsExists() ? gdsList.get(p.getGdsIdx()) : new Grib1SectionGridDefinition(pds);
    Grib1SectionBitMap bms = pds.bmsExists() ? new Grib1SectionBitMap(p.getBmsPos()) : null;

    Grib1SectionBinaryData dataSection = new Grib1SectionBinaryData(p.getDataPos(), p.getDataLen());
    return new Grib1Record(p.getHeader().toByteArray(), is, gds, pds, bms, dataSection);
  }

  private Grib1SectionGridDefinition readGds(Grib1IndexProto.Grib1GdsSection proto) {
    ByteString bytes = proto.getGds();
    return new Grib1SectionGridDefinition(bytes.toByteArray());
  }

  ////////////////////////////////////////////////////////////////////////////////

  // LOOK what about extending an index ??
  public boolean makeIndex(String filename, RandomAccessFile dataRaf, Formatter f)
      throws IOException {
    File idxFile = GribCollection.getIndexFile(filename + GBX9_IDX);
    FileOutputStream fout = new FileOutputStream(idxFile);
    RandomAccessFile raf = null;
    try {
      //// header message
      fout.write(MAGIC_START.getBytes("UTF-8"));
      NcStream.writeVInt(fout, version);

      Map<Long, Integer> gdsMap = new HashMap<Long, Integer>();
      gdsList = new ArrayList<Grib1SectionGridDefinition>();
      records = new ArrayList<Grib1Record>(200);

      Grib1IndexProto.Grib1Index.Builder rootBuilder = Grib1IndexProto.Grib1Index.newBuilder();
      rootBuilder.setFilename(filename);

      if (dataRaf == null) {
        raf = new RandomAccessFile(filename, "r");
        dataRaf = raf;
      }

      Grib1RecordScanner scan = new Grib1RecordScanner(dataRaf);
      while (scan.hasNext()) {
        Grib1Record r = scan.next();
        if (r == null) break; // done
        records.add(r);

        Grib1SectionGridDefinition gds = r.getGDSsection();
        Integer index = gdsMap.get(gds.calcCRC());
        if (gds.getPredefinedGridDefinition()
            >= 0) // skip predefined gds - they dont have raw bytes
        index = 0;
        else if (index == null) {
          gdsList.add(gds);
          index = gdsList.size() - 1;
          gdsMap.put(gds.calcCRC(), index);
          rootBuilder.addGdsList(makeGdsProto(gds));
        }
        rootBuilder.addRecords(makeRecordProto(r, index));
      }

      ucar.nc2.grib.grib1.Grib1IndexProto.Grib1Index index = rootBuilder.build();
      byte[] b = index.toByteArray();
      NcStream.writeVInt(fout, b.length); // message size
      fout.write(b); // message  - all in one gulp
      f.format("  made gbx9 index for %s size=%d%n", filename, b.length);
      return true;

    } finally {
      fout.close();
      if (raf != null) raf.close();
    }
  }

  private ucar.nc2.grib.grib1.Grib1IndexProto.Grib1Record makeRecordProto(
      Grib1Record r, int gdsIndex) throws IOException {
    Grib1IndexProto.Grib1Record.Builder b = Grib1IndexProto.Grib1Record.newBuilder();

    b.setHeader(ByteString.copyFrom(r.getHeader()));

    b.setGribMessageStart(r.getIs().getStartPos());
    b.setGribMessageLength(r.getIs().getMessageLength());

    b.setGdsIdx(gdsIndex);
    Grib1SectionProductDefinition pds = r.getPDSsection();
    b.setPds(ByteString.copyFrom(pds.getRawBytes()));

    if (pds.bmsExists()) {
      Grib1SectionBitMap bms = r.getBitMapSection();
      b.setBmsPos(bms.getStartingPosition());
    }

    Grib1SectionBinaryData ds = r.getDataSection();
    b.setDataPos(ds.getStartingPosition());
    b.setDataLen(ds.getLength());

    return b.build();
  }

  private Grib1IndexProto.Grib1GdsSection makeGdsProto(Grib1SectionGridDefinition gds)
      throws IOException {
    Grib1IndexProto.Grib1GdsSection.Builder b = Grib1IndexProto.Grib1GdsSection.newBuilder();
    b.setGds(ByteString.copyFrom(gds.getRawBytes()));
    return b.build();
  }

  public static void main(String args[]) throws IOException {
    String filename =
        "G:/tigge/uv/z_tigge_c_kwbc_20110605120000_glob_prod_cf_HGHT_0000_000_10_uv.grib";
    // String filename = "G:/mlode/ndfdProb/extract.Grib1";
    new Grib1Index().makeIndex(filename, null, new Formatter(System.out));
  }
}
Exemple #23
0
/**
 * A pool of precompiled XSLT stylesheets ({@link Templates}). Caching can be disabled via
 * constructor parameter or via setting a system property:
 *
 * <pre>
 * template.caching
 * </pre>
 *
 * to <code>false</code>.
 */
public final class TemplatesPool {
  private static final Logger logger = org.slf4j.LoggerFactory.getLogger(TemplatesPool.class);

  /**
   * Global system property disabling template caching. This property can also be set at runtime
   * (after the pool is initialized).
   */
  public static final String TEMPLATE_CACHING_PROPERTY = "template.caching";

  /** A set of used XSLT processors. */
  private static final Set<String> reportedProcessors =
      Collections.synchronizedSet(new HashSet<String>());

  /** A map of precompiled stylesheets ({@link Templates} objects). */
  private volatile HashMap<String, Templates> stylesheets = new HashMap<String, Templates>();

  /**
   * If <code>true</code> the templates will not be cached until the application shuts down. This
   * speeds up the application, but may be annoying, especially during development.
   */
  private final boolean templateCaching;

  /** {@link SAXTransformerFactory} capable of producing SAX-based transformers. */
  public final SAXTransformerFactory tFactory;

  /** Creates a {@link TemplatesPool} with caching enabled. */
  public TemplatesPool() throws Exception {
    this(true);
  }

  /** Check for required facilities. If not available, an exception will be thrown. */
  public TemplatesPool(boolean templateCaching) throws Exception {
    final TransformerFactory tFactory = TransformerFactory.newInstance();
    final String processorClass = tFactory.getClass().getName();

    /*
     * Only report XSLT processor class once.
     */
    if (!reportedProcessors.contains(processorClass)) {
      logger.info("XSLT transformer factory: " + processorClass);
      reportedProcessors.add(processorClass);
    }

    if (!tFactory.getFeature(SAXSource.FEATURE) || !tFactory.getFeature(SAXResult.FEATURE)) {
      throw new Exception("Required source types not supported by the transformer factory.");
    }

    if (!tFactory.getFeature(SAXResult.FEATURE) || !tFactory.getFeature(StreamResult.FEATURE)) {
      throw new Exception("Required result types not supported by the transformer factory.");
    }

    if (!(tFactory instanceof SAXTransformerFactory)) {
      throw new Exception(
          "TransformerFactory not an instance of SAXTransformerFactory: "
              + tFactory.getClass().getName());
    }

    this.tFactory = ((SAXTransformerFactory) tFactory);
    this.tFactory.setErrorListener(new StylesheetErrorListener());
    this.templateCaching = templateCaching;
  }

  /** @return returns the identity transformer handler. */
  public TransformerHandler getIdentityTransformerHandler()
      throws TransformerConfigurationException {
    return tFactory.newTransformerHandler();
  }

  /** Retrieves a previously stored template, if available. */
  public Templates getTemplate(String key) {
    if (!isCaching()) {
      return null;
    }

    return stylesheets.get(key);
  }

  /**
   * Add a new template to the pool. Addition is quite costly as it replaces the internal {@link
   * #stylesheets} {@link HashMap}.
   */
  public void addTemplate(String key, Templates template) {
    if (!isCaching()) {
      return;
    }

    /*
     * Copy-on-write.
     */
    synchronized (this) {
      final HashMap<String, Templates> newMap = new HashMap<String, Templates>(this.stylesheets);
      newMap.put(key, template);
      this.stylesheets = newMap;
    }
  }

  /** @return <code>true</code> if template caching is enabled. */
  private boolean isCaching() {
    /*
     * Global override takes precedence.
     */
    final String global = System.getProperty(TEMPLATE_CACHING_PROPERTY);
    if (global != null) {
      return Boolean.parseBoolean(global);
    }

    return templateCaching;
  }

  /**
   * Compile a {@link Templates} from a given system identifier. The template is not added to the
   * pool, a manual call to {@link #addTemplate(String, Templates)} is required.
   */
  public Templates compileTemplate(String systemId) throws SAXException {
    final StreamSource source = new StreamSource(systemId);
    try {
      return tFactory.newTemplates(source);
    } catch (Exception e) {
      throw new SAXException("Could not compile stylesheet: " + systemId, e);
    }
  }

  /**
   * Compile a {@link Templates} from a given stream. The template is not added to the pool
   * automatically.
   */
  public Templates compileTemplate(InputStream stream) throws SAXException {
    final StreamSource source = new StreamSource(stream);
    try {
      return tFactory.newTemplates(source);
    } catch (Exception e) {
      throw new SAXException("Could not compile stylesheet.", e);
    }
  }

  /**
   * Return a new {@link TransformerHandler} based on a given precompiled {@link Templates}. The
   * handler {@link Transformer}'s {@link ErrorListener} is set to {@link TransformerErrorListener}
   * to raise exceptions and give proper warnings.
   */
  public TransformerHandler newTransformerHandler(Templates template)
      throws TransformerConfigurationException {
    final TransformerHandler handler = this.tFactory.newTransformerHandler(template);

    /*
     * We want to raise transformer exceptions on <xml:message terminate="true">, so
     * we add a custom listener. Also, various XSLT processors react in different ways
     * to transformation errors -- some of them report error as recoverable, some of
     * them report error as unrecoverable.
     */
    handler.getTransformer().setErrorListener(new TransformerErrorListener());
    return handler;
  }

  /**
   * Return a new {@link Transformer}.
   *
   * @see #newTransformerHandler(Templates)
   */
  public Transformer newTransformer(Templates t) throws TransformerConfigurationException {
    return newTransformerHandler(t).getTransformer();
  }
}
/** @author pepijn */
public class WPTileProvider
    implements org.pepsoft.util.swing.TileProvider, Dimension.Listener, Tile.Listener {
  public WPTileProvider(
      Dimension dimension,
      ColourScheme colourScheme,
      BiomeScheme biomeScheme,
      CustomBiomeManager customBiomeManager,
      Collection<Layer> hiddenLayers,
      boolean contourLines,
      int contourSeparation,
      TileRenderer.LightOrigin lightOrigin,
      boolean showBorder,
      org.pepsoft.util.swing.TileProvider surroundingTileProvider,
      boolean active) {
    tileProvider = dimension;
    this.colourScheme = colourScheme;
    this.biomeScheme = biomeScheme;
    this.hiddenLayers = (hiddenLayers != null) ? new HashSet<>(hiddenLayers) : null;
    this.contourLines = contourLines;
    this.contourSeparation = contourSeparation;
    this.lightOrigin = lightOrigin;
    this.active = active;
    this.customBiomeManager = customBiomeManager;
    this.surroundingTileProvider = surroundingTileProvider;
    this.showBorder = showBorder;
    tileRendererRef = createNewTileRendererRef();
  }

  public WPTileProvider(
      TileProvider tileProvider,
      ColourScheme colourScheme,
      BiomeScheme biomeScheme,
      CustomBiomeManager customBiomeManager,
      Collection<Layer> hiddenLayers,
      boolean contourLines,
      int contourSeparation,
      TileRenderer.LightOrigin lightOrigin,
      boolean showBorder,
      org.pepsoft.util.swing.TileProvider surroundingTileProvider) {
    this.tileProvider = tileProvider;
    this.colourScheme = colourScheme;
    this.biomeScheme = biomeScheme;
    this.hiddenLayers = (hiddenLayers != null) ? new HashSet<>(hiddenLayers) : null;
    this.contourLines = contourLines;
    this.contourSeparation = contourSeparation;
    this.lightOrigin = lightOrigin;
    active = false;
    this.customBiomeManager = customBiomeManager;
    this.surroundingTileProvider = surroundingTileProvider;
    this.showBorder = showBorder;
    tileRendererRef = createNewTileRendererRef();
  }

  public synchronized void addHiddenLayer(Layer layer) {
    hiddenLayers.add(layer);
    tileRendererRef = createNewTileRendererRef();
  }

  public synchronized void removeHiddenLayer(Layer layer) {
    hiddenLayers.remove(layer);
    tileRendererRef = createNewTileRendererRef();
  }

  @Override
  public int getTileSize() {
    return TILE_SIZE;
  }

  @Override
  public boolean isTilePresent(int x, int y) {
    if (zoom == 0) {
      return getUnzoomedTileType(x, y) != TileType.SURROUNDS
          || ((surroundingTileProvider != null) && surroundingTileProvider.isTilePresent(x, y));
    } else {
      final int scale = 1 << -zoom;
      for (int dx = 0; dx < scale; dx++) {
        for (int dy = 0; dy < scale; dy++) {
          switch (getUnzoomedTileType(x * scale + dx, y * scale + dy)) {
            case WORLD:
            case BORDER:
            case WALL:
              return true;
            case SURROUNDS:
              if ((surroundingTileProvider != null)
                  && surroundingTileProvider.isTilePresent(x, y)) {
                return true;
              }
              break;
          }
        }
      }
      return false;
    }
  }

  @Override
  public void paintTile(
      final Image tileImage, final int x, final int y, final int imageX, final int imageY) {
    try {
      if (zoom == 0) {
        paintUnzoomedTile(tileImage, x, y, imageX, imageY);
      } else {
        Graphics2D g2 = (Graphics2D) tileImage.getGraphics();
        try {
          BufferedImage surroundingTileImage = null;
          final Color waterColour = new Color(colourScheme.getColour(BLK_WATER));
          final Color lavaColour = new Color(colourScheme.getColour(BLK_LAVA));
          final Color voidColour = new Color(VoidRenderer.getColour());
          final Color bedrockColour = new Color(colourScheme.getColour(BLK_BEDROCK));
          final int scale = 1 << -zoom;
          final int subSize = TILE_SIZE / scale;
          for (int dx = 0; dx < scale; dx++) {
            for (int dy = 0; dy < scale; dy++) {
              TileType tileType = getUnzoomedTileType(x * scale + dx, y * scale + dy);
              switch (tileType) {
                case WORLD:
                  Tile tile = tileProvider.getTile(x * scale + dx, y * scale + dy);
                  if (tile.hasLayer(NotPresent.INSTANCE)) {
                    if (surroundingTileProvider != null) {
                      if (surroundingTileImage == null) {
                        surroundingTileImage =
                            new BufferedImage(TILE_SIZE, TILE_SIZE, BufferedImage.TYPE_INT_ARGB);
                        surroundingTileProvider.paintTile(surroundingTileImage, x, y, 0, 0);
                      }
                      g2.drawImage(
                          surroundingTileImage,
                          imageX + dx * subSize,
                          imageY + dy * subSize,
                          imageX + (dx + 1) * subSize,
                          imageY + (dy + 1) * subSize,
                          imageX + dx * subSize,
                          imageY + dy * subSize,
                          imageX + (dx + 1) * subSize,
                          imageY + (dy + 1) * subSize,
                          null);
                    } else {
                      g2.setColor(voidColour);
                      g2.fillRect(imageX + dx * subSize, imageY + dy * subSize, subSize, subSize);
                    }
                  }
                  TileRenderer tileRenderer = tileRendererRef.get();
                  tileRenderer.setTile(tile);
                  tileRenderer.renderTile(tileImage, dx * subSize, dy * subSize);
                  break;
                case BORDER:
                  Color colour;
                  switch (((Dimension) tileProvider).getBorder()) {
                    case WATER:
                      colour = waterColour;
                      break;
                    case LAVA:
                      colour = lavaColour;
                      break;
                    case VOID:
                      colour = voidColour;
                      break;
                    default:
                      throw new InternalError();
                  }
                  g2.setColor(colour);
                  g2.fillRect(imageX + dx * subSize, imageY + dy * subSize, subSize, subSize);

                  // Draw border lines
                  g2.setColor(Color.BLACK);
                  g2.setStroke(
                      new BasicStroke(
                          2,
                          BasicStroke.CAP_BUTT,
                          BasicStroke.JOIN_BEVEL,
                          0.0f,
                          new float[] {4.0f, 4.0f},
                          0.0f));
                  if (tileProvider.isTilePresent(x * scale + dx, y * scale + dy - 1)) {
                    g2.drawLine(
                        imageX + dx * subSize,
                        imageY + dy * subSize,
                        imageX + (dx + 1) * subSize - 1,
                        imageY + dy * subSize);
                  }
                  if (tileProvider.isTilePresent(x * scale + dx + 1, y * scale + dy)) {
                    g2.drawLine(
                        imageX + (dx + 1) * subSize - 1,
                        imageY + dy * subSize,
                        imageX + (dx + 1) * subSize - 1,
                        imageY + (dy + 1) * subSize - 1);
                  }
                  if (tileProvider.isTilePresent(x * scale + dx, y * scale + dy + 1)) {
                    g2.drawLine(
                        imageX + dx * subSize,
                        imageY + (dy + 1) * subSize - 1,
                        imageX + (dx + 1) * subSize - 1,
                        imageY + (dy + 1) * subSize - 1);
                  }
                  if (tileProvider.isTilePresent(x * scale + dx - 1, y * scale + dy)) {
                    g2.drawLine(
                        imageX + dx * subSize,
                        imageY + dy * subSize,
                        imageX + dx * subSize,
                        imageY + (dy + 1) * subSize - 1);
                  }
                  break;
                case SURROUNDS:
                case WALL:
                  if (surroundingTileProvider != null) {
                    if (surroundingTileImage == null) {
                      surroundingTileImage =
                          new BufferedImage(TILE_SIZE, TILE_SIZE, BufferedImage.TYPE_INT_ARGB);
                      surroundingTileProvider.paintTile(surroundingTileImage, x, y, 0, 0);
                    }
                    g2.drawImage(
                        surroundingTileImage,
                        imageX + dx * subSize,
                        imageY + dy * subSize,
                        imageX + (dx + 1) * subSize,
                        imageY + (dy + 1) * subSize,
                        imageX + dx * subSize,
                        imageY + dy * subSize,
                        imageX + (dx + 1) * subSize,
                        imageY + (dy + 1) * subSize,
                        null);
                  } else {
                    g2.setColor(voidColour);
                    g2.fillRect(imageX + dx * subSize, imageY + dy * subSize, subSize, subSize);
                  }
                  if (tileType == TileType.WALL) {
                    g2.setColor(bedrockColour);
                    TileType neighbourType =
                        getUnzoomedTileType(x * scale + dx, y * scale + dy - 1);
                    int wallWidth = Math.max(subSize / 8, 1);
                    if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) {
                      g2.fillRect(imageX + dx * subSize, imageY + dy * subSize, subSize, wallWidth);
                    }
                    neighbourType = getUnzoomedTileType(x * scale + dx + 1, y * scale + dy);
                    if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) {
                      g2.fillRect(
                          imageX + (dx + 1) * subSize - wallWidth,
                          imageY + dy * subSize,
                          wallWidth,
                          subSize);
                    }
                    neighbourType = getUnzoomedTileType(x * scale + dx, y * scale + dy + 1);
                    if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) {
                      g2.fillRect(
                          imageX + dx * subSize,
                          imageY + (dy + 1) * subSize - wallWidth,
                          subSize,
                          wallWidth);
                    }
                    neighbourType = getUnzoomedTileType(x * scale + dx - 1, y * scale + dy);
                    if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) {
                      g2.fillRect(imageX + dx * subSize, imageY + dy * subSize, wallWidth, subSize);
                    }
                  }
                  break;
              }
            }
          }
        } finally {
          g2.dispose();
        }
      }
    } catch (Throwable e) {
      // Log at debug level because this tends to happen when zooming in
      // and out, probably due to some state getting out of sync. It
      // doesn't so far appear to have any visible consequences.
      logger.error("Exception while generating image for tile at " + x + ", " + y, e);
    }
  }

  @Override
  public int getTilePriority(int x, int y) {
    if (zoom == 0) {
      return (getUnzoomedTileType(x, y) == TileType.WORLD) ? 1 : 0;
    } else {
      final int scale = 1 << -zoom;
      for (int dx = 0; dx < scale; dx++) {
        for (int dy = 0; dy < scale; dy++) {
          if (getUnzoomedTileType(x * scale + dx, y * scale + dy) == TileType.WORLD) {
            return 1;
          }
        }
      }
      return 0;
    }
  }

  @Override
  public Rectangle getExtent() {
    Rectangle sourceExtent = tileProvider.getExtent();
    if (sourceExtent != null) {
      if (zoom == 0) {
        return sourceExtent;
      } else if (zoom < 0) {
        return new Rectangle(
            sourceExtent.x >> -zoom,
            sourceExtent.y >> -zoom,
            sourceExtent.width >> -zoom,
            sourceExtent.height >> -zoom);
      } else {
        return new Rectangle(
            sourceExtent.x << zoom,
            sourceExtent.y << zoom,
            sourceExtent.width << zoom,
            sourceExtent.height << zoom);
      }
    } else {
      return null;
    }
  }

  @Override
  public void addTileListener(TileListener tileListener) {
    if (active && listeners.isEmpty()) {
      ((Dimension) tileProvider).addDimensionListener(this);
      for (Tile tile : ((Dimension) tileProvider).getTiles()) {
        tile.addListener(this);
      }
    }
    if (!listeners.contains(tileListener)) {
      listeners.add(tileListener);
    }
  }

  @Override
  public void removeTileListener(TileListener tileListener) {
    listeners.remove(tileListener);
    if (active && listeners.isEmpty()) {
      for (Tile tile : ((Dimension) tileProvider).getTiles()) {
        tile.removeListener(this);
      }
      ((Dimension) tileProvider).removeDimensionListener(this);
    }
  }

  @Override
  public boolean isZoomSupported() {
    return true;
  }

  @Override
  public int getZoom() {
    return zoom;
  }

  @Override
  public void setZoom(int zoom) {
    if (zoom != this.zoom) {
      if (zoom > 0) {
        throw new UnsupportedOperationException("Zooming in not supported");
      }
      this.zoom = zoom;
      tileRendererRef = createNewTileRendererRef();
      if (surroundingTileProvider != null) {
        surroundingTileProvider.setZoom(zoom);
      }
    }
  }

  // Dimension.Listener

  @Override
  public void tilesAdded(Dimension dimension, Set<Tile> tiles) {
    for (Tile tile : tiles) {
      tile.addListener(this);
    }
    fireTilesChangedIncludeBorder(tiles);
  }

  @Override
  public void tilesRemoved(Dimension dimension, Set<Tile> tiles) {
    for (Tile tile : tiles) {
      tile.removeListener(this);
    }
    fireTilesChangedIncludeBorder(tiles);
  }

  // Tile.Listener

  @Override
  public void heightMapChanged(Tile tile) {
    fireTileChanged(tile);
  }

  @Override
  public void terrainChanged(Tile tile) {
    fireTileChanged(tile);
  }

  @Override
  public void waterLevelChanged(Tile tile) {
    fireTileChanged(tile);
  }

  @Override
  public void layerDataChanged(Tile tile, Set<Layer> changedLayers) {
    fireTileChanged(tile);
  }

  @Override
  public void allBitLayerDataChanged(Tile tile) {
    fireTileChanged(tile);
  }

  @Override
  public void allNonBitlayerDataChanged(Tile tile) {
    fireTileChanged(tile);
  }

  @Override
  public void seedsChanged(Tile tile) {
    fireTileChanged(tile);
  }

  private TileType getUnzoomedTileType(int x, int y) {
    if (tileProvider.isTilePresent(x, y)) {
      return TileType.WORLD;
    } else if (showBorder && (tileProvider instanceof Dimension)) {
      Dimension dimension = (Dimension) tileProvider;
      if (dimension.isBorderTile(x, y)) {
        return TileType.BORDER;
      } else if (dimension.isBedrockWall()
          && ((dimension.getBorder() != null)
              ? (dimension.isBorderTile(x - 1, y)
                  || dimension.isBorderTile(x, y - 1)
                  || dimension.isBorderTile(x + 1, y)
                  || dimension.isBorderTile(x, y + 1))
              : (tileProvider.isTilePresent(x - 1, y)
                  || tileProvider.isTilePresent(x, y - 1)
                  || tileProvider.isTilePresent(x + 1, y)
                  || tileProvider.isTilePresent(x, y + 1)))) {
        return TileType.WALL;
      }
    }
    return TileType.SURROUNDS;
  }

  private void paintUnzoomedTile(
      final Image tileImage, final int x, final int y, final int dx, final int dy) {
    TileType tileType = getUnzoomedTileType(x, y);
    switch (tileType) {
      case WORLD:
        Tile tile = tileProvider.getTile(x, y);
        if (tile.hasLayer(NotPresent.INSTANCE) && (surroundingTileProvider != null)) {
          surroundingTileProvider.paintTile(tileImage, x, y, dx, dy);
        }
        TileRenderer tileRenderer = tileRendererRef.get();
        tileRenderer.setTile(tile);
        tileRenderer.renderTile(tileImage, dx, dy);
        break;
      case BORDER:
        int colour;
        switch (((Dimension) tileProvider).getBorder()) {
          case WATER:
            colour = colourScheme.getColour(BLK_WATER);
            break;
          case LAVA:
            colour = colourScheme.getColour(BLK_LAVA);
            break;
          case VOID:
            colour = VoidRenderer.getColour();
            break;
          default:
            throw new InternalError();
        }
        Graphics2D g2 = (Graphics2D) tileImage.getGraphics();
        try {
          g2.setColor(new Color(colour));
          g2.fillRect(dx, dy, TILE_SIZE, TILE_SIZE);

          // Draw border lines
          g2.setColor(Color.BLACK);
          g2.setStroke(
              new BasicStroke(
                  2,
                  BasicStroke.CAP_BUTT,
                  BasicStroke.JOIN_BEVEL,
                  0.0f,
                  new float[] {4.0f, 4.0f},
                  0.0f));
          if (tileProvider.isTilePresent(x, y - 1)) {
            g2.drawLine(dx + 1, dy + 1, dx + TILE_SIZE - 1, dy + 1);
          }
          if (tileProvider.isTilePresent(x + 1, y)) {
            g2.drawLine(dx + TILE_SIZE - 1, dy + 1, dx + TILE_SIZE - 1, dy + TILE_SIZE - 1);
          }
          if (tileProvider.isTilePresent(x, y + 1)) {
            g2.drawLine(dx + 1, dy + TILE_SIZE - 1, dx + TILE_SIZE - 1, dy + TILE_SIZE - 1);
          }
          if (tileProvider.isTilePresent(x - 1, y)) {
            g2.drawLine(dx + 1, dy + 1, dx + 1, dy + TILE_SIZE - 1);
          }
        } finally {
          g2.dispose();
        }
        break;
      case WALL:
        if (surroundingTileProvider != null) {
          surroundingTileProvider.paintTile(tileImage, x, y, dx, dy);
        }
        g2 = (Graphics2D) tileImage.getGraphics();
        try {
          if (surroundingTileProvider == null) {
            // A surrounding tile provider would have completely
            // filled the image, but since there isn't one we have
            // to make sure of that ourselves
            g2.setColor(new Color(VoidRenderer.getColour()));
            g2.fillRect(dx, dy, TILE_SIZE, TILE_SIZE);
          }
          g2.setColor(new Color(colourScheme.getColour(BLK_BEDROCK)));
          TileType neighbourType = getUnzoomedTileType(x, y - 1);
          if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) {
            g2.fillRect(dx, dy, TILE_SIZE, 16);
          }
          neighbourType = getUnzoomedTileType(x + 1, y);
          if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) {
            g2.fillRect(dx + TILE_SIZE - 16, dy, 16, TILE_SIZE);
          }
          neighbourType = getUnzoomedTileType(x, y + 1);
          if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) {
            g2.fillRect(dx, dy + TILE_SIZE - 16, TILE_SIZE, 16);
          }
          neighbourType = getUnzoomedTileType(x - 1, y);
          if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) {
            g2.fillRect(dx, dy, 16, TILE_SIZE);
          }
        } finally {
          g2.dispose();
        }
        break;
      case SURROUNDS:
        if (surroundingTileProvider != null) {
          surroundingTileProvider.paintTile(tileImage, x, y, dx, dy);
        }
        break;
      default:
        throw new InternalError();
    }
  }

  private void fireTileChanged(Tile tile) {
    Point coords = getTileCoordinates(tile);
    for (TileListener listener : listeners) {
      listener.tileChanged(this, coords.x, coords.y);
    }
  }

  private void fireTilesChangedIncludeBorder(Set<Tile> tiles) {
    if (showBorder
        && (tileProvider instanceof Dimension)
        && (((Dimension) tileProvider).getDim() == DIM_NORMAL)
        && (((Dimension) tileProvider).getBorder() != null)) {
      final Set<Point> coordSet = new HashSet<>();
      for (Tile tile : tiles) {
        final int tileX = tile.getX(),
            tileY = tile.getY(),
            borderSize = ((Dimension) tileProvider).getBorderSize();
        for (int dx = -borderSize; dx <= borderSize; dx++) {
          for (int dy = -borderSize; dy <= borderSize; dy++) {
            coordSet.add(getTileCoordinates(tileX + dx, tileY + dy));
          }
        }
      }
      for (TileListener listener : listeners) {
        listener.tilesChanged(this, coordSet);
      }
    } else {
      Set<Point> coords = tiles.stream().map(this::getTileCoordinates).collect(Collectors.toSet());
      for (TileListener listener : listeners) {
        listener.tilesChanged(this, coords);
      }
    }
  }

  /**
   * Convert the actual tile coordinates to zoom-corrected (tile provider coordinate system)
   * coordinates.
   *
   * @param tile The tile of which to convert the coordinates.
   * @return The coordinates of the tile in the tile provider coordinate system (corrected for
   *     zoom).
   */
  private Point getTileCoordinates(Tile tile) {
    return getTileCoordinates(tile.getX(), tile.getY());
  }

  /**
   * Convert the actual tile coordinates to zoom-corrected (tile provider coordinate system)
   * coordinates.
   *
   * @param tileX The X tile coordinate to convert.
   * @param tileY The Y tile coordinate to convert.
   * @return The coordinates of the tile in the tile provider coordinate system (corrected for
   *     zoom).
   */
  private Point getTileCoordinates(final int tileX, final int tileY) {
    if (zoom == 0) {
      return new Point(tileX, tileY);
    } else if (zoom < 0) {
      return new Point(tileX >> -zoom, tileY >> -zoom);
    } else {
      return new Point(tileX << zoom, tileY << zoom);
    }
  }

  @NotNull
  private ThreadLocal<TileRenderer> createNewTileRendererRef() {
    return new ThreadLocal<TileRenderer>() {
      @Override
      protected TileRenderer initialValue() {
        TileRenderer tileRenderer =
            new TileRenderer(tileProvider, colourScheme, biomeScheme, customBiomeManager, zoom);
        synchronized (WPTileProvider.this) {
          if (hiddenLayers != null) {
            tileRenderer.addHiddenLayers(hiddenLayers);
          }
        }
        tileRenderer.setContourLines(contourLines);
        tileRenderer.setContourSeparation(contourSeparation);
        tileRenderer.setLightOrigin(lightOrigin);
        return tileRenderer;
      }
    };
  }

  private final TileProvider tileProvider;
  private final ColourScheme colourScheme;
  private final BiomeScheme biomeScheme;
  private final Set<Layer> hiddenLayers;
  private final boolean contourLines, active, showBorder;
  private final int contourSeparation;
  private final TileRenderer.LightOrigin lightOrigin;
  private final List<TileListener> listeners = new ArrayList<>();
  private final CustomBiomeManager customBiomeManager;
  private final org.pepsoft.util.swing.TileProvider surroundingTileProvider;
  private int zoom = 0;
  private volatile ThreadLocal<TileRenderer> tileRendererRef;

  private static final org.slf4j.Logger logger =
      org.slf4j.LoggerFactory.getLogger(WPTileProvider.class);

  private enum TileType {
    /** The tile is part of the WorldPainter world. */
    WORLD,
    /** The tile is part of the WorldPainter border. */
    BORDER,
    /** The tile contains no WorldPainter-generated chunks. */
    SURROUNDS,
    /** The tile is outside the WorldPainter world and border but does contain part of a wall. */
    WALL
  }
}
Exemple #25
0
/**
 * A georeferencing "gridded" VariableEnhanced, that has a GridCoordSys. In VisAD data model, it is
 * a sampled Field. The dimension are put into canonical order: (rt, e, t, z, y, x).
 *
 * <p>
 *
 * <p>Implementation note: If the Horizontal axes are 2D, the x and y dimensions are arbitrarily
 * chosen to be gcs.getXHorizAxis().getDimension(1), gcs.getXHorizAxis().getDimension(0)
 * respectively.
 *
 * <p>
 *
 * @author caron
 */
public class GeoGrid implements NamedObject, ucar.nc2.dt.GridDatatype {
  private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GeoGrid.class);
  private static final boolean debugArrayShape = false;

  private final GridDataset dataset;
  private final GridCoordSys gcs;
  private final VariableDS vs;
  private int xDimOrgIndex = -1,
      yDimOrgIndex = -1,
      zDimOrgIndex = -1,
      tDimOrgIndex = -1,
      eDimOrgIndex = -1,
      rtDimOrgIndex = -1;
  private int xDimNewIndex = -1,
      yDimNewIndex = -1,
      zDimNewIndex = -1,
      tDimNewIndex = -1,
      eDimNewIndex = -1,
      rtDimNewIndex = -1;
  private final List<Dimension> mydims;

  /**
   * Constructor.
   *
   * @param dataset belongs to this dataset
   * @param dsvar wraps this Variable
   * @param gcs has this grid coordinate system
   */
  public GeoGrid(GridDataset dataset, VariableDS dsvar, GridCoordSys gcs) {
    this.dataset = dataset;
    this.vs = dsvar;
    this.gcs = gcs;

    CoordinateAxis xaxis = gcs.getXHorizAxis();
    if (xaxis instanceof CoordinateAxis1D) {
      xDimOrgIndex = findDimension(gcs.getXHorizAxis().getDimension(0));
      yDimOrgIndex = findDimension(gcs.getYHorizAxis().getDimension(0));

    } else { // 2D case
      yDimOrgIndex = findDimension(gcs.getXHorizAxis().getDimension(0));
      xDimOrgIndex = findDimension(gcs.getXHorizAxis().getDimension(1));
    }

    if (gcs.getVerticalAxis() != null)
      zDimOrgIndex = findDimension(gcs.getVerticalAxis().getDimension(0));
    if (gcs.getTimeAxis() != null) {
      if (gcs.getTimeAxis1D() != null)
        tDimOrgIndex = findDimension(gcs.getTimeAxis1D().getDimension(0));
      else tDimOrgIndex = findDimension(gcs.getTimeAxis().getDimension(1));
    }
    if (gcs.getEnsembleAxis() != null)
      eDimOrgIndex = findDimension(gcs.getEnsembleAxis().getDimension(0));
    if (gcs.getRunTimeAxis() != null)
      rtDimOrgIndex = findDimension(gcs.getRunTimeAxis().getDimension(0));

    // construct canonical dimension list
    int count = 0;
    this.mydims = new ArrayList<Dimension>();
    if ((rtDimOrgIndex >= 0) && (rtDimOrgIndex != tDimOrgIndex)) {
      mydims.add(dsvar.getDimension(rtDimOrgIndex));
      rtDimNewIndex = count++;
    }
    if (eDimOrgIndex >= 0) {
      mydims.add(dsvar.getDimension(eDimOrgIndex));
      eDimNewIndex = count++;
    }
    if (tDimOrgIndex >= 0) {
      mydims.add(dsvar.getDimension(tDimOrgIndex));
      tDimNewIndex = count++;
    }
    if (zDimOrgIndex >= 0) {
      mydims.add(dsvar.getDimension(zDimOrgIndex));
      zDimNewIndex = count++;
    }
    if (yDimOrgIndex >= 0) {
      mydims.add(dsvar.getDimension(yDimOrgIndex));
      yDimNewIndex = count++;
    }
    if (xDimOrgIndex >= 0) {
      mydims.add(dsvar.getDimension(xDimOrgIndex));
      xDimNewIndex = count;
    }
  }

  private int findDimension(Dimension want) {
    java.util.List dims = vs.getDimensions();
    for (int i = 0; i < dims.size(); i++) {
      Dimension d = (Dimension) dims.get(i);
      if (d.equals(want)) return i;
    }
    return -1;
  }

  /**
   * Returns an ArrayList containing the dimensions used by this geoGrid. The dimension are put into
   * canonical order: (rt, e, t, z, y, x). Note that the z and t dimensions are optional. If the
   * Horizontal axes are 2D, the x and y dimensions are arbitrarily chosen to be
   * gcs.getXHorizAxis().getDimension(1), gcs.getXHorizAxis().getDimension(0), respectively.
   *
   * @return List with objects of type Dimension, in canonical order.
   */
  public java.util.List<Dimension> getDimensions() {
    return new ArrayList<Dimension>(mydims);
  }

  /**
   * get the ith dimension
   *
   * @param i : which dimension
   * @return ith Dimension
   */
  public Dimension getDimension(int i) {
    if ((i < 0) || (i >= mydims.size())) return null;
    return mydims.get(i);
  }

  /** get the time Dimension, if it exists */
  public Dimension getTimeDimension() {
    return tDimNewIndex < 0 ? null : getDimension(tDimNewIndex);
  }

  /** get the z Dimension, if it exists */
  public Dimension getZDimension() {
    return zDimNewIndex < 0 ? null : getDimension(zDimNewIndex);
  }

  /** get the y Dimension, if it exists */
  public Dimension getYDimension() {
    return yDimNewIndex < 0 ? null : getDimension(yDimNewIndex);
  }

  /** get the x Dimension, if it exists */
  public Dimension getXDimension() {
    return xDimNewIndex < 0 ? null : getDimension(xDimNewIndex);
  }

  /** get the ensemble Dimension, if it exists */
  public Dimension getEnsembleDimension() {
    return eDimNewIndex < 0 ? null : getDimension(eDimNewIndex);
  }

  /** get the run time Dimension, if it exists */
  public Dimension getRunTimeDimension() {
    return rtDimNewIndex < 0 ? null : getDimension(rtDimNewIndex);
  }

  /** get the time Dimension index in the geogrid (canonical order), or -1 if none */
  public int getTimeDimensionIndex() {
    return tDimNewIndex;
  }

  /** get the z Dimension index in the geogrid (canonical order), or -1 if none */
  public int getZDimensionIndex() {
    return zDimNewIndex;
  }

  /** get the y Dimension index in the geogrid (canonical order) */
  public int getYDimensionIndex() {
    return yDimNewIndex;
  }

  /** get the x Dimension index in the geogrid (canonical order) */
  public int getXDimensionIndex() {
    return xDimNewIndex;
  }

  /** get the ensemble Dimension index in the geogrid (canonical order) */
  public int getEnsembleDimensionIndex() {
    return eDimNewIndex;
  }

  /** get the runtime Dimension index in the geogrid (canonical order) */
  public int getRunTimeDimensionIndex() {
    return rtDimNewIndex;
  }

  /**
   * Convenience function; lookup Attribute by name.
   *
   * @param name the name of the attribute
   * @return the attribute, or null if not found
   */
  public Attribute findAttributeIgnoreCase(String name) {
    return vs.findAttributeIgnoreCase(name);
  }

  /**
   * Convenience function; lookup Attribute value by name. Must be String valued
   *
   * @param attName name of the attribute
   * @param defaultValue if not found, use this as the default
   * @return Attribute string value, or default if not found.
   */
  public String findAttValueIgnoreCase(String attName, String defaultValue) {
    return dataset.getNetcdfDataset().findAttValueIgnoreCase((Variable) vs, attName, defaultValue);
  }

  // implementation of GridDatatype interface

  /** get the rank */
  public int getRank() {
    return mydims.size();
  }

  /** get the shape */
  public int[] getShape() {
    int[] shape = new int[mydims.size()];
    for (int i = 0; i < mydims.size(); i++) {
      Dimension d = mydims.get(i);
      shape[i] = d.getLength();
    }
    return shape;
  }

  /** get the data type */
  public DataType getDataType() {
    return vs.getDataType();
  }

  public List<Attribute> getAttributes() {
    return vs.getAttributes();
  }

  public VariableDS getVariable() {
    return vs;
  }

  public String getFullName() {
    return vs.getFullName();
  }

  public String getName() {
    return vs.getFullName();
  }

  public String getShortName() {
    return vs.getShortName();
  }

  /** get the GridCoordSys for this GeoGrid. */
  public GridCoordSystem getCoordinateSystem() {
    return gcs;
  }

  /** get the Projection. */
  public ProjectionImpl getProjection() {
    return gcs.getProjection();
  }

  /** @return ArrayList of thredds.util.NamedObject, from the GridCoordSys. */
  public List<NamedObject> getLevels() {
    return gcs.getLevels();
  }

  /** @return ArrayList of thredds.util.NamedObject, from the GridCoordSys. */
  public List<NamedObject> getTimes() {
    return gcs.getTimes();
  }

  /** get the standardized description */
  public String getDescription() {
    return vs.getDescription();
  }

  /** get the unit as a string */
  public String getUnitsString() {
    String units = vs.getUnitsString();
    return (units == null) ? "" : units;
  }

  /**
   * @return getUnitsString()
   * @deprecated use getUnitsString()
   */
  public java.lang.String getUnitString() {
    return getUnitsString();
  }

  // public ucar.unidata.geoloc.ProjectionImpl getProjection() { return gcs.getProjection(); }

  /** true if there may be missing data, see VariableDS.hasMissing() */
  public boolean hasMissingData() {
    return vs.hasMissing();
  }

  /** if val is missing data, see VariableDS.isMissingData() */
  public boolean isMissingData(double val) {
    return vs.isMissing(val);
  }

  /**
   * Convert (in place) all values in the given array that are considered as "missing" to Float.NaN,
   * according to isMissingData(val).
   *
   * @param values input array
   * @return input array, with missing values converted to NaNs.
   */
  public float[] setMissingToNaN(float[] values) {
    if (!vs.hasMissing()) return values;
    final int length = values.length;
    for (int i = 0; i < length; i++) {
      double value = values[i];
      if (vs.isMissing(value)) values[i] = Float.NaN;
    }
    return values;
  }

  /**
   * Get the minimum and the maximum data value of the previously read Array, skipping missing
   * values as defined by isMissingData(double val).
   *
   * @param a Array to get min/max values
   * @return both min and max value.
   */
  public MAMath.MinMax getMinMaxSkipMissingData(Array a) {
    if (!hasMissingData()) return MAMath.getMinMax(a);

    IndexIterator iter = a.getIndexIterator();
    double max = -Double.MAX_VALUE;
    double min = Double.MAX_VALUE;
    while (iter.hasNext()) {
      double val = iter.getDoubleNext();
      if (isMissingData(val)) continue;
      if (val > max) max = val;
      if (val < min) min = val;
    }
    return new MAMath.MinMax(min, max);
  }

  /**
   * Reads in the data "volume" at the given time index. If its a product set, put into canonical
   * order (z-y-x). If not a product set, reorder to (z,i,j), where i, j are from the original
   *
   * @param t time index; ignored if no time axis.
   * @return data[z,y,x] or data[y,x] if no z axis.
   */
  public Array readVolumeData(int t) throws java.io.IOException {
    // if (gcs.isProductSet())
    return readDataSlice(t, -1, -1, -1);
    /* else { // 2D XY
      int rank = vs.getRank();
      int[] shape = vs.getShape();
      int [] start = new int[rank];

      CoordinateAxis taxis = gcs.getTimeAxis();
      if (taxis != null) {
        if ((t >= 0) && (t < taxis.getSize()))
          shape[ tDim] = 1;  // fix t
          start[ tDim] = t;
      }

      if (debugArrayShape) {
        System.out.println("getDataVolume shape = ");
        for (int i=0; i<rank; i++)
          System.out.println("   start = "+start[i]+" shape = "+ shape[i]);
      }

      Array dataVolume;
      try {
        dataVolume = vs.read( start, shape);
      } catch (Exception e) {
        System.out.println("Exception: GeoGridImpl.getdataSlice() on dataset "+getName());
        e.printStackTrace();
        throw new java.io.IOException(e.getMessage());
      }

      // no reordering FIX
      return dataVolume.reduce();
    } */
  }

  /**
   * Reads a Y-X "horizontal slice" at the given time and vertical index. If its a product set, put
   * into canonical order (y-x).
   *
   * @param t time index; ignored if no time axis.
   * @param z vertical index; ignored if no z axis.
   * @return data[y,x]
   * @throws java.io.IOException on read error
   */
  public Array readYXData(int t, int z) throws java.io.IOException {
    return readDataSlice(t, z, -1, -1);
  }

  /**
   * Reads a Z-Y "vertical slice" at the given time and x index. If its a product set, put into
   * canonical order (z-y).
   *
   * @param t time index; ignored if no time axis.
   * @param x x index; ignored if no x axis.
   * @return data[z,y]
   * @throws java.io.IOException on read error
   */
  public Array readZYData(int t, int x) throws java.io.IOException {
    return readDataSlice(t, -1, -1, x);
  }

  /**
   * @throws java.io.IOException on read error
   * @deprecated use readDataSlice
   */
  public Array getDataSlice(int t, int z, int y, int x) throws java.io.IOException {
    return readDataSlice(t, z, y, x);
  }

  /**
   * This reads an arbitrary data slice, returning the data in canonical order (t-z-y-x). If any
   * dimension does not exist, ignore it.
   *
   * @param t if < 0, get all of time dim; if valid index, fix slice to that value.
   * @param z if < 0, get all of z dim; if valid index, fix slice to that value.
   * @param y if < 0, get all of y dim; if valid index, fix slice to that value.
   * @param x if < 0, get all of x dim; if valid index, fix slice to that value.
   * @return data[t,z,y,x], eliminating missing or fixed dimension.
   */
  public Array readDataSlice(int t, int z, int y, int x) throws java.io.IOException {
    return readDataSlice(0, 0, t, z, y, x);
  }

  /**
   * This reads an arbitrary data slice, returning the data in canonical order (rt-e-t-z-y-x). If
   * any dimension does not exist, ignore it.
   *
   * @param rt if < 0, get all of runtime dim; if valid index, fix slice to that value.
   * @param e if < 0, get all of ensemble dim; if valid index, fix slice to that value.
   * @param t if < 0, get all of time dim; if valid index, fix slice to that value.
   * @param z if < 0, get all of z dim; if valid index, fix slice to that value.
   * @param y if < 0, get all of y dim; if valid index, fix slice to that value.
   * @param x if < 0, get all of x dim; if valid index, fix slice to that value.
   * @return data[rt,e,t,z,y,x], eliminating missing or fixed dimension.
   */
  public Array readDataSlice(int rt, int e, int t, int z, int y, int x) throws java.io.IOException {

    int rank = vs.getRank();
    int[] start = new int[rank];
    int[] shape = new int[rank];
    for (int i = 0; i < rank; i++) {
      start[i] = 0;
      shape[i] = 1;
    }
    Dimension xdim = getXDimension();
    Dimension ydim = getYDimension();
    Dimension zdim = getZDimension();
    Dimension tdim = getTimeDimension();
    Dimension edim = getEnsembleDimension();
    Dimension rtdim = getRunTimeDimension();

    // construct the shape of the data volume to be read
    if (rtdim != null) {
      if ((rt >= 0) && (rt < rtdim.getLength())) start[rtDimOrgIndex] = rt; // fix rt
      else {
        shape[rtDimOrgIndex] = rtdim.getLength(); // all of rt
      }
    }

    if (edim != null) {
      if ((e >= 0) && (e < edim.getLength())) start[eDimOrgIndex] = e; // fix e
      else {
        shape[eDimOrgIndex] = edim.getLength(); // all of e
      }
    }

    if (tdim != null) {
      if ((t >= 0) && (t < tdim.getLength())) start[tDimOrgIndex] = t; // fix t
      else {
        shape[tDimOrgIndex] = tdim.getLength(); // all of t
      }
    }

    if (zdim != null) {
      if ((z >= 0) && (z < zdim.getLength())) start[zDimOrgIndex] = z; // fix z
      else {
        shape[zDimOrgIndex] = zdim.getLength(); // all of z
      }
    }

    if (ydim != null) {
      if ((y >= 0) && (y < ydim.getLength())) start[yDimOrgIndex] = y; // fix y
      else {
        shape[yDimOrgIndex] = ydim.getLength(); // all of y
      }
    }

    if (xdim != null) {
      if ((x >= 0) && (x < xdim.getLength())) // all of x
      start[xDimOrgIndex] = x; // fix x
      else {
        shape[xDimOrgIndex] = xdim.getLength(); // all of x
      }
    }

    if (debugArrayShape) {
      System.out.println("read shape from org variable = ");
      for (int i = 0; i < rank; i++)
        System.out.println(
            "   start = "
                + start[i]
                + " shape = "
                + shape[i]
                + " name = "
                + vs.getDimension(i).getName());
    }

    // read it
    Array dataVolume;
    try {
      dataVolume = vs.read(start, shape);
    } catch (Exception ex) {
      log.error(
          "GeoGrid.getdataSlice() on dataset " + getFullName() + " " + dataset.getLocation(), ex);
      throw new java.io.IOException(ex.getMessage());
    }

    // LOOK: the real problem is the lack of named dimensions in the Array object
    // figure out correct permutation for canonical ordering for permute
    List<Dimension> oldDims = new ArrayList<Dimension>(vs.getDimensions());
    int[] permuteIndex = new int[dataVolume.getRank()];
    int count = 0;
    if (oldDims.contains(rtdim)) permuteIndex[count++] = oldDims.indexOf(rtdim);
    if (oldDims.contains(edim)) permuteIndex[count++] = oldDims.indexOf(edim);
    if (oldDims.contains(tdim)) permuteIndex[count++] = oldDims.indexOf(tdim);
    if (oldDims.contains(zdim)) permuteIndex[count++] = oldDims.indexOf(zdim);
    if (oldDims.contains(ydim)) permuteIndex[count++] = oldDims.indexOf(ydim);
    if (oldDims.contains(xdim)) permuteIndex[count] = oldDims.indexOf(xdim);

    if (debugArrayShape) {
      System.out.println("oldDims = ");
      for (Dimension oldDim : oldDims) System.out.println("   oldDim = " + oldDim.getName());
      System.out.println("permute dims = ");
      for (int aPermuteIndex : permuteIndex)
        System.out.println("   oldDim index = " + aPermuteIndex);
    }

    // check to see if we need to permute
    boolean needPermute = false;
    for (int i = 0; i < permuteIndex.length; i++) {
      if (i != permuteIndex[i]) needPermute = true;
    }

    // permute to the order rt,e,t,z,y,x
    if (needPermute) dataVolume = dataVolume.permute(permuteIndex);

    // eliminate fixed dimensions, but not all dimensions of length 1.
    count = 0;
    if (rtdim != null) {
      if (rt >= 0) dataVolume = dataVolume.reduce(count);
      else count++;
    }
    if (edim != null) {
      if (e >= 0) dataVolume = dataVolume.reduce(count);
      else count++;
    }
    if (tdim != null) {
      if (t >= 0) dataVolume = dataVolume.reduce(count);
      else count++;
    }
    if (zdim != null) {
      if (z >= 0) dataVolume = dataVolume.reduce(count);
      else count++;
    }
    if (ydim != null) {
      if (y >= 0) dataVolume = dataVolume.reduce(count);
      else count++;
    }
    if (xdim != null) {
      if (x >= 0) dataVolume = dataVolume.reduce(count);
    }

    return dataVolume;
  }

  //////////////////////////////////

  /**
   * Create a new GeoGrid that is a logical subset of this GeoGrid.
   *
   * @param t_range subset the time dimension, or null if you want all of it
   * @param z_range subset the vertical dimension, or null if you want all of it
   * @param bbox a lat/lon bounding box, or null if you want all x,y
   * @param z_stride use only if z_range is null, then take all z with this stride (1 means all)
   * @param y_stride use this stride on the y coordinate (1 means all)
   * @param x_stride use this stride on the x coordinate (1 means all)
   * @return subsetted GeoGrid
   * @throws InvalidRangeException if bbox does not intersect GeoGrid
   */
  public GeoGrid subset(
      Range t_range, Range z_range, LatLonRect bbox, int z_stride, int y_stride, int x_stride)
      throws InvalidRangeException {

    if ((z_range == null) && (z_stride > 1)) {
      Dimension zdim = getZDimension();
      if (zdim != null) z_range = new Range(0, zdim.getLength() - 1, z_stride);
    }

    Range y_range = null, x_range = null;
    if (bbox != null) {
      List yx_ranges = gcs.getRangesFromLatLonRect(bbox);
      y_range = (Range) yx_ranges.get(0);
      x_range = (Range) yx_ranges.get(1);
    }

    if (y_stride > 1) {
      if (y_range == null) {
        Dimension ydim = getYDimension();
        y_range = new Range(0, ydim.getLength() - 1, y_stride);
      } else {
        y_range = new Range(y_range.first(), y_range.last(), y_stride);
      }
    }

    if (x_stride > 1) {
      if (x_range == null) {
        Dimension xdim = getXDimension();
        x_range = new Range(0, xdim.getLength() - 1, x_stride);
      } else {
        x_range = new Range(x_range.first(), x_range.last(), x_stride);
      }
    }

    return subset(t_range, z_range, y_range, x_range);
  }

  public GridDatatype makeSubset(
      Range t_range, Range z_range, LatLonRect bbox, int z_stride, int y_stride, int x_stride)
      throws InvalidRangeException {
    return subset(t_range, z_range, bbox, z_stride, y_stride, x_stride);
  }

  /**
   * Create a new GeoGrid that is a logical subset of this GeoGrid.
   *
   * @param t_range subset the time dimension, or null if you want all of it
   * @param z_range subset the vertical dimension, or null if you want all of it
   * @param y_range subset the y dimension, or null if you want all of it
   * @param x_range subset the x dimension, or null if you want all of it
   * @return subsetted GeoGrid
   * @throws InvalidRangeException if any of the ranges are invalid
   */
  public GeoGrid subset(Range t_range, Range z_range, Range y_range, Range x_range)
      throws InvalidRangeException {
    return (GeoGrid) makeSubset(null, null, t_range, z_range, y_range, x_range);
  }

  public GridDatatype makeSubset(
      Range rt_range, Range e_range, Range t_range, Range z_range, Range y_range, Range x_range)
      throws InvalidRangeException {
    // get the ranges list
    int rank = getRank();
    Range[] ranges = new Range[rank];
    if (null != getXDimension()) ranges[xDimOrgIndex] = x_range;
    if (null != getYDimension()) ranges[yDimOrgIndex] = y_range;
    if (null != getZDimension()) ranges[zDimOrgIndex] = z_range;
    if (null != getTimeDimension()) ranges[tDimOrgIndex] = t_range;
    if (null != getRunTimeDimension()) ranges[rtDimOrgIndex] = rt_range;
    if (null != getEnsembleDimension()) ranges[eDimOrgIndex] = e_range;
    List<Range> rangesList = Arrays.asList(ranges);

    // subset the variable
    VariableDS v_section = (VariableDS) vs.section(new Section(rangesList));
    List<Dimension> dims = v_section.getDimensions();
    for (Dimension dim : dims) {
      dim.setShared(true); // make them shared (section will make them unshared)
    }

    // subset the axes in the GridCoordSys
    GridCoordSys gcs_section =
        new GridCoordSys(gcs, rt_range, e_range, t_range, z_range, y_range, x_range);

    // now we can make the geogrid
    return new GeoGrid(dataset, v_section, gcs_section);
  }

  /////////////////////////////////////////////////////////////////////////////////
  /** Instances which have same name and coordinate system are equal. */
  public boolean equals(Object oo) {
    if (this == oo) return true;
    if (!(oo instanceof GeoGrid)) return false;

    GeoGrid d = (GeoGrid) oo;
    if (!getFullName().equals(d.getFullName())) return false;
    if (!getCoordinateSystem().equals(d.getCoordinateSystem())) return false;

    return true;
  }

  /** Override Object.hashCode() to be consistent with equals. */
  public int hashCode() {
    if (hashCode == 0) {
      int result = 17;
      // result = 37*result + dataset.getName().hashCode();
      result = 37 * result + getFullName().hashCode();
      result = 37 * result + getCoordinateSystem().hashCode();
      hashCode = result;
    }
    return hashCode;
  }

  private int hashCode = 0; // Bloch, item 8

  /** string representation */
  public String toString() {
    return getFullName();
  }

  /** nicely formatted information */
  public String getInfo() {
    StringBuilder buf = new StringBuilder(200);
    buf.setLength(0);
    buf.append(getFullName());
    Format.tab(buf, 30, true);
    buf.append(getUnitsString());
    Format.tab(buf, 60, true);
    buf.append(hasMissingData());
    Format.tab(buf, 66, true);
    buf.append(getDescription());
    return buf.toString();
  }

  public int compareTo(GridDatatype g) {
    return getFullName().compareTo(g.getFullName());
  }
}
/**
 * Jahia specific wrapper around <code>javax.jcr.Session</code> to be able to inject Jahia specific
 * actions and to manage sessions to multiple repository providers in the backend.
 *
 * <p>Jahia services should use this wrapper rather than the original session interface to ensure
 * that we manipulate wrapped nodes and not the ones from the underlying implementation.
 *
 * @author toto
 */
public class JCRSessionWrapper implements Session {
  private static Logger logger = org.slf4j.LoggerFactory.getLogger(JCRSessionWrapper.class);
  public static final String DEREF_SEPARATOR = "@/";

  private JCRSessionFactory sessionFactory;
  private JahiaUser user;
  private Credentials credentials;
  private JCRWorkspaceWrapper workspace;
  private boolean isLive = true;
  private Locale locale;
  private List<String> tokens = new ArrayList<String>();

  private Map<JCRStoreProvider, Session> sessions = new HashMap<JCRStoreProvider, Session>();

  private Map<String, JCRNodeWrapper> sessionCacheByPath = new HashMap<String, JCRNodeWrapper>();
  private Map<String, JCRNodeWrapper> sessionCacheByIdentifier =
      new HashMap<String, JCRNodeWrapper>();
  private Map<String, JCRNodeWrapper> newNodes = new HashMap<String, JCRNodeWrapper>();

  private Map<String, String> nsToPrefix = new HashMap<String, String>();
  private Map<String, String> prefixToNs = new HashMap<String, String>();

  private Map<String, String> uuidMapping = new HashMap<String, String>();
  private Map<String, String> pathMapping = new LinkedHashMap<String, String>();

  private boolean isSystem;
  private Date versionDate;

  private Locale fallbackLocale;
  private String versionLabel;

  private static AtomicLong activeSessions = new AtomicLong(0L);

  public JCRSessionWrapper(
      JahiaUser user,
      Credentials credentials,
      boolean isSystem,
      String workspace,
      Locale locale,
      JCRSessionFactory sessionFactory,
      Locale fallbackLocale) {
    this.user = user;
    this.credentials = credentials;
    this.isSystem = isSystem;
    this.versionDate = null;
    this.versionLabel = null;
    if (workspace == null) {
      this.workspace = new JCRWorkspaceWrapper("default", this, sessionFactory);
    } else {
      this.workspace = new JCRWorkspaceWrapper(workspace, this, sessionFactory);
    }
    this.locale = locale;
    this.fallbackLocale = fallbackLocale;
    this.sessionFactory = sessionFactory;
    activeSessions.incrementAndGet();
  }

  public JCRNodeWrapper getRootNode() throws RepositoryException {
    JCRStoreProvider provider = sessionFactory.getProvider("/");
    return provider.getNodeWrapper(getProviderSession(provider).getRootNode(), "/", null, this);
  }

  public Repository getRepository() {
    return sessionFactory;
  }

  public String getUserID() {
    return ((SimpleCredentials) credentials).getUserID();
  }

  public boolean isSystem() {
    return isSystem;
  }

  public Object getAttribute(String s) {
    return null; // To change body of implemented methods use File | Settings | File Templates.
  }

  public String[] getAttributeNames() {
    return new String
        [0]; // To change body of implemented methods use File | Settings | File Templates.
  }

  public JCRWorkspaceWrapper getWorkspace() {
    return workspace;
  }

  public Locale getLocale() {
    return locale;
  }

  //    public void setInterceptorsEnabled(boolean interceptorsEnabled) {
  //        this.interceptorsEnabled = interceptorsEnabled;
  //    }

  public Session impersonate(Credentials credentials) throws LoginException, RepositoryException {
    throw new UnsupportedRepositoryOperationException();
  }

  public JCRNodeWrapper getNodeByUUID(String uuid)
      throws ItemNotFoundException, RepositoryException {
    return getNodeByUUID(uuid, true);
  }

  public JCRNodeWrapper getNodeByUUID(final String uuid, final boolean checkVersion)
      throws ItemNotFoundException, RepositoryException {

    if (sessionCacheByIdentifier.containsKey(uuid)) {
      return sessionCacheByIdentifier.get(uuid);
    }
    RepositoryException originalEx = null;
    for (JCRStoreProvider provider : sessionFactory.getProviderList()) {
      if (!provider.isInitialized()) {
        logger.debug(
            "Provider "
                + provider.getKey()
                + " / "
                + provider.getClass().getName()
                + " is not yet initialized, skipping...");
        continue;
      }
      if (provider instanceof JackrabbitStoreProvider && JCRContentUtils.isNotJcrUuid(uuid)) {
        // not a valid UUID, probably a VFS node
        continue;
      }
      try {
        Session session = getProviderSession(provider);
        if (session instanceof JahiaSessionImpl
            && getUser() != null
            && sessionFactory.getCurrentAliasedUser() != null
            && sessionFactory.getCurrentAliasedUser().equals(getUser())) {
          ((JahiaSessionImpl) session).toggleThisSessionAsAliased();
        }
        Node n = session.getNodeByIdentifier(uuid);
        JCRNodeWrapper wrapper = provider.getNodeWrapper(n, this);
        if (getUser() != null
            && sessionFactory.getCurrentAliasedUser() != null
            && !sessionFactory.getCurrentAliasedUser().equals(getUser())) {
          JCRTemplate.getInstance()
              .doExecuteWithUserSession(
                  sessionFactory.getCurrentAliasedUser().getUsername(),
                  session.getWorkspace().getName(),
                  getLocale(),
                  new JCRCallback<Object>() {
                    public Object doInJCR(JCRSessionWrapper session) throws RepositoryException {
                      return session.getNodeByUUID(uuid, checkVersion);
                    }
                  });
        }
        if (checkVersion && (versionDate != null || versionLabel != null)) {
          wrapper = getFrozenVersionAsRegular(n, provider);
        }
        sessionCacheByIdentifier.put(uuid, wrapper);
        sessionCacheByPath.put(wrapper.getPath(), wrapper);

        return wrapper;
      } catch (ItemNotFoundException ee) {
        // All good
        if (originalEx == null) {
          originalEx = ee;
        }
      } catch (UnsupportedRepositoryOperationException uso) {
        logger.debug(
            "getNodeByUUID unsupported by : "
                + provider.getKey()
                + " / "
                + provider.getClass().getName());
        if (originalEx == null) {
          originalEx = uso;
        }
      } catch (RepositoryException ex) {
        if (originalEx == null) {
          originalEx = ex;
        }
        logger.warn(
            "repository exception : "
                + provider.getKey()
                + " / "
                + provider.getClass().getName()
                + " : "
                + ex.getMessage());
      }
    }
    if (originalEx != null) {
      if (originalEx instanceof ItemNotFoundException) {
        throw originalEx;
      } else {
        throw new ItemNotFoundException(uuid, originalEx);
      }
    }

    throw new ItemNotFoundException(uuid);
  }

  public JCRNodeWrapper getNodeByUUID(String providerKey, String uuid)
      throws ItemNotFoundException, RepositoryException {
    JCRStoreProvider provider = sessionFactory.getProviders().get(providerKey);
    if (provider == null) {
      throw new ItemNotFoundException(uuid);
    }
    Session session = getProviderSession(provider);
    Node n = session.getNodeByIdentifier(uuid);
    return provider.getNodeWrapper(n, this);
  }

  public JCRItemWrapper getItem(String path) throws PathNotFoundException, RepositoryException {
    return getItem(path, true);
  }

  public JCRItemWrapper getItem(String path, final boolean checkVersion)
      throws PathNotFoundException, RepositoryException {
    if (sessionCacheByPath.containsKey(path)) {
      return sessionCacheByPath.get(path);
    }
    if (path.contains(DEREF_SEPARATOR)) {
      JCRNodeWrapper parent =
          (JCRNodeWrapper)
              getItem(StringUtils.substringBeforeLast(path, DEREF_SEPARATOR), checkVersion);
      return dereference(parent, StringUtils.substringAfterLast(path, DEREF_SEPARATOR));
    }
    Map<String, JCRStoreProvider> dynamicMountPoints = sessionFactory.getDynamicMountPoints();
    for (Map.Entry<String, JCRStoreProvider> mp : dynamicMountPoints.entrySet()) {
      if (path.startsWith(mp.getKey() + "/")) {
        String localPath = path.substring(mp.getKey().length());
        JCRStoreProvider provider = mp.getValue();
        Item item = getProviderSession(provider).getItem(provider.getRelativeRoot() + localPath);
        if (item.isNode()) {
          return provider.getNodeWrapper((Node) item, localPath, null, this);
        } else {
          return provider.getPropertyWrapper((Property) item, this);
        }
      }
    }
    Map<String, JCRStoreProvider> mountPoints = sessionFactory.getMountPoints();
    for (Map.Entry<String, JCRStoreProvider> mp : mountPoints.entrySet()) {
      String key = mp.getKey();
      if (key.equals("/") || path.equals(key) || path.startsWith(key + "/")) {
        String localPath = path;
        if (!key.equals("/")) {
          localPath = localPath.substring(key.length());
        }
        JCRStoreProvider provider = mp.getValue();
        if (localPath.equals("")) {
          localPath = "/";
        }
        //                Item item = getProviderSession(provider).getItem(localPath);
        Session session = getProviderSession(provider);
        if (session instanceof JahiaSessionImpl
            && getUser() != null
            && sessionFactory.getCurrentAliasedUser() != null
            && sessionFactory.getCurrentAliasedUser().equals(getUser())) {
          ((JahiaSessionImpl) session).toggleThisSessionAsAliased();
        }
        Item item = session.getItem(provider.getRelativeRoot() + localPath);
        if (item.isNode()) {
          final Node node = (Node) item;
          JCRNodeWrapper wrapper = provider.getNodeWrapper(node, localPath, null, this);
          if (getUser() != null
              && sessionFactory.getCurrentAliasedUser() != null
              && !sessionFactory.getCurrentAliasedUser().equals(getUser())) {
            final JCRNodeWrapper finalWrapper = wrapper;
            JCRTemplate.getInstance()
                .doExecuteWithUserSession(
                    sessionFactory.getCurrentAliasedUser().getUsername(),
                    getWorkspace().getName(),
                    getLocale(),
                    new JCRCallback<Object>() {
                      public Object doInJCR(JCRSessionWrapper session) throws RepositoryException {
                        return session.getNodeByUUID(finalWrapper.getIdentifier(), checkVersion);
                      }
                    });
          }
          if (checkVersion
              && (versionDate != null || versionLabel != null)
              && node.isNodeType("mix:versionable")) {
            wrapper = getFrozenVersionAsRegular(node, provider);
          }
          sessionCacheByPath.put(path, wrapper);
          sessionCacheByIdentifier.put(wrapper.getIdentifier(), wrapper);

          return wrapper;
        } else {
          return provider.getPropertyWrapper((Property) item, this);
        }
      }
    }
    throw new PathNotFoundException(path);
  }

  private JCRNodeWrapper dereference(JCRNodeWrapper parent, String refPath)
      throws RepositoryException {
    JCRStoreProvider provider = parent.getProvider();
    JCRNodeWrapper wrapper;
    Node referencedNode = parent.getRealNode().getProperty("j:node").getNode();
    String fullPath = parent.getPath() + DEREF_SEPARATOR + refPath;
    if (parent.getPath().startsWith(referencedNode.getPath())) {
      throw new PathNotFoundException(fullPath);
    }
    String refRootName = StringUtils.substringBefore(refPath, "/");
    if (!referencedNode.getName().equals(refRootName)) {
      throw new PathNotFoundException(fullPath);
    }
    refPath = StringUtils.substringAfter(refPath, "/");
    if (refPath.equals("")) {
      wrapper = provider.getNodeWrapper(referencedNode, fullPath, parent, this);
    } else {
      Node node = referencedNode.getNode(refPath);
      wrapper = provider.getNodeWrapper(node, fullPath, null, this);
    }
    sessionCacheByPath.put(fullPath, wrapper);
    return wrapper;
  }

  public JCRNodeWrapper getNode(String path) throws PathNotFoundException, RepositoryException {
    return getNode(path, true);
  }

  public JCRNodeWrapper getNode(String path, boolean checkVersion)
      throws PathNotFoundException, RepositoryException {
    JCRItemWrapper item = getItem(path, checkVersion);
    if (item.isNode()) {
      return (JCRNodeWrapper) item;
    } else {
      throw new PathNotFoundException();
    }
  }

  public boolean itemExists(String path) throws RepositoryException {
    try {
      getItem(path);
      return true;
    } catch (RepositoryException e) {
      return false;
    }
  }

  public void move(String source, String dest)
      throws ItemExistsException, PathNotFoundException, VersionException,
          ConstraintViolationException, LockException, RepositoryException {
    getWorkspace().move(source, dest, true);
    if (sessionCacheByPath.containsKey(source)) {
      JCRNodeWrapper n = sessionCacheByPath.get(source);
      if (n instanceof JCRNodeDecorator) {
        n = ((JCRNodeDecorator) n).getDecoratedNode();
      }
      ((JCRNodeWrapperImpl) n).localPath = dest;
      ((JCRNodeWrapperImpl) n).localPathInProvider = dest;
    }
    flushCaches();
  }

  public void save()
      throws AccessDeniedException, ItemExistsException, ConstraintViolationException,
          InvalidItemStateException, VersionException, LockException, NoSuchNodeTypeException,
          RepositoryException {
    save(JCRObservationManager.SESSION_SAVE);
  }

  void registerNewNode(JCRNodeWrapper node) {
    newNodes.put(node.getPath(), node);
  }

  void unregisterNewNode(JCRNodeWrapper node) {
    if (!newNodes.isEmpty()) {
      newNodes.remove(node.getPath());
      try {
        if (node.hasNodes()) {
          NodeIterator it = node.getNodes();
          while (it.hasNext()) {
            unregisterNewNode((JCRNodeWrapper) it.next());
          }
        }
      } catch (RepositoryException e) {
        logger.warn("Error unregistering new nodes", e);
      }
    }
  }

  public void save(final int operationType)
      throws AccessDeniedException, ItemExistsException, ConstraintViolationException,
          InvalidItemStateException, VersionException, LockException, NoSuchNodeTypeException,
          RepositoryException {
    if (!isSystem() && getLocale() != null) {
      for (JCRNodeWrapper node : newNodes.values()) {
        for (String s : node.getNodeTypes()) {
          ExtendedPropertyDefinition[] propDefs =
              NodeTypeRegistry.getInstance().getNodeType(s).getPropertyDefinitions();
          for (ExtendedPropertyDefinition propDef : propDefs) {
            if (propDef.isMandatory()
                && propDef.getRequiredType() != PropertyType.WEAKREFERENCE
                && propDef.getRequiredType() != PropertyType.REFERENCE
                && !propDef.isProtected()
                && !node.hasProperty(propDef.getName())) {
              throw new ConstraintViolationException("Mandatory field : " + propDef.getName());
            }
          }
        }
      }
    }
    newNodes.clear();

    JCRObservationManager.doWorkspaceWriteCall(
        this,
        operationType,
        new JCRCallback<Object>() {
          public Object doInJCR(JCRSessionWrapper thisSession) throws RepositoryException {
            for (Session session : sessions.values()) {
              session.save();
            }
            return null;
          }
        });
  }

  public void refresh(boolean b) throws RepositoryException {
    for (Session session : sessions.values()) {
      session.refresh(b);
    }
  }

  public boolean hasPendingChanges() throws RepositoryException {
    for (Session session : sessions.values()) {
      if (session.hasPendingChanges()) {
        return true;
      }
    }
    return false;
  }

  public ValueFactory getValueFactory() {
    return ValueFactoryImpl.getInstance();
  }

  /**
   * Normally determines whether this <code>Session</code> has permission to perform the specified
   * actions at the specified <code>absPath</code>. This method is not supported.
   *
   * @param absPath an absolute path.
   * @param actions a comma separated list of action strings.
   * @throws UnsupportedRepositoryOperationException as long as Jahia doesn't support it
   */
  public void checkPermission(String absPath, String actions)
      throws AccessControlException, RepositoryException {
    throw new UnsupportedRepositoryOperationException();
  }

  public ContentHandler getImportContentHandler(String s, int i)
      throws PathNotFoundException, ConstraintViolationException, VersionException, LockException,
          RepositoryException {
    throw new UnsupportedRepositoryOperationException();
  }

  public void importXML(String path, InputStream inputStream, int uuidBehavior)
      throws IOException, PathNotFoundException, ItemExistsException, ConstraintViolationException,
          VersionException, InvalidSerializedDataException, LockException, RepositoryException {
    importXML(path, inputStream, uuidBehavior, DocumentViewImportHandler.ROOT_BEHAVIOUR_REPLACE);
  }

  public void importXML(String path, InputStream inputStream, int uuidBehavior, int rootBehavior)
      throws IOException, InvalidSerializedDataException, RepositoryException {
    importXML(path, inputStream, uuidBehavior, rootBehavior, null);
  }

  public void importXML(
      String path,
      InputStream inputStream,
      int uuidBehavior,
      int rootBehavior,
      Map<String, String> replacements)
      throws IOException, InvalidSerializedDataException, RepositoryException {
    JCRNodeWrapper node = getNode(path);
    try {
      if (!node.isCheckedOut()) {
        checkout(node);
      }
    } catch (UnsupportedRepositoryOperationException ex) {
      // versioning not supported
    }

    DocumentViewImportHandler documentViewImportHandler = new DocumentViewImportHandler(this, path);
    documentViewImportHandler.setRootBehavior(rootBehavior);
    documentViewImportHandler.setUuidBehavior(uuidBehavior);
    documentViewImportHandler.setReplacements(replacements);
    try {
      SAXParserFactory factory;

      factory = new SAXParserFactoryImpl();

      factory.setNamespaceAware(true);
      factory.setValidating(false);
      factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

      SAXParser parser = factory.newSAXParser();

      parser.parse(inputStream, documentViewImportHandler);
    } catch (SAXParseException e) {
      logger.error("Cannot import - File is not a valid XML", e);
    } catch (Exception e) {
      logger.error("Cannot import", e);
    }
  }

  /**
   * Applies the namespace prefix to the appropriate sessions, including the underlying provider
   * sessions.
   *
   * @param prefix
   * @param uri
   * @throws NamespaceException
   * @throws RepositoryException
   */
  public void setNamespacePrefix(String prefix, String uri)
      throws NamespaceException, RepositoryException {
    nsToPrefix.put(uri, prefix);
    prefixToNs.put(prefix, uri);
    for (Session s : sessions.values()) {
      s.setNamespacePrefix(prefix, uri);
      try {
        NamespaceRegistry nsReg = s.getWorkspace().getNamespaceRegistry();
        if (nsReg != null) {
          nsReg.registerNamespace(prefix, uri);
        }
      } catch (RepositoryException e) {
        if (logger.isDebugEnabled()) {
          logger.debug(
              "Prefix/uri could not be registered in workspace's registry- " + prefix + "/" + uri,
              e);
        }
      }
    }
  }

  public String[] getNamespacePrefixes() throws RepositoryException {
    Set<String> wsPrefixes =
        new HashSet<String>(Arrays.asList(getWorkspace().getNamespaceRegistry().getPrefixes()));
    wsPrefixes.addAll(prefixToNs.keySet());
    return wsPrefixes.toArray(new String[wsPrefixes.size()]);
  }

  public String getNamespaceURI(String prefix) throws NamespaceException, RepositoryException {
    if (prefixToNs.containsKey(prefix)) {
      return prefixToNs.get(prefix);
    }
    return getWorkspace().getNamespaceRegistry().getURI(prefix);
  }

  public String getNamespacePrefix(String uri) throws NamespaceException, RepositoryException {
    if (nsToPrefix.containsKey(uri)) {
      return nsToPrefix.get(uri);
    }
    return getWorkspace().getNamespaceRegistry().getPrefix(uri);
  }

  public void logout() {
    for (Session session : sessions.values()) {
      session.logout();
    }
    if (credentials instanceof SimpleCredentials) {
      SimpleCredentials simpleCredentials = (SimpleCredentials) credentials;
      JahiaLoginModule.removeToken(
          simpleCredentials.getUserID(), new String(simpleCredentials.getPassword()));
    }
    isLive = false;
    activeSessions.decrementAndGet();
  }

  public boolean isLive() {
    return isLive;
  }

  /**
   * Adds the specified lock token to the wrapped sessions. Holding a lock token makes the <code>
   * Session</code> the owner of the lock specified by that particular lock token.
   *
   * @param token a lock token (a string).
   * @deprecated As of JCR 2.0, {@link LockManager#addLockToken(String)} should be used instead.
   */
  public void addLockToken(String token) {
    tokens.add(token);
    for (Session session : sessions.values()) {
      session.addLockToken(token);
    }
  }

  public String[] getLockTokens() {
    List<String> allTokens = new ArrayList<String>(tokens);
    for (Session session : sessions.values()) {
      String[] tokens = session.getLockTokens();
      for (String token : tokens) {
        if (!allTokens.contains(token)) {
          allTokens.add(token);
        }
      }
    }
    return allTokens.toArray(new String[allTokens.size()]);
  }

  public void removeLockToken(String token) {
    tokens.remove(token);
    for (Session session : sessions.values()) {
      session.removeLockToken(token);
    }
  }

  /**
   * Get sessions from all providers used in this wrapper.
   *
   * @return a <code>Collection</code> of <code>JCRSessionWrapper</code> objects
   */
  public Collection<Session> getAllSessions() {
    return sessions.values();
  }

  public Session getProviderSession(JCRStoreProvider provider) throws RepositoryException {
    if (sessions.get(provider) == null) {
      Session s = null;

      if (credentials instanceof SimpleCredentials) {
        SimpleCredentials simpleCredentials = (SimpleCredentials) credentials;
        JahiaLoginModule.Token t =
            JahiaLoginModule.getToken(
                simpleCredentials.getUserID(), new String(simpleCredentials.getPassword()));

        s = provider.getSession(credentials, workspace.getName());

        credentials =
            JahiaLoginModule.getCredentials(
                simpleCredentials.getUserID(), t != null ? t.deniedPath : null);
      } else {
        s = provider.getSession(credentials, workspace.getName());
      }

      sessions.put(provider, s);
      for (String token : tokens) {
        s.addLockToken(token);
      }

      NamespaceRegistry namespaceRegistryWrapper = getWorkspace().getNamespaceRegistry();
      NamespaceRegistry providerNamespaceRegistry = s.getWorkspace().getNamespaceRegistry();

      if (providerNamespaceRegistry != null) {
        for (String prefix : namespaceRegistryWrapper.getPrefixes()) {
          try {
            providerNamespaceRegistry.getURI(prefix);
          } catch (NamespaceException ne) {
            providerNamespaceRegistry.registerNamespace(
                prefix, namespaceRegistryWrapper.getURI(prefix));
          }
        }
      }

      for (String prefix : prefixToNs.keySet()) {
        s.setNamespacePrefix(prefix, prefixToNs.get(prefix));
      }
    }
    return sessions.get(provider);
  }

  public JahiaUser getUser() {
    return user;
  }

  public JahiaUser getAliasedUser() {
    return sessionFactory.getCurrentAliasedUser();
  }

  public Calendar getPreviewDate() {
    return sessionFactory.getCurrentPreviewDate();
  }
  /**
   * Generates a document view export using a {@link
   * org.apache.jackrabbit.commons.xml.DocumentViewExporter} instance.
   *
   * @param path of the node to be exported
   * @param handler handler for the SAX events of the export
   * @param skipBinary whether binary values should be skipped
   * @param noRecurse whether to export just the identified node
   * @throws PathNotFoundException if a node at the given path does not exist
   * @throws SAXException if the SAX event handler failed
   * @throws RepositoryException if another error occurs
   */
  public void exportDocumentView(
      String path, ContentHandler handler, boolean skipBinary, boolean noRecurse)
      throws PathNotFoundException, SAXException, RepositoryException {
    DocumentViewExporter exporter = new DocumentViewExporter(this, handler, skipBinary, noRecurse);
    Item item = getItem(path);
    if (item.isNode()) {
      exporter.export((JCRNodeWrapper) item);
    } else {
      throw new PathNotFoundException("XML export is not defined for properties: " + path);
    }
  }

  /**
   * Generates a system view export using a {@link
   * org.apache.jackrabbit.commons.xml.SystemViewExporter} instance.
   *
   * @param path of the node to be exported
   * @param handler handler for the SAX events of the export
   * @param skipBinary whether binary values should be skipped
   * @param noRecurse whether to export just the identified node
   * @throws PathNotFoundException if a node at the given path does not exist
   * @throws SAXException if the SAX event handler failed
   * @throws RepositoryException if another error occurs
   */
  public void exportSystemView(
      String path, ContentHandler handler, boolean skipBinary, boolean noRecurse)
      throws PathNotFoundException, SAXException, RepositoryException {

    // todo implement our own system view .. ?
    SystemViewExporter exporter = new SystemViewExporter(this, handler, !noRecurse, !skipBinary);
    Item item = getItem(path);
    if (item.isNode()) {
      exporter.export((JCRNodeWrapper) item);
    } else {
      throw new PathNotFoundException("XML export is not defined for properties: " + path);
    }
  }

  /**
   * Calls {@link Session#exportDocumentView(String, ContentHandler, boolean, boolean)} with the
   * given arguments and a {@link ContentHandler} that serializes SAX events to the given output
   * stream.
   *
   * @param absPath passed through
   * @param out output stream to which the SAX events are serialized
   * @param skipBinary passed through
   * @param noRecurse passed through
   * @throws IOException if the SAX serialization failed
   * @throws RepositoryException if another error occurs
   */
  public void exportDocumentView(
      String absPath, OutputStream out, boolean skipBinary, boolean noRecurse)
      throws IOException, RepositoryException {
    try {
      ContentHandler handler = getExportContentHandler(out);
      exportDocumentView(absPath, handler, skipBinary, noRecurse);
    } catch (SAXException e) {
      Exception exception = e.getException();
      if (exception instanceof RepositoryException) {
        throw (RepositoryException) exception;
      } else if (exception instanceof IOException) {
        throw (IOException) exception;
      } else {
        throw new RepositoryException("Error serializing document view XML", e);
      }
    }
  }

  /**
   * Calls {@link Session#exportSystemView(String, ContentHandler, boolean, boolean)} with the given
   * arguments and a {@link ContentHandler} that serializes SAX events to the given output stream.
   *
   * @param absPath passed through
   * @param out output stream to which the SAX events are serialized
   * @param skipBinary passed through
   * @param noRecurse passed through
   * @throws IOException if the SAX serialization failed
   * @throws RepositoryException if another error occurs
   */
  public void exportSystemView(
      String absPath, OutputStream out, boolean skipBinary, boolean noRecurse)
      throws IOException, RepositoryException {
    try {
      ContentHandler handler = getExportContentHandler(out);
      exportSystemView(absPath, handler, skipBinary, noRecurse);
    } catch (SAXException e) {
      Exception exception = e.getException();
      if (exception instanceof RepositoryException) {
        throw (RepositoryException) exception;
      } else if (exception instanceof IOException) {
        throw (IOException) exception;
      } else {
        throw new RepositoryException("Error serializing system view XML", e);
      }
    }
  }

  /**
   * Creates a {@link ContentHandler} instance that serializes the received SAX events to the given
   * output stream.
   *
   * @param stream output stream to which the SAX events are serialized
   * @return SAX content handler
   * @throws RepositoryException if an error occurs
   */
  private ContentHandler getExportContentHandler(OutputStream stream) throws RepositoryException {
    try {
      SAXTransformerFactory stf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
      TransformerHandler handler = stf.newTransformerHandler();

      Transformer transformer = handler.getTransformer();
      transformer.setOutputProperty(OutputKeys.METHOD, "xml");
      transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
      transformer.setOutputProperty(OutputKeys.INDENT, "no");

      handler.setResult(new StreamResult(stream));
      return handler;
    } catch (TransformerFactoryConfigurationError e) {
      throw new RepositoryException("SAX transformer implementation not available", e);
    } catch (TransformerException e) {
      throw new RepositoryException("Error creating an XML export content handler", e);
    }
  }

  public JCRNodeWrapper getNodeByIdentifier(String id)
      throws ItemNotFoundException, RepositoryException {
    return getNodeByUUID(id);
  }

  public Property getProperty(String absPath) throws PathNotFoundException, RepositoryException {
    return (Property) getItem(absPath);
  }

  public boolean nodeExists(String absPath) throws RepositoryException {
    return itemExists(absPath);
  }

  public boolean propertyExists(String absPath) throws RepositoryException {
    return itemExists(absPath);
  }

  public void removeItem(String absPath)
      throws VersionException, LockException, ConstraintViolationException, AccessDeniedException,
          RepositoryException {
    JCRItemWrapper item = getItem(absPath);
    boolean flushNeeded = false;
    if (item.isNode()) {
      JCRNodeWrapper node = (JCRNodeWrapper) item;
      unregisterNewNode(node);
      if (node.hasNodes()) {
        flushNeeded = true;
      }
    }
    item.remove();
    if (flushNeeded) {
      flushCaches();
    } else {
      removeFromCache(item);
    }
  }

  void removeFromCache(JCRItemWrapper item) throws RepositoryException {
    sessionCacheByPath.remove(item.getPath());
    if (item instanceof JCRNodeWrapper) {
      sessionCacheByIdentifier.remove(((JCRNodeWrapper) item).getIdentifier());
    }
  }

  public boolean hasPermission(String absPath, String actions) throws RepositoryException {
    throw new UnsupportedRepositoryOperationException();
  }

  public boolean hasCapability(String s, Object o, Object[] objects) throws RepositoryException {
    throw new UnsupportedRepositoryOperationException();
  }

  /**
   * Returns the access control manager for this <code>Session</code>.
   *
   * <p>Jahia throws an <code>UnsupportedRepositoryOperationException</code>.
   *
   * @return the access control manager for this <code>Session</code>
   * @throws UnsupportedRepositoryOperationException if access control is not supported.
   * @since JCR 2.0
   */
  public AccessControlManager getAccessControlManager()
      throws UnsupportedRepositoryOperationException, RepositoryException {
    throw new UnsupportedRepositoryOperationException();
  }

  public RetentionManager getRetentionManager()
      throws UnsupportedRepositoryOperationException, RepositoryException {
    throw new UnsupportedRepositoryOperationException();
  }

  public Map<String, String> getStoredPasswordsProviders() {
    Map<String, String> results = new HashMap<String, String>();
    results.put(null, user.getUsername());
    for (JCRStoreProvider provider : sessionFactory.getProviders().values()) {
      if ("storedPasswords".equals(provider.getAuthenticationType())) {
        results.put(provider.getKey(), user.getProperty("storedUsername_" + provider.getKey()));
      }
    }
    return results;
  }

  public void storePasswordForProvider(String providerKey, String username, String password) {
    if (username == null) {
      user.removeProperty("storedUsername_" + providerKey);
    } else {
      user.setProperty("storedUsername_" + providerKey, username);
    }
    if (password == null) {
      user.removeProperty("storedPassword_" + providerKey);
    } else {
      user.setProperty("storedPassword_" + providerKey, password);
    }
  }

  /**
   * Performs check out of the specified node.
   *
   * @param node the node to perform the check out
   * @see VersionManager#checkout(String) for details
   */
  public void checkout(Node node)
      throws UnsupportedRepositoryOperationException, LockException, RepositoryException {
    while (!node.isCheckedOut()) {
      if (!node.isNodeType("mix:versionable") && !node.isNodeType("mix:simpleVersionable")) {
        node = node.getParent();
      } else {
        String absPath = node.getPath();
        VersionManager versionManager = getWorkspace().getVersionManager();
        if (!versionManager.isCheckedOut(absPath)) {
          versionManager.checkout(absPath);
        }
        return;
      }
    }
  }

  public Map<String, String> getUuidMapping() {
    return uuidMapping;
  }

  public Map<String, String> getPathMapping() {
    return pathMapping;
  }

  public Locale getFallbackLocale() {
    return fallbackLocale;
  }

  public Date getVersionDate() {
    return versionDate;
  }

  public String getVersionLabel() {
    return versionLabel;
  }

  public void setVersionDate(Date versionDate) {
    if (this.versionDate == null) {
      this.versionDate = versionDate;
    } else {
      throw new RuntimeException("Should not change versionDate on a session in same thread");
    }
  }

  public void setVersionLabel(String versionLabel) {
    if (this.versionLabel == null) {
      if (versionLabel != null && !versionLabel.startsWith(getWorkspace().getName())) {
        throw new RuntimeException(
            "Cannot use label " + versionLabel + " in workspace " + getWorkspace().getName());
      }
      this.versionLabel = versionLabel;
    } else {
      throw new RuntimeException("Should not change versionLabel on a session in same thread");
    }
  }

  /** {@inheritDoc} */
  public JCRNodeWrapper getFrozenVersionAsRegular(Node objectNode, JCRStoreProvider provider)
      throws RepositoryException {
    try {
      VersionHistory vh =
          objectNode
              .getSession()
              .getWorkspace()
              .getVersionManager()
              .getVersionHistory(objectNode.getPath());

      Version v = null;
      if (versionLabel != null) {
        v = JCRVersionService.findVersionByLabel(vh, versionLabel);
      }
      if (v == null && versionDate != null) {
        v = JCRVersionService.findClosestVersion(vh, versionDate);
      }

      if (v == null) {
        throw new PathNotFoundException();
      }

      Node frozen = v.getNode(Constants.JCR_FROZENNODE);

      return provider.getNodeWrapper(frozen, this);
    } catch (UnsupportedRepositoryOperationException e) {
      if (getVersionDate() == null && getVersionLabel() == null) {
        logger.error("Error while retrieving frozen version", e);
      }
    }
    return null;
  }

  public static AtomicLong getActiveSessions() {
    return activeSessions;
  }

  protected void flushCaches() {
    sessionCacheByIdentifier.clear();
    sessionCacheByPath.clear();
  }

  protected JCRNodeWrapper getCachedNode(String uuid) {
    return sessionCacheByIdentifier.get(uuid);
  }
}
/**
 * Created by 4535992 on 28/12/2015.
 * href:http://www.journaldev.com/2544/java-csv-parserwriter-example-using-opencsv-apache-commons-csv-and-supercsv.
 */
@SuppressWarnings("unused")
public class OpenCsvUtilities extends FileUtilities {

  private static final org.slf4j.Logger logger =
      org.slf4j.LoggerFactory.getLogger(OpenCsvUtilities.class);

  /**
   * Method to write a CSV Data List of Beans to a String.
   *
   * @param beans the List of Beans to convert.
   * @param separator the char separator.
   * @param <T> the generic variable.
   * @return the String content of the List of Beans.
   */
  public static <T> String writeCSVDataToStringWithBeans(List<T> beans, char separator) {
    try {
      Writer writer = new StringWriter();
      try (CSVWriter csvWriter = new CSVWriter(writer, separator)) {
        List<String[]> data = toStringArray(beans);
        csvWriter.writeAll(data);
      }
      return writer.toString();
    } catch (IOException e) {
      logger.error(
          "Can't write the CSV String from the Bean:"
              + beans.get(0).getClass().getName()
              + " -> "
              + e.getMessage(),
          e);
      return "";
    }
  }

  /**
   * Method to write a CSV Data List of Array of String to a String.
   *
   * @param content the List of Array of String to convert.
   * @param separator the char separator.
   * @return the String content of the List of Beans.
   */
  public static String writeCSVDataToString(List<String[]> content, char separator) {
    try {
      Writer writer = new StringWriter();
      CSVWriter csvWriter;
      if (StringUtilities.NULL_CHAR2 == separator) {
        csvWriter =
            new CSVWriter(writer, CSVWriter.DEFAULT_SEPARATOR, CSVWriter.NO_QUOTE_CHARACTER);
      } else {
        csvWriter = new CSVWriter(writer, separator, CSVWriter.NO_QUOTE_CHARACTER);
      }
      csvWriter.writeAll(content);
      csvWriter.close();
      return writer.toString();
    } catch (IOException e) {
      logger.error("Can't write the CSV String -> " + e.getMessage(), e);
      return "";
    }
  }

  /**
   * Method to write a CSV Data List of Array of String to a String.
   *
   * @param content the List of Array of String to convert.
   * @return the String content of the List of Beans.
   */
  public static String writeCSVDataToString(List<String[]> content) {
    return writeCSVDataToString(content, StringUtilities.NULL_CHAR2);
  }

  /**
   * Method to write a CSV File from List of Array of String.
   *
   * @param content the List of Array of String to convert.
   * @param separator the char separator.
   * @param fileOutputCsv the output File Csv to create.
   * @return the File Csv created.
   */
  public static File writeCSVDataToFile(
      List<String[]> content, char separator, File fileOutputCsv) {
    try {
      Writer writer =
          new FileWriter(fileOutputCsv, true); // the true value make append the result...
      CSVWriter csvWriter;
      if (StringUtilities.NULL_CHAR2 == separator) {
        csvWriter =
            new CSVWriter(writer, CSVWriter.DEFAULT_SEPARATOR, CSVWriter.NO_QUOTE_CHARACTER);
      } else {
        csvWriter = new CSVWriter(writer, separator, CSVWriter.NO_QUOTE_CHARACTER);
      }
      csvWriter.writeAll(content);
      csvWriter.close();
      return fileOutputCsv;
    } catch (IOException e) {
      logger.error("Can't write the CSV to File:" + fileOutputCsv + " -> " + e.getMessage(), e);
      return null;
    }
  }

  /**
   * Method to write a CSV File from List of Array of String.
   *
   * @param content the List of Array of String to convert.
   * @param fileOutputCsv the output File Csv to create.
   * @return the File Csv created.
   */
  public static File writeCSVDataToFile(List<String[]> content, File fileOutputCsv) {
    return writeCSVDataToFile(content, StringUtilities.NULL_CHAR2, fileOutputCsv);
  }

  /**
   * Method to write a CSV List of Array of String to System Console..
   *
   * @param content the List of Array of String to convert.
   * @param separator the char separator.
   */
  public static void writeCSVDataToConsole(List<String[]> content, char separator) {
    try {
      Writer writer = new OutputStreamWriter(System.out, StringUtilities.UTF_8);
      CSVWriter csvWriter;
      if (StringUtilities.NULL_CHAR2 == separator) {
        csvWriter =
            new CSVWriter(writer, CSVWriter.DEFAULT_SEPARATOR, CSVWriter.NO_QUOTE_CHARACTER);
      } else {
        csvWriter = new CSVWriter(writer, separator, CSVWriter.NO_QUOTE_CHARACTER);
      }
      csvWriter.writeAll(content, false);
      csvWriter.close();
    } catch (IOException e) {
      logger.error("Can't write the CSV to Console -> " + e.getMessage(), e);
    }
  }

  /**
   * Method to write a CSV List of Array of String to System Console..
   *
   * @param content the List of Array of String to convert.
   */
  public static void writeCSVDataToConsole(List<String[]> content) {
    writeCSVDataToConsole(content, StringUtilities.NULL_CHAR2);
  }

  /**
   * Method to convert a List of beans to a List of Array of Strings.
   *
   * @param beans the List of Beans.
   * @param <T> generic value.
   * @return the List of Array of String content of the csv.
   */
  public static <T> List<String[]> toStringArray(List<T> beans) {
    return toStringArray(beans, null);
  }

  /**
   * Method to convert a List of beans to a List of Array of Strings.
   *
   * @param beans the List of Beans.
   * @param addNewHeader the new String Array for the Header row.
   * @param <T> generic value.
   * @return the List of Array of String content of the csv.
   */
  @SuppressWarnings("unchecked")
  public static <T> List<String[]> toStringArray(List<T> beans, String[] addNewHeader) {
    List<String[]> records = new ArrayList<>();
    // add header record
    // records.add(new String[]{"ID","Name","Role","Salary"});
    if (addNewHeader != null) records.add(addNewHeader);
    for (T bean : beans) {
      // beans.stream().map((bean) -> {
      List<String> record = new ArrayList<>();
      // invoke getter method and convert to String
      Class<T> clazz = (Class<T>) bean.getClass();
      // T t = ReflectionUtilities.invokeConstructor(clazz);
      List<Method> getter = (List<Method>) ReflectionUtilities.findGetters(clazz, true);

      for (Method method : getter) {
        record.add(String.valueOf(ReflectionUtilities.invokeGetter(bean, method)));
      }
      // getter.stream().forEach((method) ->
      // record.add(String.valueOf(ReflectionUtilities.invokeGetter(bean,method))));
      // return record;
      // }).forEach((record) -> records.add(ListUtilities.toArray(record)));
      records.add(ListUtilities.toArray(record));
    }
    return records;
  }

  /**
   * Method use OpenCsv Library for
   *
   * @param columnMapping Map allow the user to pass the column Names to a Field Names of the Class.
   * @param fileInputCsv the File CSV to parse.
   * @param <T> the generic variable.
   * @return the List of Bean parsed from the CSV file.
   */
  public static <T> List<T> parseCSVFileAsList(
      Map<String, String> columnMapping, File fileInputCsv) {
    try {
      HeaderColumnNameTranslateMappingStrategy<T> beanStrategy =
          new HeaderColumnNameTranslateMappingStrategy<>();
      // beanStrategy.setType(clazz); //deprecated
      /*Map<String, String> columnMapping = new HashMap<>();
      columnMapping.put("ID", "id");
      columnMapping.put("Name", "name");
      columnMapping.put("Role", "role");*/
      beanStrategy.setColumnMapping(columnMapping);
      CsvToBean<T> csvToBean = new CsvToBean<>();
      CSVReader reader = new CSVReader(new FileReader(fileInputCsv));
      return csvToBean.parse(beanStrategy, reader);
    } catch (IOException e) {
      logger.error(
          "Can't parse the CSV file:" + fileInputCsv.getAbsolutePath() + " -> " + e.getMessage(),
          e);
      return new ArrayList<>();
    }
  }

  /**
   * Method use OpenCsv Library for
   *
   * @param clazz the Class of the Bean.
   * @param fileInputCsv the File CSV to parse.
   * @param separator the char separator.
   * @param <T> the generic variable.
   * @return the List of Bean parsed from the CSV file.
   */
  public static <T> List<T> parseCSVFileAsList(Class<T> clazz, File fileInputCsv, char separator) {
    try {
      List<T> beans;
      try ( // create CSVReader object
      CSVReader reader = new CSVReader(new FileReader(fileInputCsv), separator)) {
        beans = new ArrayList<>();
        // read line by line
        String[] record;
        // skip header row
        String[] headers = reader.readNext();
        // read content
        while ((record = reader.readNext()) != null) {
          T t = ReflectionUtilities.invokeConstructor(clazz);
          for (int i = 0; i < record.length; i++) {
            String nameMethod = "set" + org.apache.commons.lang3.StringUtils.capitalize(headers[i]);
            // invoke setter method
            if (ReflectionUtilities.checkMethod(clazz, nameMethod)) {
              ReflectionUtilities.invokeSetter(t, nameMethod, record[i]);
            } else {
              logger.warn(
                  "Not exists the Method with name:"
                      + nameMethod
                      + " on the Bean:"
                      + t.getClass().getName());
            }
          }
          beans.add(t);
        }
      }
      return beans;
    } catch (IOException e) {
      logger.error(
          "Can't parse the CSV file:" + fileInputCsv.getAbsolutePath() + " -> " + e.getMessage(),
          e);
      return new ArrayList<>();
    }
  }

  /**
   * Method use OpenCsv Library for
   *
   * @param fileInputCsv the File CSV to parse.
   * @param separator the char separator.
   * @return the List of Bean parsed from the CSV file.
   */
  public static List<String[]> parseCSVFileAsList(File fileInputCsv, char separator) {
    try {
      List<String[]> records;
      // read all lines at once
      try ( // create CSVReader object
      CSVReader reader = new CSVReader(new FileReader(fileInputCsv), separator)) {
        // read all lines at once
        records = reader.readAll();
        Iterator<String[]> iterator = records.iterator();
        records.clear();
        // skip header row
        iterator.next();
        while (iterator.hasNext()) {
          String[] record = iterator.next();
          records.add(record);
        }
      }
      return records;
    } catch (IOException e) {
      logger.error(
          "Can't parse the CSV file:" + fileInputCsv.getAbsolutePath() + " -> " + e.getMessage(),
          e);
      return new ArrayList<>();
    }
  }

  /**
   * Method to get the content of a comma separated file (.csv,.input,.txt)
   *
   * @param CSV the File comma separated.
   * @param noHeaders if true jump the first line of the content.
   * @return the List of Array of the content of the File comma separated.
   */
  public static List<String[]> parseCSVFileAsList(File CSV, boolean noHeaders) {
    List<String[]> content;
    try {
      CSVReader reader1 = new CSVReader(new FileReader(CSV));
      content = reader1.readAll();
      /* List<String[]> myDatas = reader1.readAll();
      String[] lineI = myDatas.get(i);
      for (String[] line : myDatas) {
          for (String value : line) {
              //do stuff with value
          }
      }*/
      if (noHeaders) content.remove(0);
      if (content.get(0).length <= 1) {
        logger.warn(
            "Attention: You haven't parsed correctly the CSV file with OpenCSV API try with Univocity Method");
      }
      return content;
    } catch (IOException e) {
      logger.error("Can't find the CSV File:" + e.getMessage(), e);
      return null;
    }
  }

  /**
   * Method to get the String array of the columns of a CSV File.
   *
   * @param fileCSV the File CSV.
   * @param hasFirstLine if true the first line of CSV File contains the columns name.
   * @return a String Array of the columns.
   */
  public static String[] getHeaders(File fileCSV, boolean hasFirstLine) {
    String[] columns = new String[0];
    try {
      CSVReader reader = new CSVReader(new FileReader(fileCSV));
      columns = reader.readNext(); // assuming first read
      if (!hasFirstLine) {
        int columnCount = 0;
        if (columns != null) columnCount = columns.length;
        columns = new String[columnCount];
        for (int i = 0; i < columnCount; i++) {
          columns[i] = "Column#" + i;
        }
      }
    } catch (IOException e) {
      logger.error("Can't find the Headers on the CSV File", e);
    }
    return columns;
  }

  /**
   * Method to get the String array of the columns of a CSV File.
   *
   * @param contentWithHeaders the {@link List} of {@link String} array of the content of the csv.
   * @return a String Array of the columns.
   */
  public static String[] getHeaders(List<String[]> contentWithHeaders) {
    String[] columns = new String[0];
    try {
      String[] headers = contentWithHeaders.get(0);
      if (headers.length <= 1) {
        throw new Exception("Can't find the delimiter with openCSV try with Univicity method.");
      }
    } catch (Exception e) {
      logger.error("Can't find the Headers on the content", e);
    }
    return columns;
  }

  /**
   * Method to get the String array of the columns of a CSV File.
   *
   * @param fileCSV the File CSV.
   * @param hasFirstLine if true the first line of CSV File contains the columns name.
   * @return a String Array of the columns.
   */
  public static String[] getHeadersWithUnivocity(File fileCSV, boolean hasFirstLine) {
    return UnivocityUtilities.getHeaders(fileCSV, hasFirstLine);
  }

  // ------------------------------------------------------------------------------

  /**
   * Method use OpenCsv Library for
   *
   * @param fileInputCsv the File CSV to parse.
   * @param noHeaders if true the headers are excluded from the cotent.
   * @return the List of Bean parsed from the CSV file.
   */
  public static List<String[]> parseCSVFileAsListWithUnivocity(
      File fileInputCsv, boolean noHeaders) {
    return UnivocityUtilities.parseCSVFileAsList(fileInputCsv, noHeaders);
  }

  /* public static void main(String[] args) throws IOException {

      List<Employee> emps = parseCSVFileLineByLine();
      System.out.println("**********");
      parseCSVFileAsList();
      System.out.println("**********");
      parseCSVToBeanList();
      System.out.println("**********");
      writeCSVData(emps);
  }*/

  public static String getFieldLatitude(String[] headers) {
    Pattern pattern = Pattern.compile("(L|l)(at)(itude)?", Pattern.CASE_INSENSITIVE);
    return getField(headers, pattern);
  }

  public static String getFieldLongitude(String[] headers) {
    Pattern pattern = Pattern.compile("(L|l)(on|ng)(gitude)?", Pattern.CASE_INSENSITIVE);
    return getField(headers, pattern);
  }

  public static String getField(String[] headers, Pattern pattern) {
    // String[] headers = CSVGetHeaders(headers,true);
    for (String s : headers) {
      if (pattern.matcher(String.valueOf(s)).matches()) {
        return s;
      }
    }
    return "NULL";
  }

  /**
   * Parse CSV file using OpenCSV library and load in given database table. href:
   * http://viralpatel.net/blogs/java-load-csv-file-to-database/. Modified by rammar:
   * https://github.com/BaderLab/pharmacogenomics/blob/master/src/CSVLoader/CSVLoader.java
   *
   * @param connection the {@link Connection} SQL.
   * @param separator the {@link Character} separator.
   * @param csvFile Input CSV InputStream
   * @param tableName Database table name to import data
   * @param truncateBeforeLoad Truncate the table before inserting new records.
   * @throws SQLException if any error is occurred with the SQL Connection.
   * @throws java.io.IOException if any error is occurred with the file.
   */
  public static void loadCSVToSQLTable(
      Connection connection,
      char separator,
      InputStream csvFile,
      String tableName,
      boolean truncateBeforeLoad)
      throws SQLException, IOException {

    String SQL_INSERT = "INSERT INTO ${table}(${keys}) VALUES(${values})";
    String TABLE_REGEX = "\\$\\{table\\}";
    String KEYS_REGEX = "\\$\\{keys\\}";
    String VALUES_REGEX = "\\$\\{values\\}";
    CSVReader csvReader;
    if (null == connection) {
      throw new SQLException("Not a valid connection.");
    }
    try {

      /* Modified by rammar.
       *
       * I was having issues with the CSVReader using the "\" to escape characters.
       * A MySQL CSV file contains quote-enclosed fields and non-quote-enclosed NULL
       * values written as "\N". The CSVReader was removing the "\". To detect "\N"
       * I must remove the escape character, and the only character you can replace
       * it with that you are pretty much guaranteed will not be used to escape
       * text is '\0'.
       * I read this on:
       * http://stackoverflow.com/questions/6008395/opencsv-in-java-ignores-backslash-in-a-field-value
       * based on:
       * http://sourceforge.net/p/opencsv/support-requests/5/
       */
      // PREVIOUS VERSION: csvReader = new CSVReader(new FileReader(csvFile), this.seprator);
      csvReader = new CSVReader(new InputStreamReader(csvFile), separator, '"', '\0');

    } catch (Exception e) {
      throw new IOException("Error occured while executing file. " + e.getMessage());
    }

    String[] headerRow = csvReader.readNext();

    if (null == headerRow) {
      throw new FileNotFoundException(
          "No columns defined in given CSV file." + "Please check the CSV file format.");
    }

    String questionmarks = StringUtils.repeat("?,", headerRow.length);
    questionmarks = (String) questionmarks.subSequence(0, questionmarks.length() - 1);

    /* NOTE from Ron: Header column names must match SQL table fields */
    String query = SQL_INSERT.replaceFirst(TABLE_REGEX, tableName);
    query = query.replaceFirst(KEYS_REGEX, StringUtils.join(headerRow, ","));
    query = query.replaceFirst(VALUES_REGEX, questionmarks);

    // System.out.println("Query: " + query); // Modified by rammar to suppress output

    String[] nextLine;
    Connection con = null;
    PreparedStatement ps = null;
    try {
      con = connection;
      con.setAutoCommit(false);
      ps = con.prepareStatement(query);

      if (truncateBeforeLoad) {
        // delete data from table before loading csv
        con.createStatement().execute("DELETE FROM " + tableName);
      }
      final int batchSize = 1000;
      int count = 0;
      Date date;
      while ((nextLine = csvReader.readNext()) != null) {
        int index = 1;
        for (String string : nextLine) {
          date = DateUtilities.convertToDate(string);
          if (null != date) {
            ps.setDate(index++, new java.sql.Date(date.getTime()));
          } else {

            /* Section modified by rammar to allow NULL values
             * to be input into the DB. */
            if (string.length() > 0 && !string.equals("\\N")) {
              ps.setString(index++, string);
            } else {
              ps.setNull(index++, Types.VARCHAR);
              // ps.setString(index++, null); // can use this syntax also - not sure which is better
            }
          }
        }
        ps.addBatch();
        if (++count % batchSize == 0) {
          ps.executeBatch();
        }
      }
      ps.executeBatch(); // insert remaining records
      logger.info(count + " records loaded into " + tableName + " DB table");
      con.commit();
    } catch (SQLException | IOException e) {
      con.rollback();
      throw new IOException(
          "Error occured while loading data from file to database." + e.getMessage());
    } finally {
      if (null != ps) ps.close();
      con.close();
      csvReader.close();
    }
  }
}
/**
 * Builds Collections of Grib1 Time Partitioned.
 *
 * @author caron
 * @since 1/7/12
 */
public class Grib1TimePartitionBuilder extends Grib1CollectionBuilder {
  public static final String MAGIC_START = "Grib1Partition0Index";

  private static final org.slf4j.Logger logger =
      org.slf4j.LoggerFactory.getLogger(Grib1TimePartitionBuilder.class);
  // static private final int versionTP = 4;
  private static final boolean trace = false;

  // called by tdm
  public static boolean update(TimePartitionCollection tpc, Formatter f) throws IOException {
    Grib1TimePartitionBuilder builder =
        new Grib1TimePartitionBuilder(tpc.getCollectionName(), new File(tpc.getRoot()), tpc);
    if (!builder.needsUpdate()) return false;
    builder.readOrCreateIndex(CollectionManager.Force.always, f);
    builder.gc.close();
    return true;
  }

  // read in the index, create if it doesnt exist or is out of date
  public static Grib1TimePartition factory(
      TimePartitionCollection tpc, CollectionManager.Force force, Formatter f) throws IOException {
    Grib1TimePartitionBuilder builder =
        new Grib1TimePartitionBuilder(tpc.getCollectionName(), new File(tpc.getRoot()), tpc);
    builder.readOrCreateIndex(force, f);
    return builder.tp;
  }

  // read in the index, index raf already open
  public static Grib1TimePartition createFromIndex(
      String name, File directory, RandomAccessFile raf) throws IOException {
    Grib1TimePartitionBuilder builder = new Grib1TimePartitionBuilder(name, directory, null);
    if (builder.readIndex(raf)) {
      return builder.tp;
    }
    throw new IOException("Reading index failed");
  }

  /**
   * write new index if needed
   *
   * @param tpc use this collection
   * @param force force index
   * @param f put status messagess here
   * @return true if index was written
   * @throws IOException on error
   */
  public static boolean writeIndexFile(
      TimePartitionCollection tpc, CollectionManager.Force force, Formatter f) throws IOException {
    Grib1TimePartitionBuilder builder = null;
    try {
      builder =
          new Grib1TimePartitionBuilder(tpc.getCollectionName(), new File(tpc.getRoot()), tpc);
      return builder.readOrCreateIndex(force, f);

    } finally {
      if ((builder != null) && (builder.tp != null)) builder.tp.close();
    }
  }

  //////////////////////////////////////////////////////////////////////////////////

  private final TimePartitionCollection tpc; // defines the partition
  private final Grib1TimePartition tp; // build this object

  private Grib1TimePartitionBuilder(String name, File directory, TimePartitionCollection tpc) {
    FeatureCollectionConfig.GribConfig config =
        (tpc == null)
            ? null
            : (FeatureCollectionConfig.GribConfig)
                tpc.getAuxInfo(FeatureCollectionConfig.AUX_GRIB_CONFIG);
    this.tp = new Grib1TimePartition(name, directory, config);
    this.gc = tp;
    this.tpc = tpc;
  }

  private boolean readOrCreateIndex(CollectionManager.Force ff, Formatter f) throws IOException {
    File idx = gc.getIndexFile();

    // force new index or test for new index needed
    boolean force =
        ((ff == CollectionManager.Force.always)
            || (ff == CollectionManager.Force.test && needsUpdate(idx.lastModified(), f)));

    // otherwise, we're good as long as the index file exists and can be read
    if (force || !idx.exists() || !readIndex(idx.getPath())) {
      logger.info("TimePartitionBuilder createIndex {}", idx.getPath());
      createPartitionedIndex(f); // write out index
      readIndex(idx.getPath()); // read back in index
      return true;
    }
    return false;
  }

  private boolean needsUpdate(long collectionLastModified, Formatter f) throws IOException {
    CollectionManager.ChangeChecker cc = Grib1Index.getChangeChecker();
    for (CollectionManager dcm :
        tpc.makePartitions()) { // LOOK not really right, since we dont know if these files are the
      // same as in the index
      File idxFile = GribCollection.getIndexFile(dcm);
      if (!idxFile.exists()) return true;
      if (collectionLastModified < idxFile.lastModified()) return true;
      for (MFile mfile : dcm.getFiles()) {
        if (cc.hasChangedSince(mfile, idxFile.lastModified())) return true;
      }
    }
    return false;
  }

  ///////////////////////////////////////////////////
  // create the index

  private boolean createPartitionedIndex(Formatter f) throws IOException {
    long start = System.currentTimeMillis();

    // create partitions based on TimePartitionCollections object
    for (CollectionManager dcm : tpc.makePartitions()) {
      tp.addPartition(dcm);
    }

    List<TimePartition.Partition> bad = new ArrayList<TimePartition.Partition>();
    for (TimePartition.Partition dc : tp.getPartitions()) {
      try {
        dc.makeGribCollection(f); // ensure collection has been read successfully
        if (trace) f.format(" Open partition %s%n", dc.getDcm().getCollectionName());
      } catch (Throwable t) {
        logger.error(" Failed to open partition " + dc.getName(), t);
        f.format(" FAIL on partition %s (remove) %n", dc.getDcm().getCollectionName());
        bad.add(dc); // LOOK may be a file leak ?
      }
    }

    // remove ones that failed
    for (TimePartition.Partition p : bad) tp.removePartition(p);

    // choose the "canonical" partition, aka prototype
    int n = tp.getPartitions().size();
    if (n == 0) {
      logger.error(" Nothing in this partition = " + tp.getName());
      f.format(" FAIL Partition empty collection = %s%n", tp.getName());
      return false;
    }
    int idx = tpc.getProtoIndex(n);
    TimePartition.Partition canon = tp.getPartitions().get(idx);
    f.format(" Using canonical partition %s%n", canon.getDcm().getCollectionName());

    // check consistency across vert and ens coords
    if (!checkPartitions(canon, f)) {
      logger.error(
          " Partition check failed, index not written on {} message = {}",
          tp.getName(),
          f.toString());
      f.format(" FAIL Partition check collection = %s%n", tp.getName());
      return false;
    }

    // make the time coordinates, place results into canon
    createPartitionedTimeCoordinates(canon, f);

    // ready to write the index file
    writeIndex(canon, f);

    // close open gc's
    tp.cleanup();

    long took = System.currentTimeMillis() - start;
    f.format(" CreatePartitionedIndex took %d msecs%n", took);
    return true;
  }

  // consistency check on variables : compare each variable to corresponding one in proto
  // also set the groupno and partno for each partition
  private boolean checkPartitions(TimePartition.Partition canon, Formatter f) throws IOException {
    List<TimePartition.Partition> partitions = tp.getPartitions();
    int npart = partitions.size();
    boolean ok = true;

    // for each group in canonical Partition
    GribCollection canonGc = canon.makeGribCollection(f);
    for (GribCollection.GroupHcs firstGroup : canonGc.getGroups()) {
      String gname = firstGroup.getId();
      if (trace) f.format(" Check Group %s%n", gname);

      // hash proto variables for quick lookup
      Map<Integer, GribCollection.VariableIndex> check =
          new HashMap<Integer, GribCollection.VariableIndex>(firstGroup.varIndex.size());
      List<GribCollection.VariableIndex> varIndexP =
          new ArrayList<GribCollection.VariableIndex>(firstGroup.varIndex.size());
      for (GribCollection.VariableIndex vi : firstGroup.varIndex) {
        TimePartition.VariableIndexPartitioned vip = tp.makeVariableIndexPartitioned(vi, npart);
        varIndexP.add(vip);
        check.put(vi.cdmHash, vip); // replace with its evil twin
      }
      firstGroup.varIndex = varIndexP; // replace with its evil twin

      // for each partition
      for (int partno = 0; partno < npart; partno++) {
        TimePartition.Partition tpp = partitions.get(partno);
        if (trace) f.format(" Check Partition %s%n", tpp.getName());

        // get corresponding group
        GribCollection gc = tpp.makeGribCollection(f);
        int groupIdx = gc.findGroupIdxById(firstGroup.getId());
        if (groupIdx < 0) {
          f.format(" Cant find group %s in partition %s%n", gname, tpp.getName());
          ok = false;
          continue;
        }
        GribCollection.GroupHcs group = gc.getGroup(groupIdx);

        // for each variable in partition group
        for (int varIdx = 0; varIdx < group.varIndex.size(); varIdx++) {
          GribCollection.VariableIndex vi2 = group.varIndex.get(varIdx);
          if (trace) f.format(" Check variable %s%n", vi2);
          int flag = 0;

          GribCollection.VariableIndex vi1 = check.get(vi2.cdmHash); // compare with proto variable
          if (vi1 == null) {
            f.format(
                "   WARN Cant find variable %s from %s in proto - ignoring that variable%n",
                vi2, tpp.getName());
            continue; // we can tolerate this
          }

          // compare vert coordinates
          VertCoord vc1 = vi1.getVertCoord();
          VertCoord vc2 = vi2.getVertCoord();
          if ((vc1 == null) != (vc2 == null)) {
            f.format(
                "   ERR Vert coordinates existence on variable %s in %s doesnt match%n",
                vi2, tpp.getName());
            ok = false;
          } else if ((vc1 != null) && !vc1.equalsData(vc2)) {
            f.format(
                "   WARN Vert coordinates values on variable %s in %s dont match%n",
                vi2, tpp.getName());
            f.format("    canon vc = %s%n", vc1);
            f.format("    this vc = %s%n", vc2);
            flag |= TimePartition.VERT_COORDS_DIFFER;
          }

          // compare ens coordinates
          EnsCoord ec1 = vi1.getEnsCoord();
          EnsCoord ec2 = vi2.getEnsCoord();
          if ((ec1 == null) != (ec2 == null)) {
            f.format(
                "   ERR Ensemble coordinates existence on variable %s in %s doesnt match%n",
                vi2, tpp.getName());
            ok = false;
          } else if ((ec1 != null) && !ec1.equalsData(ec2)) {
            f.format(
                "   WARN Ensemble coordinates values on variable %s in %s dont match%n",
                vi2, tpp.getName());
            f.format("    canon ec = %s%n", ec1);
            f.format("    this ec = %s%n", ec2);
            flag |= TimePartition.ENS_COORDS_DIFFER;
          }

          ((TimePartition.VariableIndexPartitioned) vi1)
              .setPartitionIndex(partno, groupIdx, varIdx, flag);
        } // loop over variable
      } // loop over partition
    } // loop over group

    if (ok) f.format("  Partition check: vert, ens coords OK%n");
    return ok;
  }

  private class PartGroup {
    GribCollection.GroupHcs group;
    TimePartition.Partition tpp;

    private PartGroup(GribCollection.GroupHcs group, TimePartition.Partition tpp) {
      this.group = group;
      this.tpp = tpp;
    }
  }

  private boolean createPartitionedTimeCoordinates(TimePartition.Partition canon, Formatter f)
      throws IOException {
    List<TimePartition.Partition> partitions = tp.getPartitions();
    boolean ok = true;

    // for each group in canonical Partition
    for (GribCollection.GroupHcs firstGroup : canon.makeGribCollection(f).getGroups()) {
      String gname = firstGroup.getId();
      if (trace) f.format(" Check Group %s%n", gname);

      // get list of corresponding groups from all the time partition, so we dont have to keep
      // looking it up
      List<PartGroup> pgList = new ArrayList<PartGroup>(partitions.size());
      for (TimePartition.Partition dc : partitions) {
        GribCollection.GroupHcs gg = dc.makeGribCollection(f).findGroupById(gname);
        if (gg == null) logger.error(" Cant find group {} in partition {}", gname, dc.getName());
        else pgList.add(new PartGroup(gg, dc));
      }

      // unique time coordinate unions
      List<TimeCoordUnion> unionList = new ArrayList<TimeCoordUnion>();

      // for each variable in canonical Partition
      for (GribCollection.VariableIndex viCanon : firstGroup.varIndex) {
        if (trace) f.format(" Check variable %s%n", viCanon);
        TimeCoord tcCanon = viCanon.getTimeCoord();

        List<TimeCoord> tcPartitions = new ArrayList<TimeCoord>(pgList.size());

        // for each partition, get the time index
        for (PartGroup pg : pgList) {
          // get corresponding variable
          GribCollection.VariableIndex vi2 = pg.group.findVariableByHash(viCanon.cdmHash);
          if (vi2 == null) { // apparently not in the file
            f.format(
                "   WARN Cant find variable %s in partition %s / %s%n",
                viCanon, pg.tpp.getName(), pg.group.getId());
            tcPartitions.add(null);
          } else {
            if (vi2.timeIdx < 0 || vi2.timeIdx >= pg.group.timeCoords.size()) {
              logger.error(" timeIdx out of range var= {} on partition {}", vi2, pg.tpp.getName());
              tcPartitions.add(null);
            } else {
              TimeCoord tc2 = vi2.getTimeCoord();
              if (tc2.isInterval() != tcCanon.isInterval()) {
                logger.error(
                    " timeIdx wrong interval type var= {} on partition {}", vi2, pg.tpp.getName());
                tcPartitions.add(null);
              } else {
                tcPartitions.add(tc2);
              }
            }
          }
        }

        // union of time coordinates
        TimeCoordUnion union = new TimeCoordUnion(tcCanon.getCode(), tcPartitions, tcCanon);

        // store result in the first group
        viCanon.partTimeCoordIdx =
            TimeCoordUnion.findUnique(unionList, union); // this merges identical TimeCoordUnion
      }

      /* turn TimeIndex into TimeCoord
      for (int tidx = 0; tidx <unionList.size(); tidx++) {
        TimeCoordUnion union = unionList.get(tidx);
        f.format(" %s %d: timeIndexList=", firstGroup.hcs.getName(), tidx);
        for (int idx : union.) f.format("%d,",idx);
        f.format("%n");
      } */

      // store results in first group
      firstGroup.timeCoordPartitions = unionList;
    }

    return ok;
  }

  //////////////////////////////////////////////////////////

  @Override
  public String getMagicStart() {
    return MAGIC_START;
  }

  // writing ncx
  /*
  MAGIC_START
  version
  sizeRecords
  VariableRecords (sizeRecords bytes)
  sizeIndex
  GribCollectionIndex (sizeIndex bytes)
  */
  private boolean writeIndex(TimePartition.Partition canon, Formatter f) throws IOException {
    File file = tp.getIndexFile();
    if (file.exists()) {
      if (!file.delete()) logger.error("Cant delete " + file.getPath());
    }

    RandomAccessFile raf = new RandomAccessFile(file.getPath(), "rw");
    raf.order(RandomAccessFile.BIG_ENDIAN);
    try {
      //// header message
      raf.write(MAGIC_START.getBytes("UTF-8"));
      raf.writeInt(version);
      raf.writeLong(0); // no record section

      GribCollectionProto.GribCollectionIndex.Builder indexBuilder =
          GribCollectionProto.GribCollectionIndex.newBuilder();
      indexBuilder.setName(tp.getName());

      GribCollection canonGc = canon.makeGribCollection(f);
      for (GribCollection.GroupHcs g : canonGc.getGroups())
        indexBuilder.addGroups(writeGroupProto(g));

      indexBuilder.setCenter(canonGc.getCenter());
      indexBuilder.setSubcenter(canonGc.getSubcenter());
      indexBuilder.setMaster(canonGc.getMaster());
      indexBuilder.setLocal(canonGc.getLocal());

      for (TimePartition.Partition p : tp.getPartitions()) {
        indexBuilder.addPartitions(writePartitionProto(p.getName(), (TimePartition.Partition) p));
      }

      GribCollectionProto.GribCollectionIndex index = indexBuilder.build();
      byte[] b = index.toByteArray();
      NcStream.writeVInt(raf, b.length); // message size
      raf.write(b); // message  - all in one gulp
      f.format("GribCollectionTimePartitionedIndex= %d bytes%n", b.length);

    } finally {
      f.format("file size =  %d bytes%n", raf.length());
      raf.close();
    }

    return true;
  }

  private GribCollectionProto.Group writeGroupProto(GribCollection.GroupHcs g) throws IOException {
    GribCollectionProto.Group.Builder b = GribCollectionProto.Group.newBuilder();

    b.setGds(ByteString.copyFrom(g.rawGds));
    b.setGdsHash(g.gdsHash);

    for (GribCollection.VariableIndex vb : g.varIndex)
      b.addVariables(writeVariableProto((TimePartition.VariableIndexPartitioned) vb));

    for (int i = 0; i < g.timeCoordPartitions.size(); i++)
      b.addTimeCoordUnions(writeTimeCoordUnionProto(g.timeCoordPartitions.get(i), i));

    List<VertCoord> vertCoords = g.vertCoords;
    for (int i = 0; i < vertCoords.size(); i++)
      b.addVertCoords(writeCoordProto(vertCoords.get(i), i));

    List<EnsCoord> ensCoords = g.ensCoords;
    for (int i = 0; i < ensCoords.size(); i++) b.addEnsCoords(writeCoordProto(ensCoords.get(i), i));
    return b.build();
  }

  private GribCollectionProto.Variable writeVariableProto(TimePartition.VariableIndexPartitioned v)
      throws IOException {
    GribCollectionProto.Variable.Builder b = GribCollectionProto.Variable.newBuilder();

    b.setDiscipline(v.discipline);
    b.setCategory(v.category);
    b.setParameter(v.parameter);
    b.setLevelType(v.levelType);
    b.setIsLayer(v.isLayer);
    b.setIntervalType(v.intvType);
    if (v.intvName != null) b.setIntvName(v.intvName);
    b.setCdmHash(v.cdmHash);
    b.setRecordsPos(0);
    b.setRecordsLen(0);
    b.setTimeIdx(v.partTimeCoordIdx); // note
    if (v.vertIdx >= 0) b.setVertIdx(v.vertIdx);
    if (v.ensIdx >= 0) b.setEnsIdx(v.ensIdx);
    if (v.ensDerivedType >= 0) b.setEnsDerivedType(v.ensDerivedType); // derived type (table 4.7)
    if (v.probabilityName != null) b.setProbabilityName(v.probabilityName);
    if (v.probType >= 0) b.setProbabilityType(v.probType);
    for (int idx : v.groupno) b.addGroupno(idx);
    for (int idx : v.varno) b.addVarno(idx);
    for (int idx : v.flag) b.addFlag(idx);

    return b.build();
  }

  protected GribCollectionProto.TimeCoordUnion writeTimeCoordUnionProto(
      TimeCoordUnion tcu, int index) throws IOException {
    GribCollectionProto.TimeCoordUnion.Builder b = GribCollectionProto.TimeCoordUnion.newBuilder();
    b.setCode(index);
    b.setUnit(tcu.getUnits());

    if (tcu.isInterval()) {
      for (TimeCoord.Tinv tinv : tcu.getIntervals()) {
        b.addValues((float) tinv.getBounds1());
        b.addBound((float) tinv.getBounds2());
      }
    } else {
      for (int value : tcu.getCoords()) b.addValues((float) value);
    }
    for (TimeCoordUnion.Val val : tcu.getValues()) {
      b.addPartition(val.getPartition());
      b.addIndex(val.getIndex());
    }

    return b.build();
  }

  private GribCollectionProto.Partition writePartitionProto(String name, TimePartition.Partition p)
      throws IOException {
    GribCollectionProto.Partition.Builder b = GribCollectionProto.Partition.newBuilder();

    b.setFilename(p.getIndexFilename());
    b.setName(name);

    return b.build();
  }

  ///////////////////////////////////////////////////////////////////////////
  // reading ncx

  @Override
  protected boolean readPartitions(GribCollectionProto.GribCollectionIndex proto) {
    for (int i = 0; i < proto.getPartitionsCount(); i++) {
      GribCollectionProto.Partition pp = proto.getPartitions(i);
      tp.addPartition(pp.getName(), pp.getFilename());
    }
    return proto.getPartitionsCount() > 0;
  }

  @Override
  protected void readTimePartitions(
      GribCollection.GroupHcs group, GribCollectionProto.Group proto) {
    List<TimeCoord> list = new ArrayList<TimeCoord>(proto.getTimeCoordUnionsCount());
    for (int i = 0; i < proto.getTimeCoordUnionsCount(); i++) {
      GribCollectionProto.TimeCoordUnion tpu = proto.getTimeCoordUnions(i);
      list.add(readTimePartition(tpu, i));
    }
    group.timeCoords = list;
  }

  /*
   protected TimeCoord readTimePartition(GribCollectionProto.TimeCoordUnion pc, int timeIndex) {
    int[] partition = new int[pc.getPartitionCount()];
    int[] index = new int[pc.getPartitionCount()];  // better be the same
    for (int i = 0; i < pc.getPartitionCount(); i++) {
      partition[i] = pc.getPartition(i);
      index[i] = pc.getIndex(i);
    }

    if (pc.getBoundCount() > 0) {  // its an interval
      List<TimeCoord.Tinv> coords = new ArrayList<TimeCoord.Tinv>(pc.getValuesCount());
      for (int i = 0; i < pc.getValuesCount(); i++)
        coords.add(new TimeCoord.Tinv((int) pc.getValues(i), (int) pc.getBound(i)));
      TimeCoordUnion tc =  new TimeCoordUnion(pc.getCode(), pc.getUnit(), coords, partition, index);
      return tc.setIndex( timeIndex);

    } else {
      List<Integer> coords = new ArrayList<Integer>(pc.getValuesCount());
      for (float value : pc.getValuesList())
        coords.add((int) value);
      TimeCoordUnion tc = new TimeCoordUnion(pc.getCode(), pc.getUnit(), coords, partition, index);
      return tc.setIndex( timeIndex);
    }
  }

   */

  protected TimeCoord readTimePartition(GribCollectionProto.TimeCoordUnion pc, int timeIndex) {
    int[] partition = new int[pc.getPartitionCount()];
    int[] index = new int[pc.getPartitionCount()]; // better be the same
    for (int i = 0; i < pc.getPartitionCount(); i++) {
      partition[i] = pc.getPartition(i);
      index[i] = pc.getIndex(i);
    }

    if (pc.getBoundCount() > 0) { // its an interval
      List<TimeCoord.Tinv> coords = new ArrayList<TimeCoord.Tinv>(pc.getValuesCount());
      for (int i = 0; i < pc.getValuesCount(); i++)
        coords.add(new TimeCoord.Tinv((int) pc.getValues(i), (int) pc.getBound(i)));
      TimeCoordUnion tc = new TimeCoordUnion(pc.getCode(), pc.getUnit(), coords, partition, index);
      return tc.setIndex(timeIndex);

    } else {
      List<Integer> coords = new ArrayList<Integer>(pc.getValuesCount());
      for (float value : pc.getValuesList()) coords.add((int) value);
      TimeCoordUnion tc = new TimeCoordUnion(pc.getCode(), pc.getUnit(), coords, partition, index);
      return tc.setIndex(timeIndex);
    }
  }

  @Override
  protected GribCollection.VariableIndex readVariable(
      GribCollectionProto.Variable pv, GribCollection.GroupHcs group) {
    int discipline = pv.getDiscipline();
    int category = pv.getCategory();
    int param = pv.getParameter();
    int levelType = pv.getLevelType();
    int intvType = pv.getIntervalType();
    String intvName = pv.getIntvName();
    boolean isLayer = pv.getIsLayer();
    int ensDerivedType = pv.getEnsDerivedType();
    int probType = pv.getProbabilityType();
    String probabilityName = pv.getProbabilityName();
    int cdmHash = pv.getCdmHash();
    long recordsPos = pv.getRecordsPos();
    int recordsLen = pv.getRecordsLen();
    int timeIdx = pv.getTimeIdx();
    int vertIdx = pv.getVertIdx();
    int ensIdx = pv.getEnsIdx();
    int tableVersion = pv.getTableVersion();
    List<Integer> groupnoList = pv.getGroupnoList();
    List<Integer> varnoList = pv.getVarnoList();
    List<Integer> flagList = pv.getFlagList();

    return tp.makeVariableIndex(
        group,
        tableVersion,
        discipline,
        category,
        param,
        levelType,
        isLayer,
        intvType,
        intvName,
        ensDerivedType,
        probType,
        probabilityName,
        -1,
        cdmHash,
        timeIdx,
        vertIdx,
        ensIdx,
        recordsPos,
        recordsLen,
        groupnoList,
        varnoList,
        flagList);
  }

  public static void main(String[] args) throws IOException {
    Formatter f = new Formatter();
    String indexName = (args.length > 0) ? args[0] : "F:/nomads/NOMADS-cfsrr-timeseries.ncx";
    RandomAccessFile raf = new RandomAccessFile(indexName, "r");
    Grib1TimePartition gtc = Grib1TimePartitionBuilder.createFromIndex("test", null, raf);
    gtc.showIndex(f);
    System.out.printf("%s%n", f);
  }
}
Exemple #29
0
/**
 * Forecast Model Run Collection, manages dynamic collections of GridDatasets. Fmrc represents a
 * virtual dataset. To instantiate, you obtain an FmrcInv "snapshot" from which you can call
 * getDatatset().
 *
 * <p>Assumes that we dont have multiple runtimes in the same file. Can handle different time steps
 * in different files. Can handle different grids in different files. However this creates problems
 * for the "typical dataset". Cannot handle different ensembles in different files. (LOOK fix)
 * Cannot handle different levels in different files. ok
 *
 * @author caron
 * @since Jan 11, 2010
 */
@ThreadSafe
public class Fmrc {
  private static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Fmrc.class);

  /**
   * Factory method
   *
   * @param collection describes the collection. May be one of:
   *     <ol>
   *       <li>collection specification string
   *       <li>catalog:catalogURL
   *       <li>filename.ncml
   *       <li>
   *     </ol>
   *     collectionSpec date extraction is used to get rundates
   * @param errlog place error messages here
   * @return Fmrc or null on error
   * @throws IOException on read error
   * @see
   *     "http://www.unidata.ucar.edu/software/netcdf-java/reference/collections/CollectionSpecification.html"
   */
  public static Fmrc open(String collection, Formatter errlog) throws IOException {
    if (collection.startsWith(MFileCollectionManager.CATALOG)) {
      CatalogCollectionManager manager = new CatalogCollectionManager(collection);
      return new Fmrc(manager, new FeatureCollectionConfig());

    } else if (collection.endsWith(".ncml")) {
      NcmlCollectionReader ncmlCollection = NcmlCollectionReader.open(collection, errlog);
      if (ncmlCollection == null) return null;
      Fmrc fmrc = new Fmrc(ncmlCollection.getCollectionManager(), new FeatureCollectionConfig());
      fmrc.setNcml(ncmlCollection.getNcmlOuter(), ncmlCollection.getNcmlInner());
      return fmrc;
    }

    return new Fmrc(collection, errlog);
  }

  public static Fmrc open(FeatureCollectionConfig config, Formatter errlog) throws IOException {
    if (config.spec.startsWith(MFileCollectionManager.CATALOG)) {
      CatalogCollectionManager manager = new CatalogCollectionManager(config.spec);
      return new Fmrc(manager, config);
    }

    return new Fmrc(config, errlog);
  }

  ////////////////////////////////////////////////////////////////////////
  private final CollectionManager manager;
  private final FeatureCollectionConfig config;

  // should be final
  // private Element ncmlOuter, ncmlInner;

  // the current state - changing must be thread safe
  private final Object lock = new Object();
  private FmrcDataset fmrcDataset;
  private volatile boolean forceProto = false;
  private volatile long lastInvChanged;
  private volatile long lastProtoChanged;

  private Fmrc(String collectionSpec, Formatter errlog) throws IOException {
    this.manager = MFileCollectionManager.open(collectionSpec, null, errlog);
    this.config = new FeatureCollectionConfig();
    this.config.spec = collectionSpec;
  }

  private Fmrc(FeatureCollectionConfig config, Formatter errlog) {
    this.manager = new MFileCollectionManager(config, errlog);
    this.config = config;
  }

  // from AggregationFmrc
  public Fmrc(CollectionManager manager, FeatureCollectionConfig config) {
    this.manager = manager;
    this.config = config;
  }

  public void setNcml(Element outerNcml, Element innerNcml) {
    config.protoConfig.outerNcml = outerNcml;
    config.innerNcml = innerNcml;
  }

  public void close() {
    manager.close();
  }

  // exposed for debugging

  public CollectionManager getManager() {
    return manager;
  }

  public FmrcInv getFmrcInv(Formatter debug) throws IOException {
    return makeFmrcInv(debug);
  }

  /////////////////////////////////////////////////////////////////////////////////////////

  public CalendarDateRange getDateRangeForRun(CalendarDate run) {
    return fmrcDataset.getDateRangeForRun(run);
  }

  public CalendarDateRange getDateRangeForOffset(double offset) {
    return fmrcDataset.getDateRangeForOffset(offset);
  }

  public List<CalendarDate> getRunDates() throws IOException {
    checkNeeded(false); // ??
    return fmrcDataset.getRunDates();
  }

  public List<CalendarDate> getForecastDates() throws IOException {
    checkNeeded(false); // ??
    return fmrcDataset.getForecastDates();
  }

  // for making offset datasets
  public double[] getForecastOffsets() throws IOException {
    checkNeeded(false); // ??
    return fmrcDataset.getForecastOffsets();
  }

  // LOOK : all of these guys could use ehcache
  public GridDataset getDataset2D(NetcdfDataset result) throws IOException {
    checkNeeded(false);
    GridDataset gds = fmrcDataset.getNetcdfDataset2D(result);
    return gds;
  }

  public GridDataset getDatasetBest() throws IOException {
    checkNeeded(false);
    GridDataset gds = fmrcDataset.getBest();
    return gds;
  }

  public GridDataset getDatasetBest(FeatureCollectionConfig.BestDataset bd) throws IOException {
    checkNeeded(false);
    GridDataset gds = fmrcDataset.getBest(bd);
    return gds;
  }

  public GridDataset getRunTimeDataset(CalendarDate run) throws IOException {
    checkNeeded(false);
    GridDataset gds = fmrcDataset.getRunTimeDataset(run);
    return gds;
  }

  public GridDataset getConstantForecastDataset(CalendarDate time) throws IOException {
    checkNeeded(false);
    GridDataset gds = fmrcDataset.getConstantForecastDataset(time);
    return gds;
  }

  public GridDataset getConstantOffsetDataset(double hour) throws IOException {
    checkNeeded(false);
    GridDataset gds = fmrcDataset.getConstantOffsetDataset(hour);
    return gds;
  }

  /////////////////////////////////////////

  public void updateProto() {
    forceProto = true;
  }

  public void update() {
    synchronized (lock) {
      boolean forceProtoLocal = forceProto;

      if (fmrcDataset == null) {
        try {
          fmrcDataset = new FmrcDataset(config);
        } catch (Throwable t) {
          logger.error(config.spec + ": initial fmrcDataset creation failed", t);
          // throw new RuntimeException(t);
        }
      }

      try {
        FmrcInv fmrcInv = makeFmrcInv(null);
        fmrcDataset.setInventory(fmrcInv, forceProtoLocal);
        logger.debug(config.spec + ": make new Dataset, new proto = {}", forceProtoLocal);
        if (forceProtoLocal) forceProto = false;
        this.lastInvChanged = System.currentTimeMillis();
        if (forceProtoLocal) this.lastProtoChanged = this.lastInvChanged;

      } catch (Throwable t) {
        logger.error(config.spec + ": makeFmrcInv failed", t);
        // throw new RuntimeException(t);
      }
    }
  }

  // true if things have changed since given time
  public boolean checkInvState(long lastInvChange) throws IOException {
    return this.lastInvChanged > lastInvChange;
  }
  // true if things have changed since given time
  public boolean checkProtoState(long lastProtoChanged) throws IOException {
    return this.lastProtoChanged > lastProtoChanged;
  }

  public void checkNeeded(boolean force) {
    synchronized (lock) {
      if (fmrcDataset == null) {
        try {
          manager.scan(true);
          update();
          return;
        } catch (Throwable t) {
          logger.error(config.spec + ": rescan failed");
          throw new RuntimeException(t);
        }
      }

      if (!force && !manager.isScanNeeded()) return;
      try {
        if (!manager.scan(true)) return;
        update();
      } catch (Throwable t) {
        logger.error(config.spec + ": rescan failed");
        throw new RuntimeException(t);
      }
    }
  }

  // scan has been done, create FmrcInv
  private FmrcInv makeFmrcInv(Formatter debug) throws IOException {
    try {
      Map<CalendarDate, FmrInv> fmrMap =
          new HashMap<CalendarDate, FmrInv>(); // all files are grouped by run date in an FmrInv
      List<FmrInv> fmrList = new ArrayList<FmrInv>(); // an fmrc is a collection of fmr

      // get the inventory, sorted by path
      for (MFile f : manager.getFiles()) {
        if (logger.isDebugEnabled()) logger.debug("Fmrc: " + config.spec + ": file=" + f.getPath());

        GridDatasetInv inv = null;
        try {
          inv =
              GridDatasetInv.open(
                  manager, f, config.innerNcml); // inventory is discovered for each GDS
        } catch (IOException ioe) {
          logger.warn("Error opening " + f.getPath() + "(skipped)", ioe);
          continue; // skip
        }

        CalendarDate runDate = inv.getRunDate();
        if (debug != null)
          debug.format("  opened %s rundate = %s%n", f.getPath(), inv.getRunDateString());

        // add to fmr for that rundate
        FmrInv fmr = fmrMap.get(runDate);
        if (fmr == null) {
          fmr = new FmrInv(runDate);
          fmrMap.put(runDate, fmr);
          fmrList.add(fmr);
        }
        fmr.addDataset(inv, debug);
      }
      if (debug != null) debug.format("%n");

      // finish the FmrInv
      Collections.sort(fmrList);
      for (FmrInv fmr : fmrList) {
        fmr.finish();
        if (logger.isDebugEnabled())
          logger.debug(
              "Fmrc: spec="
                  + config.spec
                  + ": fmr rundate="
                  + fmr.getRunDate()
                  + " nfiles= "
                  + fmr.getFiles().size());
      }

      return new FmrcInv(
          "fmrc:" + manager.getCollectionName(), fmrList, config.fmrcConfig.regularize);

    } catch (Throwable t) {
      logger.error("makeFmrcInv", t);
      throw new RuntimeException(t);
    }
  }

  public void showDetails(Formatter out) throws IOException {
    checkNeeded(false);
    fmrcDataset.showDetails(out);
  }

  public static void main(String[] args) throws IOException {
    Formatter errlog = new Formatter();

    String spec1 = "/data/testdata/ncml/nc/nam_c20s/NAM_CONUS_20km_surface_#yyyyMMdd_HHmm#.grib1";
    String spec2 = "/data/testdata/grid/grib/grib1/data/agg/.*grb";
    String spec3 = "/data/testdata/ncml/nc/ruc_conus40/RUC_CONUS_40km_#yyyyMMdd_HHmm#.grib1";
    String spec4 = "/data/testdata/cdmUnitTest/rtmodels/.*_nmm\\.GrbF[0-9]{5}$";

    String cat1 =
        "catalog:http://motherlode.ucar.edu:8080/thredds/catalog/fmrc/NCEP/RUC2/CONUS_40km/files/catalog.xml";
    String cat2 =
        "catalog:http://motherlode.ucar.edu:8080/thredds/catalog/fmrc/NCEP/NDFD/CONUS_5km/files/catalog.xml";

    String specH = "C:/data/datasets/nogaps/US058GMET-GR1mdl.*air_temp";
    String specH2 = "C:/data/ft/grid/cg/.*nc$";
    String specH3 = "C:/data/ft/grid/namExtract/#yyyyMMdd_HHmm#.*nc$";
    Fmrc fmrc = new Fmrc(specH3, errlog);
    System.out.printf("errlog = %s%n", errlog);
  }
}