blob: 4dd1c307c295964913e89e69582c1b157b9d6471 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ace.deployment.provider.repositorybased;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.osgi.framework.Version;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Provides a SAX-based push parser for obtaining all versions of the deployment packages of a certain target.
*/
class BaseRepositoryHandler extends DefaultHandler {
private final String m_targetID;
/** Denotes the current tag in the XML structure. */
private XmlTag m_currentTag;
/** Denotes the current version of the found target. */
private Version m_currentVersion;
/** Denotes whether or not the requested target is found. */
private boolean m_targetFound;
/** Denotes the current deployment artifact. */
private XmlDeploymentArtifact m_currentArtifact;
/** Denotes the directive key of the current deployment artifact. */
private String m_currentDirectiveKey;
/** Denotes the size of an artifact. */
private long m_artifactSize;
/** Denotes the actual URL to the artifact. */
private URL m_artifactURL;
/** To collect characters() */
private final StringBuilder m_buffer;
/**
* Creates a new {@link BaseRepositoryHandler} instance.
*
* @param targetID
* the target ID to search for, cannot be <code>null</code>.
*/
public BaseRepositoryHandler(String targetID) {
m_targetID = targetID;
m_currentTag = XmlTag.unknown;
m_buffer = new StringBuilder();
}
/**
* Parses the given text as {@link Version}.
*
* @param text
* the text to parse as version, can not be <code>null</code>.
* @return a {@link Version} if the given text represent a correct version, never <code>null</code>.
*/
static final Version parseVersion(String text) {
try {
if (text != null) {
return Version.parseVersion(text);
}
}
catch (Exception e) {
// Ignore; simply return an empty version to denote this invalid version...
}
return Version.emptyVersion;
}
@Override
public void startDocument() throws SAXException {
m_currentTag = XmlTag.unknown;
m_currentVersion = null;
m_targetFound = false;
m_currentArtifact = null;
m_currentDirectiveKey = null;
m_artifactURL = null;
m_artifactSize = -1L;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
XmlTag tag = XmlTag.asXmlTag(qName);
// If the given element is an expected child of the current tag, we
// traverse deeper into the XML-hierarchy; otherwise, consider it an
// "unknown"/uninteresting child and keep the current tag as-is...
if (m_currentTag.isExpectedChild(tag)) {
m_currentTag = tag;
}
m_currentDirectiveKey = null;
// If we're parsing the directives of an artifact, take the name for
// later use (the literal text in this tag will be used as value)...
if (XmlTag.directives.equals(m_currentTag)) {
m_currentDirectiveKey = qName;
}
if (XmlTag.size.equals(m_currentTag)) {
m_artifactSize = -1L;
}
m_buffer.setLength(0);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// just collect whatever comes along into our buffer (ACE-399)
// see: http://stackoverflow.com/questions/4567636/java-sax-parser-split-calls-to-characters
m_buffer.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
String text = m_buffer.toString();
if (XmlTag.targetID.equals(m_currentTag)) {
// verify whether we're in the DP for the requested target...
m_targetFound = m_targetID.equals(text);
}
else if (XmlTag.version.equals(m_currentTag)) {
// Don't assume we've got the desired version (yet)...
m_currentVersion = null;
if (m_targetFound) {
m_currentVersion = parseAsVersion(text);
}
}
else if (XmlTag.url.equals(m_currentTag)) {
try {
m_artifactURL = new URL(text);
}
catch (MalformedURLException e) {
throw new SAXException("Unexpected URL!", e);
}
}
else if (XmlTag.size.equals(m_currentTag)) {
try {
m_artifactSize = Long.valueOf(text);
}
catch (NumberFormatException exception) {
m_artifactSize = -1L;
}
}
else if (XmlTag.directives.equals(m_currentTag)) {
if (m_currentArtifact == null) {
m_currentArtifact = new XmlDeploymentArtifact(m_artifactURL, m_artifactSize);
}
String value = text.trim();
if (m_currentDirectiveKey != null && !value.equals("")) {
m_currentArtifact.m_directives.put(m_currentDirectiveKey, value);
}
}
XmlTag tag = XmlTag.asXmlTag(qName);
// When we're ending the current tag, traverse up to its parent...
if (!XmlTag.unknown.equals(tag) && (m_currentTag == tag)) {
m_currentTag = tag.getParent();
}
// Invoke the callbacks for events we're interested in...
if (XmlTag.version.equals(tag)) {
if (m_currentVersion != null && !Version.emptyVersion.equals(m_currentVersion)) {
// Let the version be handled (if needed)...
handleVersion(m_currentVersion);
}
// Let the currentVersion field as-is! We want to reuse it for the artifacts...
}
else if (XmlTag.deploymentArtifact.equals(tag)) {
if (m_currentArtifact != null) {
// push out the current deployment artifact...
handleArtifact(m_currentVersion, m_currentArtifact);
}
m_currentArtifact = null;
m_artifactURL = null;
m_artifactSize = -1L;
}
else if (XmlTag.directives.equals(tag)) {
m_currentDirectiveKey = null;
}
}
/**
* Allows subclasses to handle the given version of a target's deployment package.
* <p>
* By default, this method does nothing.
* </p>
*
* @param version
* the version found, never <code>null</code>.
*/
protected void handleVersion(Version version) {
// NO-op
}
/**
* Allows subclasses to handle the given deployment artifact for the given version of the deployment package.
* <p>
* By default, this method does nothing.
* </p>
*
* @param version
* the version of the deployment package;
* @param artifact
* the deployment artifact itself.
*/
protected void handleArtifact(Version version, XmlDeploymentArtifact artifact) {
// NO-op
}
/**
* Parses the given text as {@link Version}.
*
* @param text
* the text to parse as version, can not be <code>null</code>.
* @return a {@link Version} if the given text represent a correct version, can be <code>null</code> in case of an
* incorrect/empty version..
*/
protected final Version parseAsVersion(String text) {
Version result = parseVersion(text);
if (Version.emptyVersion.equals(result)) {
return null;
}
return result;
}
/**
* Helper class to store a pair of URL and directive, in which the directive may be empty.
*/
public static class XmlDeploymentArtifact {
private final URL m_url;
private final long m_size;
private final Map<String, String> m_directives;
private XmlDeploymentArtifact(URL url, long size) {
m_url = url;
m_size = size;
m_directives = new HashMap<>();
}
public long getSize() {
return m_size;
}
public URL getUrl() {
return m_url;
}
public Map<String, String> getDirective() {
return m_directives;
}
}
/**
* Defines the structure of our XML (only the parts we're interested in).
*/
public static enum XmlTag {
targetID,
version,
attributes(targetID, version),
url,
size,
directives,
deploymentArtifact(url, size, directives),
artifacts(deploymentArtifact),
tags,
deploymentversion(attributes, tags, artifacts),
deploymentversions(deploymentversion),
repository(deploymentversions),
unknown(repository);
private final XmlTag[] m_children;
private XmlTag m_parent;
private XmlTag(XmlTag... possibleChildren) {
m_children = possibleChildren;
// Update the children's parent...
for (int i = 0; i < m_children.length; i++) {
m_children[i].m_parent = this;
}
}
/**
* Returns the parent tag of this tag.
*
* @return a parent tag, can be <code>null</code>.
*/
public XmlTag getParent() {
return m_parent;
}
/**
* Returns whether the given XML tag is an expected child of this tag.
*
* @param xmlTag
* the XML tag to test, cannot be <code>null</code>.
* @return <code>true</code> if the given tag is an expected child of this tag, <code>false</code> otherwise.
*/
public boolean isExpectedChild(XmlTag xmlTag) {
for (int i = 0; i < m_children.length; i++) {
if (xmlTag.equals(m_children[i])) {
return true;
}
}
return false;
}
/**
* Provides a "safe" way of representing the given tag name as instance of this enum. If the given name does not
* represent a defined tag, "unknown" will be returned.
*
* @param name
* the XML tag-name to represent as enum value, cannot be <code>null</code>.
* @return a {@link XmlTag} representation of the given tag-name, never <code>null</code>.
*/
public static XmlTag asXmlTag(String name) {
XmlTag[] values = { artifacts, attributes, deploymentArtifact, deploymentversion, deploymentversions, directives, repository, size, tags, targetID, url, version };
for (int i = 0; i < values.length; i++) {
if (values[i].name().equals(name)) {
return values[i];
}
}
return XmlTag.unknown;
}
}
}