/**
   * Obtain values from XML an create value object ContentRelation.
   *
   * @param xml The content relation XML (validated by schema).
   * @return ContentRelation
   * @throws InvalidContentException Thrown if content is invalid
   * @throws MissingAttributeValueException Thrown if attribute value is missing
   * @throws SystemException Thrown if internal error occur
   * @throws de.escidoc.core.common.exceptions.application.invalid.XmlCorruptedException
   */
  private static ContentRelationCreate parseContentRelation(final String xml)
      throws MissingAttributeValueException, InvalidContentException, SystemException,
          XmlCorruptedException {

    final StaxParser sp = new StaxParser();

    final ContentRelationHandler contentRelationHandler = new ContentRelationHandler(sp);
    sp.addHandler(contentRelationHandler);

    try {
      sp.parse(xml);
    } catch (final InvalidContentException e) {
      throw new InvalidContentException(e.getMessage(), e);
    } catch (final RelationPredicateNotFoundException e) {
      // shouldn't happen
      throw new SystemException(e);
    } catch (final XmlCorruptedException e) {
      throw new XmlCorruptedException(e.getMessage(), e);
    } catch (final MissingAttributeValueException e) {
      throw new MissingAttributeValueException(e.getMessage(), e);
    } catch (final InvalidStatusException e) {
      // shouldn't happen
      throw new SystemException(e);
    } catch (final SystemException e) {
      throw new SystemException(null, e);
    } catch (final Exception e) {
      XmlUtility.handleUnexpectedStaxParserException(null, e);
    }
    return contentRelationHandler.getContentRelation();
  }
  /**
   * See Interface for functional description.
   *
   * @param xmlData aggregationDefinition as xml in aggregationDefinition schema.
   * @return Returns the XML representation of the resource.
   * @throws MissingMethodParameterException ex
   * @throws ScopeNotFoundException ex
   * @throws SystemException ex
   * @see de.escidoc.core.sm.business.interfaces
   *     .AggregationDefinitionHandlerInterface#create(java.lang.String)
   */
  @Override
  @Transactional(rollbackFor = {SystemException.class, RuntimeException.class})
  public String create(final String xmlData)
      throws MissingMethodParameterException, ScopeNotFoundException, SystemException {
    if (xmlData == null || xmlData.length() == 0) {
      throw new MissingMethodParameterException("xml may not be null");
    }

    // parse
    final StaxParser sp = new StaxParser();
    final AggregationDefinitionStaxHandler handler = new AggregationDefinitionStaxHandler(sp);
    sp.addHandler(handler);
    try {
      sp.parse(xmlData);
    } catch (final Exception e) {
      throw new SystemException(e);
    }

    final String scopeId = handler.getAggregationDefinition().getScope().getId();
    final Scope scope = scopesDao.retrieve(scopeId);

    // get AggregationDefinitionObject to insert aggregation-definition
    // into database
    final AggregationDefinition aggregationDefinition = handler.getAggregationDefinition();
    aggregationDefinition.setCreatorId(utility.getCurrentUserId());
    aggregationDefinition.setCreationDate(new Timestamp(System.currentTimeMillis()));
    aggregationDefinition.setScope(scope);

    dao.save(aggregationDefinition);
    handler.setAggregationDefinition(aggregationDefinition);

    // AggregationStatisticDataSelectors
    for (final AggregationStatisticDataSelector selector :
        handler.getAggregationStatisticDataSelectors()) {
      dao.save(selector);
    }
    aggregationDefinition.setAggregationStatisticDataSelectors(
        handler.getAggregationStatisticDataSelectors());

    // AggregationTables
    for (final AggregationTable aggregationTable : handler.getAggregationTables()) {
      dao.save(aggregationTable);
    }
    aggregationDefinition.setAggregationTables(handler.getAggregationTables());

    // Get databaseTableVos for all Aggregation-Tables
    // defined in Aggregation Definition
    final Collection<DatabaseTableVo> databaseTableVos =
        generateAggregationDatabaseTableVos(aggregationDefinition);
    if (databaseTableVos != null) {
      for (final DatabaseTableVo databaseTableVo : databaseTableVos) {
        // create aggregation table in Database
        dbAccessor.createTable(databaseTableVo);
      }
    }

    return renderer.render(aggregationDefinition);
  }
  /**
   * Handle the start of an element.
   *
   * @param element The element.
   * @return The element.
   * @throws MissingAttributeValueException If a required element is not set.
   * @throws InvalidContentException If the parsed XML contains not allowed parts.
   * @throws WebserverSystemException If the eSciDoc configuration file can not be read. FIXME
   *     should probably not be thrown so late.
   */
  @Override
  public StartElement startElement(final StartElement element)
      throws MissingAttributeValueException, InvalidContentException, WebserverSystemException {

    final String currentPath = parser.getCurPath();
    if (currentPath.equals(this.metadataXPath)) {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Parser reached " + this.metadataXPath);
      }
      this.curMdRecordDefinition = new MdRecordDefinitionCreate();

      try {
        curMdRecordDefinition.setName(element.getAttributeValue(null, "name"));
      } catch (final NoSuchAttributeException e) {
        throw new InvalidContentException("Attribute name required for md-record-definition.", e);
      }
    } else if (currentPath.equals(this.metadataXPath + "/schema")) {

      try {
        curMdRecordDefinition.setSchemaHref(
            element.getAttributeValue(Constants.XLINK_NS_URI, "href"));
      } catch (final MalformedURLException e) {
        throw new InvalidContentException(e);
      } catch (final IOException e) {
        throw new WebserverSystemException("Configuration could not be read.", e);
      } catch (final NoSuchAttributeException e) {
        throw new InvalidContentException("No href for schema element.", e);
      }
    }

    return element;
  }
  @Override
  public String characters(final String data, final StartElement element)
      throws IntegritySystemException {
    final String curPath = parser.getCurPath();
    final String theName = element.getLocalName();

    // organizational-unit
    if (curPath.equals(DC_PATH + '/' + Elements.ELEMENT_DC_TITLE)) {
      if (data.length() == 0) {
        properties.put(Elements.ELEMENT_DC_TITLE, "");
        properties.put(TripleStoreUtility.PROP_DC_TITLE, "");
      } else {
        // propertiesMap.put(theName, data);
        properties.put(Elements.ELEMENT_DC_TITLE, data);
        properties.put(TripleStoreUtility.PROP_DC_TITLE, data);
      }
    } else if (theName.equals(Elements.ELEMENT_DESCRIPTION)) {

      if (data.length() == 0) {

        properties.put(Elements.ELEMENT_DESCRIPTION, "");
        properties.put(TripleStoreUtility.PROP_DC_DESCRIPTION, "");
      } else {
        // propertiesMap.put(theName, data);
        properties.put(Elements.ELEMENT_DESCRIPTION, data);
        properties.put(TripleStoreUtility.PROP_DC_DESCRIPTION, data);
      }
    }
    return data;
  }
  public StartElement startElement(StartElement element) {

    String currentPath = parser.getCurPath();

    if (itemPath.equals(currentPath) || containerPath.equals(currentPath)) {
      membersCount++;
    }
    return element;
  }
  @Override
  public EndElement endElement(final EndElement element) {

    final String currentPath = parser.getCurPath();
    if (currentPath.equals(this.metadataXPath)) {

      this.mdRecordDefinitions.add(this.curMdRecordDefinition);
    }

    return element;
  }
  /**
   * Parser hits an XML character element. Write all elements and all attributes with path to
   * element and value (eg /properties/version/status=released) in a list. If attribute is
   * xlink:href, extract objectId out of href and replace attribute-name in path with /id.
   *
   * @param s XML character element.
   * @param element StAX StartElement
   * @return XML character element.
   */
  @Override
  public String characters(final String s, final StartElement element)
      throws UnsupportedEncodingException {

    String path = parser.getCurPath().replaceFirst("/.*?/", "/");
    if (path.equals(parser.getCurPath())) {
      path = "";
      values.add("\"type\"=\"" + parser.getCurPath().substring(1) + "\"");
    } else {
      if (s != null && !s.trim().equals("")) {
        values.add("\"" + path + "\"=\"" + s + "\"");
      }
    }
    List<Attribute> attributes = element.getAttributes();
    for (Attribute attribute : attributes) {
      if (attribute.getValue() != null && !attribute.getValue().trim().equals("")) {
        String value = attribute.getValue();
        String namespace = attribute.getNamespace();
        String localName = attribute.getLocalName();
        if (namespace != null && Constants.NS_EXTERNAL_XLINK.equals(namespace)) {
          if (attribute.getLocalName().equals("href")) {
            localName = "id";
            value = value.replaceFirst(".*/", "");
            if (path.equals("")) {
              value = value.replaceFirst("(.*?:.*?):.*", "$1");
            }
            if (!value.contains(":")) {
              value = "";
            }
          } else {
            value = "";
          }
        }
        if (!value.equals("")) {
          values.add("\"" + path + "/" + localName + "\"=\"" + value + "\"");
        }
      }
    }

    return s;
  }
  @Override
  public StartElement startElement(final StartElement element) throws XMLStreamException {
    final String curPath = parser.getCurPath();
    if (this.inside) {
      this.deepLevel++;
      writeElement(element);

      final int attCount = element.getAttributeCount();
      for (int i = 0; i < attCount; i++) {
        final Attribute curAtt = element.getAttribute(i);
        writeAttribute(
            curAtt.getNamespace(),
            element.getLocalName(),
            curAtt.getLocalName(),
            curAtt.getValue(),
            curAtt.getPrefix());
      }
    } else {
      if (curPath.endsWith("components/component")) {
        final int indexObjid = element.indexOfAttribute(null, "objid");
        final int indexHref = element.indexOfAttribute(Constants.XLINK_NS_URI, "href");
        if (!(indexObjid > -1 && element.getAttribute(indexObjid).getValue().length() > 0
            || indexHref > -1
                && Utility.getId(element.getAttribute(indexHref).getValue()).length() > 0)) {

          // start new component if there is no ID
          final ByteArrayOutputStream out = new ByteArrayOutputStream();

          this.writer = XmlUtility.createXmlStreamWriter(out);
          outputStreams.add(out);

          this.inside = true;
          this.deepLevel++;
          writeElement(element);

          final int attCount = element.getAttributeCount();
          for (int i = 0; i < attCount; i++) {
            final Attribute curAtt = element.getAttribute(i);
            writeAttribute(
                curAtt.getNamespace(),
                element.getLocalName(),
                curAtt.getLocalName(),
                curAtt.getValue(),
                curAtt.getPrefix());
          }
        }
      }
    }
    return element;
  }
  @Override
  public EndElement endElement(final EndElement element) throws XMLStreamException {
    final String curPath = parser.getCurPath();

    if (this.inside) {
      writer.writeEndElement();
      this.deepLevel--;

      final String ns = element.getNamespace();
      List nsTrace = nsuris.get(ns);

      if (nsTrace != null
          && (nsTrace.get(2) == null || nsTrace.get(2).equals(element.getPrefix()))
          && nsTrace.get(1).equals(element.getLocalName())
          && (Integer) nsTrace.get(0) == this.deepLevel + 1) {

        nsuris.remove(ns);
      }

      // attribute namespaces
      // TODO iteration is a hack, use
      // javax.xml.namespace.NamespaceContext
      Iterator<String> it = nsuris.keySet().iterator();
      final Collection<String> toRemove = new ArrayList<String>();
      while (it.hasNext()) {
        final String key = it.next();
        nsTrace = nsuris.get(key);
        if ((Integer) nsTrace.get(0) == this.deepLevel + 1) {
          toRemove.add(key);
        }
      }
      it = toRemove.iterator();
      while (it.hasNext()) {
        final String key = it.next();
        nsuris.remove(key);
      }
      if (curPath.endsWith("components/component")) {
        this.inside = false;
        writer.flush();
        writer.close();
      }
    }
    return element;
  }
  @Override
  public StartElement startElement(final StartElement element)
      throws TripleStoreSystemException, WebserverSystemException, InvalidContentException {
    final String curPath = parser.getCurPath();
    if (curPath.startsWith(this.componentPath) && curPath.equals(this.componentPath)) {
      // do my job
      // save componentId
      final int indexObjid = element.indexOfAttribute(null, "objid");
      final int indexHref = element.indexOfAttribute(Constants.XLINK_NS_URI, "href");
      if (indexObjid >= 0 || indexHref >= 0) {
        final String componentId =
            indexObjid >= 0
                ? element.getAttribute(indexObjid).getValue()
                : Utility.getId(element.getAttribute(indexHref).getValue());

        if (componentId.length() > 0) {
          // check if component exists
          boolean componentExists = false;
          final List<String> existingComponents =
              TripleStoreUtility.getInstance().getComponents(this.itemId);
          for (final String existingComponent : existingComponents) {
            if (existingComponent.equals(componentId)) {
              componentExists = true;
              break;
            }
          }
          if (!componentExists) {
            throw new InvalidContentException(
                "Component with id "
                    + componentId
                    + " does not exist in item "
                    + this.itemId
                    + '.');
          }
        }
      }
    }
    return element;
  }
  /**
   * Parser hits an XML end element.
   *
   * @param element StAX EndElement
   * @return StAX EndElement
   */
  @Override
  public EndElement endElement(final EndElement element) throws WebserverSystemException {

    final String currentPath = parser.getCurPath();

    if (xpathContentStream.equals(currentPath)) {

      this.parsingContent = false;

      if (this.hasContent) {
        final Map<String, Object> outputStreams = this.contentHandler.getOutputStreams();

        // MultipleExtractor could deliver a list of stream. But it
        // should be only possible to extract one with this parser
        // chain.
        if (outputStreams.size() > 1) {
          LOGGER.warn("Multiple content-streams.");
        }
        final Iterator<String> it = outputStreams.keySet().iterator();
        final ByteArrayOutputStream outStream =
            (ByteArrayOutputStream) outputStreams.get(it.next());

        try {
          this.content.setContent(outStream.toString(XmlUtility.CHARACTER_ENCODING));
        } catch (final UnsupportedEncodingException e) {
          throw new WebserverSystemException("Application default encoding not supported.", e);
        }
        this.hasContent = false;
        this.contentHandler = null;
      }
      this.contentStream.setContent(this.content);
    } else {
      if (this.parsingContent && this.hasContent) {
        this.contentHandler.endElement(element);
      }
    }

    return element;
  }
  /**
   * Parser hits an XML start element.
   *
   * @param element StartElement from StAX parser
   * @return StAX StartElement
   */
  @Override
  public StartElement startElement(final StartElement element)
      throws InvalidContentException, MissingAttributeValueException, WebserverSystemException {

    if (this.parsingContent) {
      if (this.contentHandler == null) {
        // reached first element after content-stream root element
        this.contentHandler =
            new MultipleExtractor(
                this.xpathContentStream + '/' + element.getLocalName(), this.parser);
      }
      this.hasContent = true;
      this.contentHandler.startElement(element);
    } else {
      final String currentPath = parser.getCurPath();
      if (currentPath.equals(this.xpathContentStream)) {
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parser reached " + currentPath);
        }
        this.parsingContent = true;

        this.contentStream = new ContentStreamCreate();

        this.contentStream.setName(getAttributeValue(element, null, "name"));
        this.contentStream.setMimeType(getAttributeValue(element, null, "mime-type"));
        // this seams strange (title is a href attribute which should
        // be ignored)
        this.contentStream.setTitle(getAttributeValue(element, Constants.XLINK_NS_URI, "title"));

        this.content = new BinaryContent();
        this.content.setStorageType(getAttributeValue(element, null, "storage"));
        this.content.setDataLocation(getAttributeValue(element, Constants.XLINK_NS_URI, "href"));
      }
    }

    return element;
  }
  /**
   * Retrieve the properties of the last version (RELS-EXT) and inject values into
   * ContentRelationCreate object.
   *
   * @param cr ContentRelation object
   * @throws SystemException Thrown in case of internal failure.
   * @throws ContentRelationNotFoundException Thrown if resource with provided id could not be found
   *     in Fedora repository.
   */
  private static void setRelsExtValues(final ContentRelationCreate cr)
      throws SystemException, ContentRelationNotFoundException {

    // retrieve resource with id from Fedora
    final Datastream relsExt;
    try {
      relsExt = new Datastream(Datastream.RELS_EXT_DATASTREAM, cr.getObjid(), null);
    } catch (final StreamNotFoundException e) {
      throw new ContentRelationNotFoundException(
          "Content Relation with id '" + cr.getObjid() + "' could not be found.", e);
    }

    final StaxParser sp = new StaxParser();

    final RelsExtReadHandler eve = new RelsExtReadHandler(sp);
    eve.cleanIdentifier(true);
    sp.addHandler(eve);
    try {
      sp.parse(relsExt.getStream());
    } catch (final Exception e) {
      throw new WebserverSystemException(e);
    }

    final List<Triple> triples = eve.getElementValues().getTriples();

    // write triple values into ContentRelation object

    for (final Triple triple : triples) {
      if (triple.getPredicate().equals(TripleStoreUtility.PROP_FRAMEWORK_BUILD)) {
        cr.setBuildNumber(triple.getObject());
      }
      // creator --------------
      else if (triple.getPredicate().equals(TripleStoreUtility.PROP_CREATED_BY_ID)) {
        cr.getProperties().setCreatedById(triple.getObject());
      } else if (triple.getPredicate().equals(TripleStoreUtility.PROP_CREATED_BY_TITLE)) {
        cr.getProperties().setCreatedByName(triple.getObject());
      }
      // modifier --------------
      else if (triple.getPredicate().equals(TripleStoreUtility.PROP_MODIFIED_BY_ID)) {
        cr.getProperties().setModifiedById(triple.getObject());
      } else if (triple.getPredicate().equals(TripleStoreUtility.PROP_MODIFIED_BY_TITLE)) {
        cr.getProperties().setModifiedByName(triple.getObject());
      }
      // public-status --------------
      else if (triple.getPredicate().equals(TripleStoreUtility.PROP_PUBLIC_STATUS)) {

        final StatusType st;
        try {
          st = StatusType.getStatusType(triple.getObject());
        } catch (final InvalidStatusException e) {
          // shouldn't happen
          throw new SystemException(e);
        }
        cr.getProperties().setStatus(st);
      } else if (triple.getPredicate().equals(TripleStoreUtility.PROP_PUBLIC_STATUS_COMMENT)) {
        cr.getProperties().setStatusComment(triple.getObject());
      } else if (triple.getPredicate().equals(TripleStoreUtility.PROP_OBJECT_TYPE)) {
        // this is not the ContentRelation type, this is the type of
        // resource
        if (!(Constants.CONTENT_RELATION2_OBJECT_TYPE.equals(triple.getObject())
            || (Constants.RDF_NAMESPACE_URI + "Statement").equals(triple.getObject()))) {
          throw new WebserverSystemException("Resource is not from type ContentRelation.");
        }
      } else if (triple.getPredicate().equals(TripleStoreUtility.PROP_CONTENT_RELATION_SUBJECT)) {
        cr.setSubject(triple.getObject());
      } else if (triple.getPredicate().equals(TripleStoreUtility.PROP_CONTENT_RELATION_OBJECT)) {
        cr.setObject(triple.getObject());
      } else if (triple
          .getPredicate()
          .equals(TripleStoreUtility.PROP_CONTENT_RELATION_DESCRIPTION)) {
        cr.getProperties().setDescription(triple.getObject());
      } else if (triple.getPredicate().equals(TripleStoreUtility.PROP_CONTENT_RELATION_TYPE)) {
        try {
          cr.setType(new URI(triple.getObject()));
        } catch (final URISyntaxException e) {
          // shouldn't happen
          throw new SystemException("Stored value for URI in invalid.", e);
        }
      } else if (triple
          .getPredicate()
          .equals(TripleStoreUtility.PROP_CONTENT_RELATION_OBJECT_VERSION)) {
        cr.setObjectVersion(triple.getObject());
      } else {
        // add values for mapping
        LOGGER.warn("Predicate not mapped " + triple.getPredicate() + " = " + triple.getObject());
      }
    }
  }
  @Override
  public StartElement startElement(final StartElement element) throws XMLStreamException {
    final String elementName = element.getLocalName();
    if ("component".equals(elementName)) {
      this.inComponent = true;
      if (this.pids != null) {
        this.componentId = pids.get(this.number);
        this.number++;
      } else {
        final int index = element.indexOfAttribute(null, "objid");
        if (index != -1) {
          final String value = element.getAttribute(index).getValue();
          if (value != null && value.length() > 0) {
            this.componentId = value;
          }
        }
      }
    }

    if (!this.inside) {
      final String currentPath = parser.getCurPath();
      if (paths.containsKey(currentPath)) {
        if (this.insideLevel != 0) {
          throw new XMLStreamException("insideLevel != 0: " + this.insideLevel);
        }

        this.inside = true;
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        this.writer = newInitializedWriter(out);

        final String attributeName = paths.get(currentPath);
        if (this.inComponent) {
          if (this.components == null) {
            this.components = new HashMap<String, Map>();
            outputStreams.put("components", this.components);
          }
          final Map<String, Object> component;
          if (components.containsKey(this.componentId)) {
            component = (HashMap) components.get(this.componentId);
          } else {
            component = new HashMap<String, Object>();
            components.put(this.componentId, component);
          }

          if (attributeName == null) {
            component.put(elementName, out);
          } else {
            final String attributeValue = getAttributeValue(element, null, attributeName);
            if ("md-record".equals(elementName)) {
              Map<String, OutputStream> mdRecords = components.get(this.components);
              if (mdRecords == null) {
                mdRecords = new HashMap<String, OutputStream>();
                component.put("md-records", mdRecords);
              }
              mdRecords.put(attributeValue, out);
            } else {
              component.put(attributeValue, out);
            }
          }
        } else {
          if (attributeName == null) {
            outputStreams.put(elementName, out);
          } else {
            final String attributeValue = getAttributeValue(element, null, attributeName);
            if ("md-record".equals(elementName)) {
              if (this.metadata == null) {
                this.metadata = new HashMap<String, OutputStream>();
                outputStreams.put("md-records", this.metadata);
              }
              metadata.put(attributeValue, out);
            } else {
              outputStreams.put(attributeValue, out);
            }
          }
        }
      }
    }

    // write start element with attributes (and implicit neccessary
    // namespace declarations due to the repairing xml writer
    if (this.inside) {
      String namespace = element.getNamespace();
      if (namespace != null && !namespaceMap.containsKey(namespace)) {
        final String prefix = element.getPrefix();
        if (prefix != null) {
          writer.setPrefix(prefix, element.getNamespace());
        } else {
          writer.setDefaultNamespace(element.getNamespace());
        }
      }

      if (!("md-record".equals(elementName) && paths.containsKey(parser.getCurPath()))) {
        writer.writeStartElement(element.getNamespace(), elementName);
      }
      final int attCount = element.getAttributeCount();
      for (int i = 0; i < attCount; i++) {
        final Attribute curAtt = element.getAttribute(i);
        namespace = curAtt.getNamespace();
        if (namespace != null && !namespaceMap.containsKey(namespace)) {
          // Prefix is not null. (FRS)
          writer.setPrefix(curAtt.getPrefix(), namespace);
        }
        if (!("md-record".equals(elementName) && paths.containsKey(parser.getCurPath()))) {
          writer.writeAttribute(namespace, curAtt.getLocalName(), curAtt.getValue());
        }
      }
      this.insideLevel++;
    }

    // this has to be the last handler
    return element;
  }