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