/* (non-Javadoc)
   * @see org.bedework.synch.ConnectorInstance#changed()
   */
  @Override
  public boolean changed() throws SynchException {
    /* This implementation needs to at least check the change token for the
     * collection and match it against the stored token.
     */

    if (info.getChangeToken() == null) {
      fetchedIcal = null; // Force refetch
      return true;
    }

    DavClient cl = getClient();

    try {
      int rc = cl.sendRequest("HEAD", info.getUri(), null);

      if (rc != HttpServletResponse.SC_OK) {
        info.setLastRefreshStatus(String.valueOf(rc));
        if (debug) {
          trace("Unsuccessful response from server was " + rc);
        }
        info.setChangeToken(null); // Force refresh next time
        fetchedIcal = null; // Force refetch
        return true;
      }

      String etag = cl.getResponse().getResponseHeaderValue("Etag");
      if (etag == null) {
        if (debug) {
          trace("Received null etag");
        }

        return false;
      }

      if (debug) {
        trace("Received etag:" + etag + ", ours=" + info.getChangeToken());
      }

      if (info.getChangeToken().equals(etag)) {
        return false;
      }

      fetchedIcal = null; // Force refetch
      return true;
    } catch (SynchException se) {
      throw se;
    } catch (Throwable t) {
      throw new SynchException(t);
    } finally {
      try {
        client.release();
      } catch (Throwable t) {
      }
    }
  }
  private DavClient getClient() throws SynchException {
    if (client != null) {
      return client;
    }

    DavClient cl = null;

    try {
      cl = new DavClient(15 * 1000);

      if (info.getPrincipalHref() != null) {
        cl.setCredentials(info.getPrincipalHref(), cnctr.getSyncher().decrypt(info.getPassword()));
      }

      client = cl;

      return cl;
    } catch (DavioException de) {
      throw new SynchException(de);
    }
  }
  /* Fetch the iCalendar for the subscription. If it fails set the status and
   * return null. Unchanged data will return null with no status change.
   */
  private void getIcal() throws SynchException {
    try {
      if (fetchedIcal != null) {
        return;
      }

      getClient();

      Header[] hdrs = null;

      if ((uidMap != null) && (info.getChangeToken() != null) && (fetchedIcal != null)) {
        hdrs = new Header[] {new BasicHeader("If-None-Match", info.getChangeToken())};
      }

      int rc = client.sendRequest("GET", info.getUri(), hdrs);
      info.setLastRefreshStatus(String.valueOf(rc));

      if (rc == HttpServletResponse.SC_NOT_MODIFIED) {
        // Data unchanged.
        if (debug) {
          trace("data unchanged");
        }
        return;
      }

      if (rc != HttpServletResponse.SC_OK) {
        info.setLastRefreshStatus(String.valueOf(rc));
        if (debug) {
          trace("Unsuccessful response from server was " + rc);
        }
        info.setChangeToken(null); // Force refresh next time
        return;
      }

      CalendarBuilder builder = new CalendarBuilder();

      InputStream is = client.getResponse().getContentStream();

      Calendar ical = builder.build(is);

      /* Convert each entity to XML */

      fetchedIcal = IcalToXcal.fromIcal(ical, null);

      uidMap = new HashMap<String, MapEntry>();

      prodid = null;

      for (VcalendarType vcal : fetchedIcal.getVcalendar()) {
        /* Extract the prodid from the converted calendar - we use it when we
         * generate a new icalendar for each entity.
         */
        if ((prodid == null) && (vcal.getProperties() != null)) {
          for (JAXBElement<? extends BasePropertyType> pel :
              vcal.getProperties().getBasePropertyOrTzid()) {
            if (pel.getValue() instanceof ProdidPropType) {
              prodid = ((ProdidPropType) pel.getValue()).getText();
              break;
            }
          }
        }

        for (JAXBElement<? extends BaseComponentType> comp :
            vcal.getComponents().getBaseComponent()) {
          UidPropType uidProp = (UidPropType) XcalUtil.findProperty(comp.getValue(), XcalTags.uid);

          if (uidProp == null) {
            // Should flag as an error
            continue;
          }

          String uid = uidProp.getText();

          MapEntry me = uidMap.get(uid);

          if (me == null) {
            me = new MapEntry();
            me.uid = uid;
            uidMap.put(uidProp.getText(), me);
          }

          LastModifiedPropType lm =
              (LastModifiedPropType) XcalUtil.findProperty(comp.getValue(), XcalTags.lastModified);

          String lastmod = null;
          if (lm != null) {
            lastmod = lm.getUtcDateTime().toXMLFormat();
          }

          if (Util.cmpObjval(me.lastMod, lastmod) < 0) {
            me.lastMod = lastmod;
          }

          me.comps.add(comp);
        }
      }

      /* Looks like we translated ok. Save any etag and delete everything in the
       * calendar.
       */

      String etag = client.getResponse().getResponseHeaderValue("Etag");
      if (etag != null) {
        info.setChangeToken(etag);
      }
    } catch (SynchException se) {
      throw se;
    } catch (Throwable t) {
      throw new SynchException(t);
    } finally {
      try {
        client.release();
      } catch (Throwable t) {
      }
    }
  }