blob: 6c897c8c4fe4b2e88c6c134d91373183b1d2b746 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Prashant Deva - Bug 194674 [prov] Provide write access to metadata repository
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata.repository;
import java.io.*;
import java.net.URL;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.p2.core.helpers.OrderedProperties;
import org.eclipse.equinox.internal.p2.metadata.repository.io.MetadataParser;
import org.eclipse.equinox.internal.p2.metadata.repository.io.MetadataWriter;
import org.eclipse.equinox.internal.p2.persistence.XMLWriter;
import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.internal.provisional.p2.metadata.query.InstallableUnitQuery;
import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository;
import org.eclipse.equinox.internal.provisional.p2.query.Collector;
import org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.AbstractMetadataRepository;
import org.eclipse.equinox.internal.provisional.spi.p2.metadata.repository.AbstractMetadataRepository.RepositoryState;
import org.eclipse.osgi.service.resolver.VersionRange;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Version;
import org.xml.sax.*;
/**
* This class reads and writes provisioning metadata.
*/
public class MetadataRepositoryIO {
/**
* Reads metadata from the given stream, and returns the contained array
* of abstract metadata repositories.
* This method performs buffering, and closes the stream when finished.
*/
public IMetadataRepository read(URL location, InputStream input, IProgressMonitor monitor) throws ProvisionException {
BufferedInputStream bufferedInput = null;
try {
try {
bufferedInput = new BufferedInputStream(input);
Parser repositoryParser = new Parser(Activator.getContext(), Activator.ID);
repositoryParser.parse(input, monitor);
IStatus result = repositoryParser.getStatus();
switch (result.getSeverity()) {
case IStatus.CANCEL :
throw new OperationCanceledException();
case IStatus.ERROR :
throw new ProvisionException(result);
case IStatus.WARNING :
case IStatus.INFO :
LogHelper.log(result);
}
return repositoryParser.getRepository();
} finally {
if (bufferedInput != null)
bufferedInput.close();
}
} catch (IOException ioe) {
String msg = NLS.bind(Messages.io_failedRead, location);
throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_READ, msg, ioe));
}
}
/**
*
*/
public void write(IMetadataRepository repository, OutputStream output) throws IOException {
OutputStream bufferedOutput = null;
try {
bufferedOutput = new BufferedOutputStream(output);
Writer repositoryWriter = new Writer(bufferedOutput, repository.getClass());
repositoryWriter.write(repository);
} finally {
if (bufferedOutput != null) {
bufferedOutput.close();
}
}
}
private interface XMLConstants extends org.eclipse.equinox.internal.p2.metadata.repository.io.XMLConstants {
// Constants defining the structure of the XML for a MetadataRepository
// A format version number for metadata repository XML.
public static final String XML_VERSION = "1.0.0"; //$NON-NLS-1$
public static final Version CURRENT_VERSION = new Version(XML_VERSION);
public static final VersionRange XML_TOLERANCE = new VersionRange(CURRENT_VERSION, true, new Version(2, 0, 0), false);
// Constants for processing Instructions
public static final String PI_REPOSITORY_TARGET = "metadataRepository"; //$NON-NLS-1$
// Constants for metadata repository elements
public static final String REPOSITORY_ELEMENT = "repository"; //$NON-NLS-1$
}
protected XMLWriter.ProcessingInstruction[] createPI(Class repositoryClass) {
//TODO We should remove this processing instruction, but currently old clients rely on this. See bug 210450.
return new XMLWriter.ProcessingInstruction[] {XMLWriter.ProcessingInstruction.makeClassVersionInstruction(XMLConstants.PI_REPOSITORY_TARGET, repositoryClass, XMLConstants.CURRENT_VERSION)};
}
// XML writer for a IMetadataRepository
protected class Writer extends MetadataWriter implements XMLConstants {
public Writer(OutputStream output, Class repositoryClass) throws IOException {
super(output, createPI(repositoryClass));
}
/**
* Write the given metadata repository to the output stream.
*/
public void write(IMetadataRepository repository) {
start(REPOSITORY_ELEMENT);
attribute(NAME_ATTRIBUTE, repository.getName());
attribute(TYPE_ATTRIBUTE, repository.getType());
attribute(VERSION_ATTRIBUTE, repository.getVersion());
attributeOptional(PROVIDER_ATTRIBUTE, repository.getProvider());
attributeOptional(DESCRIPTION_ATTRIBUTE, repository.getDescription()); // TODO: could be cdata?
writeProperties(repository.getProperties());
Collector units = repository.query(InstallableUnitQuery.ANY, new Collector(), null);
writeInstallableUnits(units.iterator(), units.size());
end(REPOSITORY_ELEMENT);
flush();
}
}
/*
* Parser for the contents of a metadata repository,
* as written by the Writer class.
*/
private class Parser extends MetadataParser implements XMLConstants {
private IMetadataRepository theRepository = null;
protected Class theRepositoryClass = null;
public Parser(BundleContext context, String bundleId) {
super(context, bundleId);
}
public synchronized void parse(InputStream stream, IProgressMonitor monitor) throws IOException {
this.status = null;
setProgressMonitor(monitor);
monitor.beginTask(Messages.REPO_LOADING, IProgressMonitor.UNKNOWN);
try {
// TODO: currently not caching the parser since we make no assumptions
// or restrictions on concurrent parsing
getParser();
RepositoryHandler repositoryHandler = new RepositoryHandler();
xmlReader.setContentHandler(new RepositoryDocHandler(REPOSITORY_ELEMENT, repositoryHandler));
xmlReader.parse(new InputSource(stream));
if (isValidXML()) {
theRepository = repositoryHandler.getRepository();
}
} catch (SAXException e) {
throw new IOException(e.getMessage());
} catch (ParserConfigurationException e) {
throw new IOException(e.getMessage());
} finally {
monitor.done();
stream.close();
}
}
public IMetadataRepository getRepository() {
return theRepository;
}
public Class getRepositoryClass() {
return theRepositoryClass;
}
protected Object getRootObject() {
return theRepository;
}
private final class RepositoryDocHandler extends DocHandler {
public RepositoryDocHandler(String rootName, RootHandler rootHandler) {
super(rootName, rootHandler);
}
public void processingInstruction(String target, String data) throws SAXException {
if (PI_REPOSITORY_TARGET.equals(target)) {
Version repositoryVersion = extractPIVersion(target, data);
if (!MetadataRepositoryIO.XMLConstants.XML_TOLERANCE.isIncluded(repositoryVersion)) {
throw new SAXException(NLS.bind(Messages.io_IncompatibleVersion, repositoryVersion, MetadataRepositoryIO.XMLConstants.XML_TOLERANCE));
}
}
}
}
private final class RepositoryHandler extends RootHandler {
private final String[] required = new String[] {NAME_ATTRIBUTE, TYPE_ATTRIBUTE, VERSION_ATTRIBUTE};
private final String[] optional = new String[] {DESCRIPTION_ATTRIBUTE, PROVIDER_ATTRIBUTE};
private InstallableUnitsHandler unitsHandler = null;
private PropertiesHandler propertiesHandler = null;
private AbstractMetadataRepository repository = null;
private RepositoryState state = new RepositoryState();
public RepositoryHandler() {
super();
}
public IMetadataRepository getRepository() {
return repository;
}
protected void handleRootAttributes(Attributes attributes) {
String[] values = parseAttributes(attributes, required, optional);
Version version = checkVersion(this.elementHandled, VERSION_ATTRIBUTE, values[2]);
state.Name = values[0];
state.Type = values[1];
state.Version = version;
state.Description = values[3];
state.Provider = values[4];
state.Location = null;
}
public void startElement(String name, Attributes attributes) {
checkCancel();
if (PROPERTIES_ELEMENT.equals(name)) {
if (propertiesHandler == null) {
propertiesHandler = new PropertiesHandler(this, attributes);
} else {
duplicateElement(this, name, attributes);
}
} else if (INSTALLABLE_UNITS_ELEMENT.equals(name)) {
if (unitsHandler == null) {
unitsHandler = new InstallableUnitsHandler(this, attributes);
} else {
duplicateElement(this, name, attributes);
}
} else {
invalidElement(name, attributes);
}
}
protected void finished() {
if (isValidXML()) {
state.Properties = (propertiesHandler == null ? new OrderedProperties(0) //
: propertiesHandler.getProperties());
state.Units = (unitsHandler == null ? new IInstallableUnit[0] //
: unitsHandler.getUnits());
try {
//can't create repository if missing type - this is already logged when parsing attributes
if (state.Type == null)
return;
//migration support for change to MetadataCache (bug 211934)
if (state.Type.equals("org.eclipse.equinox.internal.p2.installregistry.MetadataCache")) //$NON-NLS-1$
state.Type = "org.eclipse.equinox.internal.p2.metadata.repository.LocalMetadataRepository"; //$NON-NLS-1$
Class clazz = Class.forName(state.Type);
Object repositoryObject = clazz.newInstance();
if (repositoryObject instanceof AbstractMetadataRepository) {
repository = (AbstractMetadataRepository) repositoryObject;
repository.initialize(state);
}
} catch (InstantiationException e) {
// TODO: Throw a SAXException
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO: Throw a SAXException
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO: Throw a SAXException
e.printStackTrace();
}
}
}
}
protected String getErrorMessage() {
return Messages.io_parseError;
}
public String toString() {
// TODO:
return null;
}
}
}