1 /*******************************************************************************
2  * Copyright (c) 2012, 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  *******************************************************************************/
14 package org.eclipse.osgi.storage;
15 
16 import java.io.BufferedReader;
17 import java.io.File;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.io.InputStreamReader;
21 import java.net.MalformedURLException;
22 import java.net.URL;
23 import java.security.ProtectionDomain;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.Dictionary;
27 import java.util.Enumeration;
28 import java.util.List;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.ResourceBundle;
32 import java.util.Set;
33 import java.util.concurrent.TimeUnit;
34 import org.eclipse.osgi.container.Module;
35 import org.eclipse.osgi.container.ModuleContainerAdaptor.ModuleEvent;
36 import org.eclipse.osgi.container.ModuleRevision;
37 import org.eclipse.osgi.container.ModuleRevisionBuilder;
38 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
39 import org.eclipse.osgi.framework.util.CaseInsensitiveDictionaryMap;
40 import org.eclipse.osgi.framework.util.ThreadInfoReport;
41 import org.eclipse.osgi.internal.container.LockSet;
42 import org.eclipse.osgi.internal.debug.Debug;
43 import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
44 import org.eclipse.osgi.internal.framework.EquinoxContainer;
45 import org.eclipse.osgi.internal.hookregistry.StorageHookFactory;
46 import org.eclipse.osgi.internal.hookregistry.StorageHookFactory.StorageHook;
47 import org.eclipse.osgi.internal.messages.Msg;
48 import org.eclipse.osgi.storage.Storage.StorageException;
49 import org.eclipse.osgi.storage.bundlefile.BundleEntry;
50 import org.eclipse.osgi.storage.bundlefile.BundleFile;
51 import org.eclipse.osgi.storage.url.BundleResourceHandler;
52 import org.eclipse.osgi.storage.url.bundleentry.Handler;
53 import org.eclipse.osgi.util.ManifestElement;
54 import org.eclipse.osgi.util.NLS;
55 import org.osgi.framework.BundleException;
56 import org.osgi.framework.Constants;
57 
58 public final class BundleInfo {
59 	public static final String OSGI_BUNDLE_MANIFEST = "META-INF/MANIFEST.MF"; //$NON-NLS-1$
60 	public static final String MULTI_RELEASE_HEADER = "Multi-Release"; //$NON-NLS-1$
61 	public static final String MULTI_RELEASE_VERSIONS = "META-INF/versions/"; //$NON-NLS-1$
62 	public static final Collection<String> MULTI_RELEASE_FILTER_PREFIXES = Collections.singleton("META-INF/"); //$NON-NLS-1$
63 
64 	public final class Generation {
65 		private final long generationId;
66 		private final Object genMonitor = new Object();
67 		private final Dictionary<String, String> cachedHeaders;
68 		private File content;
69 		private boolean isDirectory;
70 		private boolean isReference;
71 		private boolean hasPackageInfo;
72 		private BundleFile bundleFile;
73 		private Map<String, String> rawHeaders;
74 		private ModuleRevision revision;
75 		private ManifestLocalization headerLocalization;
76 		private ProtectionDomain domain;
77 		private NativeCodeFinder nativeCodeFinder;
78 		private List<StorageHook<?, ?>> storageHooks;
79 		private long lastModified;
80 		private boolean isMRJar;
81 
Generation(long generationId)82 		Generation(long generationId) {
83 			this.generationId = generationId;
84 			this.cachedHeaders = new CachedManifest(this, Collections.<String, String> emptyMap());
85 		}
86 
Generation(long generationId, File content, boolean isDirectory, boolean isReference, boolean hasPackageInfo, Map<String, String> cached, long lastModified, boolean isMRJar)87 		Generation(long generationId, File content, boolean isDirectory, boolean isReference, boolean hasPackageInfo, Map<String, String> cached, long lastModified, boolean isMRJar) {
88 			this.generationId = generationId;
89 			this.content = content;
90 			this.isDirectory = isDirectory;
91 			this.isReference = isReference;
92 			this.hasPackageInfo = hasPackageInfo;
93 			this.cachedHeaders = new CachedManifest(this, cached);
94 			this.lastModified = lastModified;
95 			this.isMRJar = isMRJar;
96 		}
97 
getBundleFile()98 		public BundleFile getBundleFile() {
99 			synchronized (genMonitor) {
100 				if (bundleFile == null) {
101 					if (getBundleId() == 0 && content == null) {
102 						bundleFile = new SystemBundleFile();
103 					} else {
104 						bundleFile = getStorage().createBundleFile(content, this, isDirectory, true);
105 					}
106 				}
107 				return bundleFile;
108 			}
109 		}
110 
close()111 		public void close() {
112 			synchronized (genMonitor) {
113 				if (bundleFile != null) {
114 					try {
115 						bundleFile.close();
116 					} catch (IOException e) {
117 						// ignore
118 					}
119 				}
120 			}
121 		}
122 
getHeaders()123 		public Dictionary<String, String> getHeaders() {
124 			return cachedHeaders;
125 		}
126 
getRawHeaders()127 		Map<String, String> getRawHeaders() {
128 			synchronized (genMonitor) {
129 				if (rawHeaders == null) {
130 					BundleEntry manifest = getBundleFile().getEntry(OSGI_BUNDLE_MANIFEST);
131 					if (manifest == null) {
132 						rawHeaders = Collections.emptyMap();
133 					} else {
134 						try {
135 							Map<String, String> merged = ManifestElement.parseBundleManifest(manifest.getInputStream(), new CaseInsensitiveDictionaryMap<String, String>());
136 							// For MRJARs only replace Import-Package and Require-Capability if the versioned values are non-null
137 							if (Boolean.parseBoolean(merged.get(MULTI_RELEASE_HEADER))) {
138 								for (int i = getStorage().getRuntimeVersion().getMajor(); i > 8; i--) {
139 									String versionManifest = "META-INF/versions/" + i + "/OSGI-INF/MANIFEST.MF"; //$NON-NLS-1$ //$NON-NLS-2$
140 									BundleEntry versionEntry = getBundleFile().getEntry(versionManifest);
141 									if (versionEntry != null) {
142 										Map<String, String> versioned = ManifestElement.parseBundleManifest(versionEntry.getInputStream(), new CaseInsensitiveDictionaryMap<String, String>());
143 										String versionedImport = versioned.get(Constants.IMPORT_PACKAGE);
144 										String versionedRequireCap = versioned.get(Constants.REQUIRE_CAPABILITY);
145 										if (versionedImport != null) {
146 											merged.put(Constants.IMPORT_PACKAGE, versionedImport);
147 										}
148 										if (versionedRequireCap != null) {
149 											merged.put(Constants.REQUIRE_CAPABILITY, versionedRequireCap);
150 										}
151 										// found a versioned entry; stop searching for more versions
152 										break;
153 									}
154 								}
155 							}
156 							rawHeaders = Collections.unmodifiableMap(merged);
157 						} catch (Exception e) {
158 							if (e instanceof RuntimeException) {
159 								throw (RuntimeException) e;
160 							}
161 							throw new RuntimeException("Error occurred getting the bundle manifest.", e); //$NON-NLS-1$
162 						}
163 					}
164 				}
165 				return rawHeaders;
166 			}
167 		}
168 
getHeaders(String locale)169 		public Dictionary<String, String> getHeaders(String locale) {
170 			ManifestLocalization current = getManifestLocalization();
171 			return current.getHeaders(locale);
172 		}
173 
getResourceBundle(String locale)174 		public ResourceBundle getResourceBundle(String locale) {
175 			ManifestLocalization current = getManifestLocalization();
176 			String defaultLocale = Locale.getDefault().toString();
177 			if (locale == null) {
178 				locale = defaultLocale;
179 			}
180 			return current.getResourceBundle(locale, defaultLocale.equals(locale));
181 		}
182 
getManifestLocalization()183 		private ManifestLocalization getManifestLocalization() {
184 			synchronized (genMonitor) {
185 				if (headerLocalization == null) {
186 					headerLocalization = new ManifestLocalization(this, getHeaders(), getStorage().getConfiguration().getConfiguration(EquinoxConfiguration.PROP_ROOT_LOCALE, "en")); //$NON-NLS-1$
187 				}
188 				return headerLocalization;
189 			}
190 		}
191 
clearManifestCache()192 		public void clearManifestCache() {
193 			synchronized (genMonitor) {
194 				if (headerLocalization != null) {
195 					headerLocalization.clearCache();
196 				}
197 			}
198 		}
199 
getGenerationId()200 		public long getGenerationId() {
201 			return this.generationId;
202 		}
203 
getLastModified()204 		public long getLastModified() {
205 			return lastModified;
206 		}
207 
isDirectory()208 		public boolean isDirectory() {
209 			synchronized (this.genMonitor) {
210 				return this.isDirectory;
211 			}
212 		}
213 
isReference()214 		public boolean isReference() {
215 			synchronized (this.genMonitor) {
216 				return this.isReference;
217 			}
218 		}
219 
hasPackageInfo()220 		public boolean hasPackageInfo() {
221 			synchronized (this.genMonitor) {
222 				return this.hasPackageInfo;
223 			}
224 		}
225 
isMRJar()226 		public boolean isMRJar() {
227 			synchronized (this.genMonitor) {
228 				return this.isMRJar;
229 			}
230 		}
231 
getContent()232 		public File getContent() {
233 			synchronized (this.genMonitor) {
234 				return this.content;
235 			}
236 		}
237 
setContent(File content, boolean isReference)238 		void setContent(File content, boolean isReference) {
239 			synchronized (this.genMonitor) {
240 				this.content = content;
241 				this.isDirectory = content == null ? false : Storage.secureAction.isDirectory(content);
242 				this.isReference = isReference;
243 				setLastModified(content);
244 			}
245 		}
246 
setLastModified(File content)247 		private void setLastModified(File content) {
248 			if (content == null) {
249 				// Bug 477787: content will be null when the osgi.framework configuration property contains an invalid value.
250 				lastModified = 0;
251 				return;
252 			}
253 			if (isDirectory)
254 				content = new File(content, "META-INF/MANIFEST.MF"); //$NON-NLS-1$
255 			lastModified = Storage.secureAction.lastModified(content);
256 		}
257 
setStorageHooks(List<StorageHook<?, ?>> storageHooks, boolean install)258 		void setStorageHooks(List<StorageHook<?, ?>> storageHooks, boolean install) {
259 			synchronized (this.genMonitor) {
260 				this.storageHooks = storageHooks;
261 				if (install) {
262 					this.hasPackageInfo = BundleInfo.hasPackageInfo(getBundleFile());
263 					this.isMRJar = Boolean.parseBoolean(getRawHeaders().get(MULTI_RELEASE_HEADER));
264 				}
265 			}
266 		}
267 
268 		@SuppressWarnings("unchecked")
getStorageHook(Class<? extends StorageHookFactory<S, L, H>> factoryClass)269 		public <S, L, H extends StorageHook<S, L>> H getStorageHook(Class<? extends StorageHookFactory<S, L, H>> factoryClass) {
270 			synchronized (this.genMonitor) {
271 				if (this.storageHooks == null)
272 					return null;
273 				for (StorageHook<?, ?> hook : storageHooks) {
274 					if (hook.getFactoryClass().equals(factoryClass)) {
275 						return (H) hook;
276 					}
277 				}
278 			}
279 			return null;
280 		}
281 
getRevision()282 		public ModuleRevision getRevision() {
283 			synchronized (this.genMonitor) {
284 				return this.revision;
285 			}
286 		}
287 
setRevision(ModuleRevision revision)288 		public void setRevision(ModuleRevision revision) {
289 			synchronized (this.genMonitor) {
290 				this.revision = revision;
291 			}
292 		}
293 
getDomain()294 		public ProtectionDomain getDomain() {
295 			if (getBundleId() == 0 || System.getSecurityManager() == null) {
296 				return null;
297 			}
298 			synchronized (this.genMonitor) {
299 				if (domain == null) {
300 					if (revision == null) {
301 						throw new IllegalStateException("The revision is not yet set for this generation."); //$NON-NLS-1$
302 					}
303 					domain = getStorage().getSecurityAdmin().createProtectionDomain(revision.getBundle());
304 				}
305 				return domain;
306 			}
307 		}
308 
309 		/**
310 		 * Gets called by BundleFile during {@link BundleFile#getFile(String, boolean)}.  This method
311 		 * will allocate a File object where content of the specified path may be
312 		 * stored for this generation.  The returned File object may
313 		 * not exist if the content has not previously been stored.
314 		 * @param path the path to the content to extract from the generation
315 		 * @return a file object where content of the specified path may be stored.
316 		 * @throws StorageException if the path will escape the persistent storage of the generation
317 		 */
getExtractFile(String path)318 		public File getExtractFile(String path) {
319 			return getExtractFile(null, path);
320 		}
321 
322 		/**
323 		 * Gets called by BundleFile during {@link BundleFile#getFile(String, boolean)}.  This method
324 		 * will allocate a File object where content of the specified path may be
325 		 * stored for this generation.  The returned File object may
326 		 * not exist if the content has not previously been stored.
327 		 * @param path the path to the content to extract from the generation
328 		 * @param base the base path that is prepended to the path, may be null
329 		 * @return a file object where content of the specified path may be stored.
330 		 * @throws StorageException if the path will escape the persistent storage of
331 		 * the generation starting at the specified base
332 		 */
getExtractFile(String base, String path)333 		public File getExtractFile(String base, String path) {
334 			StringBuilder baseBuilder = new StringBuilder();
335 			baseBuilder.append(getBundleId()).append('/').append(getGenerationId());
336 			if (base != null) {
337 				baseBuilder.append('/').append(base);
338 			}
339 
340 			return getStorage().getFile(baseBuilder.toString(), path, true);
341 		}
342 
storeContent(File destination, InputStream in, boolean nativeCode)343 		public void storeContent(File destination, InputStream in, boolean nativeCode) throws IOException {
344 			/* the entry has not been cached */
345 			if (getStorage().getConfiguration().getDebug().DEBUG_STORAGE)
346 				Debug.println("Creating file: " + destination.getPath()); //$NON-NLS-1$
347 			/* create the necessary directories */
348 			File dir = new File(destination.getParent());
349 			if (!dir.mkdirs() && !dir.isDirectory()) {
350 				if (getStorage().getConfiguration().getDebug().DEBUG_STORAGE)
351 					Debug.println("Unable to create directory: " + dir.getPath()); //$NON-NLS-1$
352 				throw new IOException(NLS.bind(Msg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, dir.getAbsolutePath()));
353 			}
354 			/* copy the entry to the cache */
355 			File tempDest = File.createTempFile("staged", ".tmp", dir); //$NON-NLS-1$ //$NON-NLS-2$
356 			StorageUtil.readFile(in, tempDest);
357 			if (destination.exists()) {
358 				// maybe because some other thread already beat us there.
359 				// just delete our staged copy
360 				tempDest.delete();
361 			} else {
362 				StorageUtil.move(tempDest, destination, getStorage().getConfiguration().getDebug().DEBUG_STORAGE);
363 			}
364 			if (nativeCode) {
365 				getBundleInfo().getStorage().setPermissions(destination);
366 			}
367 		}
368 
getBundleInfo()369 		public BundleInfo getBundleInfo() {
370 			return BundleInfo.this;
371 		}
372 
delete()373 		public void delete() {
374 			List<StorageHook<?, ?>> hooks = getStorageHooks();
375 			if (hooks != null) {
376 				for (StorageHook<?, ?> hook : hooks) {
377 					hook.deletingGeneration();
378 				}
379 			}
380 			synchronized (this.genMonitor) {
381 				// make sure the bundle file is closed
382 				if (bundleFile != null) {
383 					try {
384 						bundleFile.close();
385 					} catch (IOException e) {
386 						// ignore
387 					}
388 				}
389 			}
390 			getBundleInfo().delete(this);
391 		}
392 
getEntry(String path)393 		public URL getEntry(String path) {
394 			BundleEntry entry = getBundleFile().getEntry(path);
395 			if (entry == null)
396 				return null;
397 			path = BundleFile.fixTrailingSlash(path, entry);
398 			try {
399 				//use the constant string for the protocol to prevent duplication
400 				return Storage.secureAction.getURL(BundleResourceHandler.OSGI_ENTRY_URL_PROTOCOL, Long.toString(getBundleId()) + BundleResourceHandler.BID_FWKID_SEPARATOR + Integer.toString(getStorage().getModuleContainer().hashCode()), 0, path, new Handler(getStorage().getModuleContainer(), entry));
401 			} catch (MalformedURLException e) {
402 				return null;
403 			}
404 		}
405 
findLibrary(String libname)406 		public String findLibrary(String libname) {
407 			NativeCodeFinder currentFinder;
408 			synchronized (this.genMonitor) {
409 				if (nativeCodeFinder == null) {
410 					nativeCodeFinder = new NativeCodeFinder(this);
411 				}
412 				currentFinder = nativeCodeFinder;
413 			}
414 			return currentFinder.findLibrary(libname);
415 		}
416 
getStorageHooks()417 		List<StorageHook<?, ?>> getStorageHooks() {
418 			synchronized (this.genMonitor) {
419 				return this.storageHooks;
420 			}
421 		}
422 
adaptModuleRevisionBuilder(ModuleEvent operation, Module origin, ModuleRevisionBuilder builder)423 		public ModuleRevisionBuilder adaptModuleRevisionBuilder(ModuleEvent operation, Module origin, ModuleRevisionBuilder builder) {
424 			List<StorageHook<?, ?>> hooks = getStorageHooks();
425 			if (hooks != null) {
426 				for (StorageHook<?, ?> hook : hooks) {
427 					ModuleRevisionBuilder hookResult = hook.adaptModuleRevisionBuilder(operation, origin, builder);
428 					if (hookResult != null) {
429 						builder = hookResult;
430 					}
431 				}
432 			}
433 			return builder;
434 		}
435 	}
436 
437 	private final Storage storage;
438 	private final long bundleId;
439 	private final String location;
440 	private long nextGenerationId;
441 	private final Object infoMonitor = new Object();
442 	private LockSet<Long> generationLocks;
443 
BundleInfo(Storage storage, long bundleId, String location, long nextGenerationId)444 	public BundleInfo(Storage storage, long bundleId, String location, long nextGenerationId) {
445 		this.storage = storage;
446 		this.bundleId = bundleId;
447 		this.location = location;
448 		this.nextGenerationId = nextGenerationId;
449 	}
450 
getBundleId()451 	public long getBundleId() {
452 		return bundleId;
453 	}
454 
getLocation()455 	public String getLocation() {
456 		return location;
457 	}
458 
createGeneration()459 	Generation createGeneration() throws BundleException {
460 		synchronized (this.infoMonitor) {
461 			if (generationLocks == null) {
462 				generationLocks = new LockSet<>();
463 			}
464 			boolean lockedID;
465 			try {
466 				lockedID = generationLocks.tryLock(nextGenerationId, 5, TimeUnit.SECONDS);
467 			} catch (InterruptedException e) {
468 				Thread.currentThread().interrupt();
469 				throw new BundleException("Failed to obtain id locks for generation.", BundleException.STATECHANGE_ERROR, e); //$NON-NLS-1$
470 			}
471 			if (!lockedID) {
472 				throw new BundleException("Failed to obtain id locks for generation.", BundleException.STATECHANGE_ERROR, new ThreadInfoReport(generationLocks.getLockInfo(nextGenerationId))); //$NON-NLS-1$
473 			}
474 			Generation newGeneration = new Generation(nextGenerationId++);
475 			return newGeneration;
476 		}
477 	}
478 
unlockGeneration(Generation generation)479 	void unlockGeneration(Generation generation) {
480 		synchronized (this.infoMonitor) {
481 			if (generationLocks == null) {
482 				throw new IllegalStateException("The generation id was not locked."); //$NON-NLS-1$
483 			}
484 			generationLocks.unlock(generation.getGenerationId());
485 		}
486 	}
487 
restoreGeneration(long generationId, File content, boolean isDirectory, boolean isReference, boolean hasPackageInfo, Map<String, String> cached, long lastModified, boolean isMRJar)488 	Generation restoreGeneration(long generationId, File content, boolean isDirectory, boolean isReference, boolean hasPackageInfo, Map<String, String> cached, long lastModified, boolean isMRJar) {
489 		synchronized (this.infoMonitor) {
490 			Generation restoredGeneration = new Generation(generationId, content, isDirectory, isReference, hasPackageInfo, cached, lastModified, isMRJar);
491 			return restoredGeneration;
492 		}
493 	}
494 
getStorage()495 	public Storage getStorage() {
496 		return storage;
497 	}
498 
delete()499 	public void delete() {
500 		try {
501 			getStorage().delete(getStorage().getFile(Long.toString(getBundleId()), false));
502 		} catch (IOException e) {
503 			storage.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.WARNING, "Error deleting bunlde info.", e); //$NON-NLS-1$
504 		}
505 	}
506 
delete(Generation generation)507 	void delete(Generation generation) {
508 		try {
509 			getStorage().delete(getStorage().getFile(getBundleId() + "/" + generation.getGenerationId(), false)); //$NON-NLS-1$
510 		} catch (IOException e) {
511 			storage.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.WARNING, "Error deleting generation.", e); //$NON-NLS-1$
512 		}
513 	}
514 
getNextGenerationId()515 	public long getNextGenerationId() {
516 		synchronized (this.infoMonitor) {
517 			return nextGenerationId;
518 		}
519 	}
520 
getDataFile(String path)521 	public File getDataFile(String path) {
522 		File dataRoot = getStorage().getFile(getBundleId() + "/" + Storage.BUNDLE_DATA_DIR, false); //$NON-NLS-1$
523 		if (!Storage.secureAction.isDirectory(dataRoot) && (storage.isReadOnly() || !(Storage.secureAction.mkdirs(dataRoot) || Storage.secureAction.isDirectory(dataRoot)))) {
524 			if (getStorage().getConfiguration().getDebug().DEBUG_STORAGE)
525 				Debug.println("Unable to create bundle data directory: " + dataRoot.getAbsolutePath()); //$NON-NLS-1$
526 			return null;
527 		}
528 		return path == null ? dataRoot : new File(dataRoot, path);
529 	}
530 
531 	// Used to check the bundle manifest file for any package information.
532 	// This is used when '.' is on the Bundle-ClassPath to prevent reading
533 	// the bundle manifest for package information when loading classes.
hasPackageInfo(BundleFile bundleFile)534 	static boolean hasPackageInfo(BundleFile bundleFile) {
535 		if (bundleFile == null) {
536 			return false;
537 		}
538 		BundleEntry manifest = bundleFile.getEntry(OSGI_BUNDLE_MANIFEST);
539 		if (manifest == null) {
540 			return false;
541 		}
542 		BufferedReader br = null;
543 		try {
544 			br = new BufferedReader(new InputStreamReader(manifest.getInputStream()));
545 			String line;
546 			while ((line = br.readLine()) != null) {
547 				if (line.length() < 20)
548 					continue;
549 				switch (line.charAt(0)) {
550 					case 'S' :
551 						if (line.charAt(1) == 'p')
552 							if (line.startsWith("Specification-Title: ") || line.startsWith("Specification-Version: ") || line.startsWith("Specification-Vendor: ")) //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
553 								return true;
554 						break;
555 					case 'I' :
556 						if (line.startsWith("Implementation-Title: ") || line.startsWith("Implementation-Version: ") || line.startsWith("Implementation-Vendor: ")) //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
557 							return true;
558 						break;
559 				}
560 			}
561 		} catch (IOException ioe) {
562 			// do nothing
563 		} finally {
564 			if (br != null)
565 				try {
566 					br.close();
567 				} catch (IOException e) {
568 					// do nothing
569 				}
570 		}
571 		return false;
572 	}
573 
574 	static class CachedManifest extends Dictionary<String, String> implements Map<String, String> {
575 		private final Map<String, String> cached;
576 		private final Generation generation;
577 
CachedManifest(Generation generation, Map<String, String> cached)578 		CachedManifest(Generation generation, Map<String, String> cached) {
579 			this.generation = generation;
580 			this.cached = cached;
581 		}
582 
583 		@Override
elements()584 		public Enumeration<String> elements() {
585 			return Collections.enumeration(generation.getRawHeaders().values());
586 		}
587 
588 		@Override
get(Object key)589 		public String get(Object key) {
590 			if (cached.containsKey(key)) {
591 				return cached.get(key);
592 			}
593 			if (!cached.isEmpty() && generation.getBundleInfo().getStorage().getConfiguration().getDebug().DEBUG_CACHED_MANIFEST) {
594 				Debug.println("Header key is not cached: " + key + "; for bundle: " + generation.getBundleInfo().getBundleId()); //$NON-NLS-1$ //$NON-NLS-2$
595 			}
596 			return generation.getRawHeaders().get(key);
597 		}
598 
599 		@Override
isEmpty()600 		public boolean isEmpty() {
601 			return generation.getRawHeaders().isEmpty();
602 		}
603 
604 		@Override
keys()605 		public Enumeration<String> keys() {
606 			return Collections.enumeration(generation.getRawHeaders().keySet());
607 		}
608 
609 		@Override
put(String key, String value)610 		public String put(String key, String value) {
611 			return generation.getRawHeaders().put(key, value);
612 		}
613 
614 		@Override
remove(Object key)615 		public String remove(Object key) {
616 			return generation.getRawHeaders().remove(key);
617 		}
618 
619 		@Override
size()620 		public int size() {
621 			return generation.getRawHeaders().size();
622 		}
623 
624 		@Override
containsKey(Object key)625 		public boolean containsKey(Object key) {
626 			return cached.containsKey(key) || generation.getRawHeaders().containsKey(key);
627 		}
628 
629 		@Override
containsValue(Object value)630 		public boolean containsValue(Object value) {
631 			return cached.containsValue(value) || generation.getRawHeaders().containsValue(value);
632 		}
633 
634 		@Override
putAll(Map<? extends String, ? extends String> m)635 		public void putAll(Map<? extends String, ? extends String> m) {
636 			generation.getRawHeaders().putAll(m);
637 		}
638 
639 		@Override
clear()640 		public void clear() {
641 			generation.getRawHeaders().clear();
642 		}
643 
644 		@Override
keySet()645 		public Set<String> keySet() {
646 			return generation.getRawHeaders().keySet();
647 		}
648 
649 		@Override
values()650 		public Collection<String> values() {
651 			return generation.getRawHeaders().values();
652 		}
653 
654 		@Override
entrySet()655 		public Set<java.util.Map.Entry<String, String>> entrySet() {
656 			return generation.getRawHeaders().entrySet();
657 		}
658 	}
659 
660 }
661