1 /******************************************************************************* 2 * Copyright (c) 2007, 2018 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Prashant Deva - Bug 194674 [prov] Provide write access to metadata repository 14 *******************************************************************************/ 15 package org.eclipse.equinox.internal.p2.metadata.repository; 16 17 import java.io.*; 18 import java.lang.reflect.Constructor; 19 import java.net.MalformedURLException; 20 import java.net.URL; 21 import java.util.Iterator; 22 import java.util.Set; 23 import javax.xml.parsers.ParserConfigurationException; 24 import org.eclipse.core.runtime.*; 25 import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; 26 import org.eclipse.equinox.internal.p2.core.helpers.OrderedProperties; 27 import org.eclipse.equinox.internal.p2.metadata.repository.io.MetadataParser; 28 import org.eclipse.equinox.internal.p2.metadata.repository.io.MetadataWriter; 29 import org.eclipse.equinox.internal.p2.persistence.XMLWriter; 30 import org.eclipse.equinox.p2.core.IProvisioningAgent; 31 import org.eclipse.equinox.p2.core.ProvisionException; 32 import org.eclipse.equinox.p2.metadata.*; 33 import org.eclipse.equinox.p2.query.QueryUtil; 34 import org.eclipse.equinox.p2.repository.IRepositoryReference; 35 import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository; 36 import org.eclipse.equinox.p2.repository.metadata.spi.AbstractMetadataRepository; 37 import org.eclipse.equinox.p2.repository.metadata.spi.AbstractMetadataRepository.RepositoryState; 38 import org.eclipse.osgi.util.NLS; 39 import org.osgi.framework.BundleContext; 40 import org.osgi.framework.FrameworkUtil; 41 import org.xml.sax.*; 42 43 /** 44 * This class reads and writes provisioning metadata. 45 */ 46 public class MetadataRepositoryIO { 47 48 protected final IProvisioningAgent agent; 49 MetadataRepositoryIO(IProvisioningAgent agent)50 public MetadataRepositoryIO(IProvisioningAgent agent) { 51 this.agent = agent; 52 } 53 54 /** 55 * Reads metadata from the given stream, and returns the contained array 56 * of abstract metadata repositories. 57 * This method performs buffering, and closes the stream when finished. 58 */ read(URL location, InputStream input, IProgressMonitor monitor)59 public IMetadataRepository read(URL location, InputStream input, IProgressMonitor monitor) throws ProvisionException { 60 BufferedInputStream bufferedInput = null; 61 try { 62 try { 63 bufferedInput = new BufferedInputStream(input); 64 65 Parser repositoryParser = new Parser( 66 FrameworkUtil.getBundle(MetadataRepositoryIO.class).getBundleContext(), Constants.ID); 67 repositoryParser.setErrorContext(location.toExternalForm()); 68 repositoryParser.parse(input, monitor); 69 IStatus result = repositoryParser.getStatus(); 70 switch (result.getSeverity()) { 71 case IStatus.CANCEL : 72 throw new OperationCanceledException(); 73 case IStatus.ERROR : 74 throw new ProvisionException(result); 75 case IStatus.WARNING : 76 case IStatus.INFO : 77 LogHelper.log(result); 78 } 79 return repositoryParser.getRepository(); 80 } finally { 81 if (bufferedInput != null) 82 bufferedInput.close(); 83 } 84 } catch (IOException ioe) { 85 String msg = NLS.bind(Messages.io_failedRead, location); 86 throw new ProvisionException(new Status(IStatus.ERROR, Constants.ID, ProvisionException.REPOSITORY_FAILED_READ, msg, ioe)); 87 } 88 } 89 90 /** 91 * 92 */ write(IMetadataRepository repository, OutputStream output)93 public void write(IMetadataRepository repository, OutputStream output) throws IOException { 94 try (OutputStream bufferedOutput = new BufferedOutputStream(output)) { 95 Writer repositoryWriter = new Writer(bufferedOutput, repository.getClass()); 96 repositoryWriter.write(repository); 97 } 98 } 99 100 private interface XMLConstants extends org.eclipse.equinox.internal.p2.metadata.repository.io.XMLConstants { 101 102 // Constants defining the structure of the XML for a MetadataRepository 103 104 // A format version number for metadata repository XML. 105 Version COMPATIBLE_VERSION = Version.createOSGi(1, 0, 0); 106 Version CURRENT_VERSION = Version.createOSGi(1, 2, 0); 107 VersionRange XML_TOLERANCE = new VersionRange(COMPATIBLE_VERSION, true, Version.createOSGi(2, 0, 0), false); 108 109 // Constants for processing Instructions 110 String PI_REPOSITORY_TARGET = "metadataRepository"; //$NON-NLS-1$ 111 112 // Constants for metadata repository elements 113 String REPOSITORY_ELEMENT = "repository"; //$NON-NLS-1$ 114 115 } 116 createPI(Class<?> repositoryClass)117 protected XMLWriter.ProcessingInstruction[] createPI(Class<?> repositoryClass) { 118 //TODO We should remove this processing instruction, but currently old clients rely on this. See bug 210450. 119 return new XMLWriter.ProcessingInstruction[] {XMLWriter.ProcessingInstruction.makeTargetVersionInstruction(XMLConstants.PI_REPOSITORY_TARGET, XMLConstants.CURRENT_VERSION)}; 120 } 121 122 // XML writer for a IMetadataRepository 123 protected class Writer extends MetadataWriter implements XMLConstants { 124 Writer(OutputStream output, Class<? extends IMetadataRepository> repositoryClass)125 public Writer(OutputStream output, Class<? extends IMetadataRepository> repositoryClass) { 126 super(output, createPI(repositoryClass)); 127 } 128 129 /** 130 * Write the given metadata repository to the output stream. 131 */ write(IMetadataRepository repository)132 public void write(IMetadataRepository repository) { 133 start(REPOSITORY_ELEMENT); 134 attribute(NAME_ATTRIBUTE, repository.getName()); 135 attribute(TYPE_ATTRIBUTE, repository.getType()); 136 attribute(VERSION_ATTRIBUTE, repository.getVersion()); 137 attributeOptional(PROVIDER_ATTRIBUTE, repository.getProvider()); 138 attributeOptional(DESCRIPTION_ATTRIBUTE, repository.getDescription()); // TODO: could be cdata? 139 140 writeProperties(repository.getProperties()); 141 if (repository instanceof LocalMetadataRepository) { 142 Set<IRepositoryReference> references = ((LocalMetadataRepository) repository).repositories; 143 writeRepositoryReferences(references.iterator(), references.size()); 144 } 145 // The size attribute is a problematic since it forces the use of a collection. 146 Set<IInstallableUnit> units = repository.query(QueryUtil.createIUAnyQuery(), null).toUnmodifiableSet(); 147 writeInstallableUnits(units.iterator(), units.size()); 148 149 end(REPOSITORY_ELEMENT); 150 flush(); 151 } 152 153 /** 154 * Writes a list of {@link IRepositoryReference}. 155 * @param references An Iterator of {@link IRepositoryReference}. 156 * @param size The number of references to write 157 */ writeRepositoryReferences(Iterator<IRepositoryReference> references, int size)158 protected void writeRepositoryReferences(Iterator<IRepositoryReference> references, int size) { 159 if (size == 0) 160 return; 161 start(REPOSITORY_REFERENCES_ELEMENT); 162 attribute(COLLECTION_SIZE_ATTRIBUTE, size); 163 while (references.hasNext()) 164 writeRepositoryReference(references.next()); 165 end(REPOSITORY_REFERENCES_ELEMENT); 166 } 167 writeRepositoryReference(IRepositoryReference reference)168 private void writeRepositoryReference(IRepositoryReference reference) { 169 start(REPOSITORY_REFERENCE_ELEMENT); 170 attribute(URI_ATTRIBUTE, reference.getLocation().toString()); 171 172 try { 173 // we write the URL attribute for backwards compatibility with 3.4.x 174 // this attribute should be removed if we make a breaking format change. 175 attribute(URL_ATTRIBUTE, URIUtil.toURL(reference.getLocation()).toExternalForm()); 176 } catch (MalformedURLException e) { 177 attribute(URL_ATTRIBUTE, reference.getLocation().toString()); 178 } 179 180 attribute(TYPE_ATTRIBUTE, Integer.toString(reference.getType())); 181 attribute(OPTIONS_ATTRIBUTE, Integer.toString(reference.getOptions())); 182 end(REPOSITORY_REFERENCE_ELEMENT); 183 } 184 } 185 186 /* 187 * Parser for the contents of a metadata repository, 188 * as written by the Writer class. 189 */ 190 private class Parser extends MetadataParser implements XMLConstants { 191 192 private IMetadataRepository theRepository = null; 193 Parser(BundleContext context, String bundleId)194 public Parser(BundleContext context, String bundleId) { 195 super(context, bundleId); 196 } 197 parse(InputStream stream, IProgressMonitor monitor)198 public synchronized void parse(InputStream stream, IProgressMonitor monitor) throws IOException { 199 this.status = null; 200 setProgressMonitor(monitor); 201 monitor.beginTask(Messages.repo_loading, IProgressMonitor.UNKNOWN); 202 try { 203 // TODO: currently not caching the parser since we make no assumptions 204 // or restrictions on concurrent parsing 205 getParser(); 206 RepositoryHandler repositoryHandler = new RepositoryHandler(); 207 xmlReader.setContentHandler(new RepositoryDocHandler(REPOSITORY_ELEMENT, repositoryHandler)); 208 xmlReader.parse(new InputSource(stream)); 209 if (isValidXML()) { 210 theRepository = repositoryHandler.getRepository(); 211 } 212 } catch (SAXException e) { 213 if (!(e.getException() instanceof OperationCanceledException)) { 214 IOException ioException = new IOException(e.getMessage()); 215 ioException.initCause(e); 216 throw ioException; 217 } 218 } catch (ParserConfigurationException e) { 219 IOException ioException = new IOException(e.getMessage()); 220 ioException.initCause(e); 221 throw ioException; 222 } finally { 223 monitor.done(); 224 stream.close(); 225 } 226 } 227 getRepository()228 public IMetadataRepository getRepository() { 229 return theRepository; 230 } 231 232 @Override getRootObject()233 protected Object getRootObject() { 234 return theRepository; 235 } 236 237 private final class RepositoryDocHandler extends DocHandler { 238 RepositoryDocHandler(String rootName, RootHandler rootHandler)239 public RepositoryDocHandler(String rootName, RootHandler rootHandler) { 240 super(rootName, rootHandler); 241 } 242 243 @Override processingInstruction(String target, String data)244 public void processingInstruction(String target, String data) throws SAXException { 245 if (PI_REPOSITORY_TARGET.equals(target)) { 246 Version repositoryVersion = extractPIVersion(target, data); 247 if (!MetadataRepositoryIO.XMLConstants.XML_TOLERANCE.isIncluded(repositoryVersion)) { 248 throw new SAXException(NLS.bind(Messages.io_IncompatibleVersion, repositoryVersion, MetadataRepositoryIO.XMLConstants.XML_TOLERANCE)); 249 } 250 } 251 } 252 253 } 254 255 private final class RepositoryHandler extends RootHandler { 256 257 private final String[] required = new String[] {NAME_ATTRIBUTE, TYPE_ATTRIBUTE, VERSION_ATTRIBUTE}; 258 private final String[] optional = new String[] {DESCRIPTION_ATTRIBUTE, PROVIDER_ATTRIBUTE}; 259 260 private InstallableUnitsHandler unitsHandler = null; 261 private PropertiesHandler propertiesHandler = null; 262 private RepositoryReferencesHandler repositoryReferencesHandler = null; 263 264 private AbstractMetadataRepository repository = null; 265 266 private RepositoryState state = new RepositoryState(); 267 RepositoryHandler()268 public RepositoryHandler() { 269 super(); 270 } 271 getRepository()272 public IMetadataRepository getRepository() { 273 return repository; 274 } 275 276 @Override handleRootAttributes(Attributes attributes)277 protected void handleRootAttributes(Attributes attributes) { 278 String[] values = parseAttributes(attributes, required, optional); 279 Version version = checkVersion(this.elementHandled, VERSION_ATTRIBUTE, values[2]); 280 state.Name = values[0]; 281 state.Type = values[1]; 282 state.Version = version; 283 state.Description = values[3]; 284 state.Provider = values[4]; 285 state.Location = null; 286 } 287 288 @Override startElement(String name, Attributes attributes)289 public void startElement(String name, Attributes attributes) { 290 checkCancel(); 291 if (PROPERTIES_ELEMENT.equals(name)) { 292 if (propertiesHandler == null) { 293 propertiesHandler = new PropertiesHandler(this, attributes); 294 } else { 295 duplicateElement(this, name, attributes); 296 } 297 } else if (INSTALLABLE_UNITS_ELEMENT.equals(name)) { 298 if (unitsHandler == null) { 299 unitsHandler = new InstallableUnitsHandler(this, attributes); 300 } else { 301 duplicateElement(this, name, attributes); 302 } 303 } else if (REPOSITORY_REFERENCES_ELEMENT.equals(name)) { 304 if (repositoryReferencesHandler == null) { 305 repositoryReferencesHandler = new RepositoryReferencesHandler(this, attributes); 306 } else { 307 duplicateElement(this, name, attributes); 308 } 309 } else { 310 invalidElement(name, attributes); 311 } 312 } 313 314 @Override finished()315 protected void finished() { 316 if (isValidXML()) { 317 state.Properties = (propertiesHandler == null ? new OrderedProperties(0) // 318 : propertiesHandler.getProperties()); 319 state.Units = (unitsHandler == null ? new IInstallableUnit[0] // 320 : unitsHandler.getUnits()); 321 state.Repositories = repositoryReferencesHandler == null ? new IRepositoryReference[0] : repositoryReferencesHandler.getReferences(); 322 Object repositoryObject = null; 323 //can't create repository if missing type - this is already logged when parsing attributes 324 if (state.Type == null) 325 return; 326 try { 327 Class<?> clazz = Class.forName(state.Type); 328 Constructor<?> ctor = clazz.getConstructor(IProvisioningAgent.class); 329 repositoryObject = ctor.newInstance(agent); 330 } catch (Exception e) { 331 // TODO: Throw a SAXException 332 e.printStackTrace(); 333 } 334 if (repositoryObject instanceof AbstractMetadataRepository) { 335 repository = (AbstractMetadataRepository) repositoryObject; 336 repository.initialize(state); 337 } 338 } 339 } 340 } 341 342 @Override getErrorMessage()343 protected String getErrorMessage() { 344 return Messages.io_parseError; 345 } 346 347 @Override toString()348 public String toString() { 349 // TODO: 350 return null; 351 } 352 } 353 } 354