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