1b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin/**
2b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * Copyright (C) 2011 Google Inc.
3b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin *
4b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * Licensed under the Apache License, Version 2.0 (the "License");
5b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * you may not use this file except in compliance with the License.
6b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * You may obtain a copy of the License at
7b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin *
8b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * http://www.apache.org/licenses/LICENSE-2.0
9b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin *
10b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * Unless required by applicable law or agreed to in writing, software
11b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * distributed under the License is distributed on an "AS IS" BASIS,
12b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * See the License for the specific language governing permissions and
14b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * limitations under the License.
15b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin */
16b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
17b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinpackage com.google.inject.servlet;
18b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
19b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport static com.google.inject.name.Names.named;
20b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport static java.lang.annotation.RetentionPolicy.RUNTIME;
21b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
22b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.common.collect.ImmutableMap;
23b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.AbstractModule;
24b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.Binding;
25b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.Guice;
26b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.Injector;
27b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.Key;
28b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.Module;
29b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.PrivateModule;
30b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.Provides;
31b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.ScopeAnnotation;
32b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.Scopes;
33b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.Singleton;
34b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.name.Named;
35b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.spi.Element;
36b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.spi.Elements;
37b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.spi.PrivateElements;
38b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport com.google.inject.util.Providers;
39b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
40b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport junit.framework.TestCase;
41b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
42b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport java.lang.annotation.ElementType;
43b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport java.lang.annotation.Retention;
44b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport java.lang.annotation.Target;
45b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport java.util.List;
46b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinimport java.util.Map;
47b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
48b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin/**
49b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * Tests for {@link ServletScopes}.
50b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin *
51b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin * @author forster@google.com (Mike Forster)
52b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin */
53b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlinpublic class ServletScopesTest extends TestCase {
54b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  public void testIsRequestScopedPositive() {
55b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> a = Key.get(String.class, named("A"));
56b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> b = Key.get(String.class, named("B"));
57b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> c = Key.get(String.class, named("C"));
58b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> d = Key.get(String.class, named("D"));
59b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<Object> e = Key.get(Object.class, named("E"));
60b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> f = Key.get(String.class, named("F"));
61b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> g = Key.get(String.class, named("G"));
62b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
63b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    Module requestScopedBindings = new AbstractModule() {
64b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      @Override
65b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      protected void configure() {
66b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(a).to(b);
67b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(b).to(c);
68b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(c).toProvider(Providers.of("c")).in(ServletScopes.REQUEST);
69b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(d).toProvider(Providers.of("d")).in(RequestScoped.class);
70b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(e).to(AnnotatedRequestScopedClass.class);
71b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        install(new PrivateModule() {
72b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin          @Override
73b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin          protected void configure() {
74b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin            bind(f).toProvider(Providers.of("f")).in(RequestScoped.class);
75b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin            expose(f);
76b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin          }
77b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        });
78b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      }
79b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
80b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      @Provides
81b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      @Named("G")
82b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      @RequestScoped
83b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      String provideG() {
84b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        return "g";
85b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      }
86b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    };
87b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
88b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    @SuppressWarnings("unchecked") // we know the module contains only bindings
89b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    List<Element> moduleBindings = Elements.getElements(requestScopedBindings);
90b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    ImmutableMap<Key<?>, Binding<?>> map = indexBindings(moduleBindings);
91b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    // linked bindings are not followed by modules
92b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(a)));
93b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(b)));
94b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertTrue(ServletScopes.isRequestScoped(map.get(c)));
95b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertTrue(ServletScopes.isRequestScoped(map.get(d)));
96b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    // annotated classes are not followed by modules
97b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(e)));
98b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertTrue(ServletScopes.isRequestScoped(map.get(f)));
99b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertTrue(ServletScopes.isRequestScoped(map.get(g)));
100b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
101b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    Injector injector = Guice.createInjector(requestScopedBindings, new ServletModule());
102b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(a)));
103b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(b)));
104b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(c)));
105b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(d)));
106b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(e)));
107b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(f)));
108b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertTrue(ServletScopes.isRequestScoped(injector.getBinding(g)));
109b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  }
110b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
111b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  public void testIsRequestScopedNegative() {
112b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> a = Key.get(String.class, named("A"));
113b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> b = Key.get(String.class, named("B"));
114b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> c = Key.get(String.class, named("C"));
115b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> d = Key.get(String.class, named("D"));
116b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> e = Key.get(String.class, named("E"));
117b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> f = Key.get(String.class, named("F"));
118b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> g = Key.get(String.class, named("G"));
119b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> h = Key.get(String.class, named("H"));
120b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> i = Key.get(String.class, named("I"));
121b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    final Key<String> j = Key.get(String.class, named("J"));
122b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
123b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    Module requestScopedBindings = new AbstractModule() {
124b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      @Override
125b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      protected void configure() {
126b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(a).to(b);
127b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(b).to(c);
128b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(c).toProvider(Providers.of("c")).in(Scopes.NO_SCOPE);
129b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(d).toInstance("d");
130b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(e).toProvider(Providers.of("e")).asEagerSingleton();
131b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(f).toProvider(Providers.of("f")).in(Scopes.SINGLETON);
132b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(g).toProvider(Providers.of("g")).in(Singleton.class);
133b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bind(h).toProvider(Providers.of("h")).in(CustomScoped.class);
134b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        bindScope(CustomScoped.class, Scopes.NO_SCOPE);
135b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        install(new PrivateModule() {
136b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin          @Override
137b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin          protected void configure() {
138b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin            bind(i).toProvider(Providers.of("i")).in(CustomScoped.class);
139b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin            expose(i);
140b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin          }
141b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        });
142b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      }
143b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
144b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      @Provides
145b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      @Named("J")
146b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      @CustomScoped
147b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      String provideJ() {
148b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        return "j";
149b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      }
150b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    };
151b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
152b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    @SuppressWarnings("unchecked") // we know the module contains only bindings
153b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    List<Element> moduleBindings = Elements.getElements(requestScopedBindings);
154b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    ImmutableMap<Key<?>, Binding<?>> map = indexBindings(moduleBindings);
155b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(a)));
156b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(b)));
157b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(c)));
158b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(d)));
159b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(e)));
160b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(f)));
161b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(g)));
162b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(h)));
163b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(i)));
164b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(map.get(j)));
165b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
166b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    Injector injector = Guice.createInjector(requestScopedBindings);
167b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(a)));
168b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(b)));
169b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(c)));
170b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(d)));
171b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(e)));
172b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(f)));
173b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(g)));
174b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(h)));
175b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(i)));
176b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    assertFalse(ServletScopes.isRequestScoped(injector.getBinding(j)));
177b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  }
178b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
179b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  @RequestScoped
180b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  static class AnnotatedRequestScopedClass {}
181b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
182b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  @Target({ ElementType.TYPE, ElementType.METHOD })
183b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  @Retention(RUNTIME)
184b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  @ScopeAnnotation
185b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  private @interface CustomScoped {}
186b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin
187b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  private ImmutableMap<Key<?>, Binding<?>> indexBindings(Iterable<Element> elements) {
188b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    ImmutableMap.Builder<Key<?>, Binding<?>> builder = ImmutableMap.builder();
189b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    for (Element element : elements) {
190b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      if (element instanceof Binding) {
191b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        Binding<?> binding = (Binding<?>) element;
192b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        builder.put(binding.getKey(), binding);
193b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      } else if (element instanceof PrivateElements) {
194b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        PrivateElements privateElements = (PrivateElements) element;
195b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        Map<Key<?>, Binding<?>> privateBindings = indexBindings(privateElements.getElements());
196b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        for (Key<?> exposed : privateElements.getExposedKeys()) {
197b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin          builder.put(exposed, privateBindings.get(exposed));
198b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin        }
199b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin      }
200b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    }
201b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin    return builder.build();
202b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin  }
203b2f558228f0b812a38a0c2407baf5826b452cb2cSam Berlin}
204