Search in sources :

Example 26 with Application

use of javax.ws.rs.core.Application in project tomee by apache.

the class Contexts method bind.

/**
 * Using a set ensures we don't set the thread local twice or more,
 * there may be super classes with injection points of identical types
 *
 * Also allows us to get context references from other sources such as interceptors
 *
 * @param exchange Exchange
 * @param types    Collection
 */
public static void bind(final Exchange exchange, final Collection<Class<?>> types) {
    // used in lazy mode by RESTResourceFinder if cdi beans uses @Context, === initThreadLocal
    EXCHANGE.set(exchange);
    CdiAppContextsService.pushRequestReleasable(CleanUpThreadLocal.INSTANCE);
    for (final Class<?> type : types) {
        if (Request.class.equals(type)) {
            final Request binding = JAXRSUtils.createContextValue(exchange.getInMessage(), null, Request.class);
            ThreadLocalContextManager.REQUEST.set(binding);
        } else if (UriInfo.class.equals(type)) {
            final UriInfo binding = JAXRSUtils.createContextValue(exchange.getInMessage(), null, UriInfo.class);
            ThreadLocalContextManager.URI_INFO.set(binding);
        } else if (HttpHeaders.class.equals(type)) {
            final HttpHeaders binding = JAXRSUtils.createContextValue(exchange.getInMessage(), null, HttpHeaders.class);
            ThreadLocalContextManager.HTTP_HEADERS.set(binding);
        } else if (SecurityContext.class.equals(type)) {
            final SecurityContext binding = JAXRSUtils.createContextValue(exchange.getInMessage(), null, SecurityContext.class);
            ThreadLocalContextManager.SECURITY_CONTEXT.set(binding);
        } else if (ContextResolver.class.equals(type)) {
            final ContextResolver<?> binding = JAXRSUtils.createContextValue(exchange.getInMessage(), type, ContextResolver.class);
            ThreadLocalContextManager.CONTEXT_RESOLVER.set(binding);
        } else if (Providers.class.equals(type)) {
            final Providers providers = JAXRSUtils.createContextValue(exchange.getInMessage(), null, Providers.class);
            ThreadLocalContextManager.PROVIDERS.set(providers);
        } else if (ServletRequest.class.equals(type)) {
            ServletRequest servletRequest = JAXRSUtils.createContextValue(exchange.getInMessage(), null, ServletRequest.class);
            if (servletRequest == null) {
                // probably the case with CXF
                servletRequest = JAXRSUtils.createContextValue(exchange.getInMessage(), null, HttpServletRequest.class);
            }
            ThreadLocalContextManager.SERVLET_REQUEST.set(servletRequest);
        } else if (HttpServletRequest.class.equals(type)) {
            final HttpServletRequest httpServletRequest = JAXRSUtils.createContextValue(exchange.getInMessage(), null, HttpServletRequest.class);
            ThreadLocalContextManager.HTTP_SERVLET_REQUEST.set(httpServletRequest);
        } else if (HttpServletResponse.class.equals(type)) {
            final HttpServletResponse httpServletResponse = JAXRSUtils.createContextValue(exchange.getInMessage(), null, HttpServletResponse.class);
            ThreadLocalContextManager.HTTP_SERVLET_RESPONSE.set(httpServletResponse);
        } else if (ServletConfig.class.equals(type)) {
            final ServletConfig servletConfig = JAXRSUtils.createContextValue(exchange.getInMessage(), null, ServletConfig.class);
            ThreadLocalContextManager.SERVLET_CONFIG.set(servletConfig);
        } else if (Configuration.class.equals(type)) {
            final Configuration config = JAXRSUtils.createContextValue(exchange.getInMessage(), null, Configuration.class);
            ThreadLocalContextManager.CONFIGURATION.set(config);
        } else if (ResourceInfo.class.equals(type)) {
            final ResourceInfo config = JAXRSUtils.createContextValue(exchange.getInMessage(), null, ResourceInfo.class);
            ThreadLocalContextManager.RESOURCE_INFO.set(config);
        } else if (ResourceContext.class.equals(type)) {
            final ResourceContext config = JAXRSUtils.createContextValue(exchange.getInMessage(), null, ResourceContext.class);
            ThreadLocalContextManager.RESOURCE_CONTEXT.set(config);
        } else if (Application.class.equals(type)) {
            final Application config = JAXRSUtils.createContextValue(exchange.getInMessage(), null, Application.class);
            ThreadLocalContextManager.APPLICATION.set(config);
        } else {
            final Message message = exchange.getInMessage();
            final ContextProvider<?> provider = ProviderFactory.getInstance(message).createContextProvider(type, message);
            if (provider != null) {
                final Object value = provider.createContext(message);
                Map<String, Object> map = ThreadLocalContextManager.OTHERS.get();
                if (map == null) {
                    map = new HashMap<>();
                    ThreadLocalContextManager.OTHERS.set(map);
                }
                map.put(type.getName(), value);
            }
        }
    }
}
Also used : HttpHeaders(javax.ws.rs.core.HttpHeaders) HttpServletRequest(javax.servlet.http.HttpServletRequest) ServletRequest(javax.servlet.ServletRequest) OperationResourceInfo(org.apache.cxf.jaxrs.model.OperationResourceInfo) ResourceInfo(javax.ws.rs.container.ResourceInfo) ClassResourceInfo(org.apache.cxf.jaxrs.model.ClassResourceInfo) ResourceContext(javax.ws.rs.container.ResourceContext) Configuration(javax.ws.rs.core.Configuration) Message(org.apache.cxf.message.Message) HashMap(java.util.HashMap) HttpServletRequest(javax.servlet.http.HttpServletRequest) ServletRequest(javax.servlet.ServletRequest) Request(javax.ws.rs.core.Request) ServletConfig(javax.servlet.ServletConfig) HttpServletResponse(javax.servlet.http.HttpServletResponse) ContextProvider(org.apache.cxf.jaxrs.ext.ContextProvider) Providers(javax.ws.rs.ext.Providers) HttpServletRequest(javax.servlet.http.HttpServletRequest) SecurityContext(javax.ws.rs.core.SecurityContext) Application(javax.ws.rs.core.Application) HashMap(java.util.HashMap) Map(java.util.Map) UriInfo(javax.ws.rs.core.UriInfo)

Example 27 with Application

use of javax.ws.rs.core.Application in project tomee by apache.

the class RESTService method fullServletDeployment.

private void fullServletDeployment(final AppInfo appInfo, final WebAppInfo webApp, final WebContext webContext, final Map<String, EJBRestServiceInfo> restEjbs, final ClassLoader classLoader, final Collection<Injection> injections, final WebBeansContext owbCtx, final Context context, final Collection<Object> additionalProviders, final Collection<IdPropertiesInfo> initPojoConfigurations) {
    // The spec says:
    // 
    // "The resources and providers that make up a JAX-RS application are configured via an application-supplied
    // subclass of Application. An implementation MAY provide alternate mechanisms for locating resource
    // classes and providers (e.g. runtime class scanning) but use of Application is the only portable means of
    // configuration."
    // 
    // The choice here is to deploy using the Application if it exists or to use the scanned classes
    // if there is no Application.
    // 
    // Like this providing an Application subclass user can totally control deployed services.
    Collection<IdPropertiesInfo> pojoConfigurations = null;
    boolean useApp = false;
    String appPrefix = webApp.contextRoot;
    for (final String app : webApp.restApplications) {
        // if multiple application classes reinit it
        appPrefix = webApp.contextRoot;
        if (!appPrefix.endsWith("/")) {
            appPrefix += "/";
        }
        final Application appInstance;
        final Class<?> appClazz;
        try {
            appClazz = classLoader.loadClass(app);
            appInstance = Application.class.cast(appClazz.newInstance());
            if (owbCtx.getBeanManagerImpl().isInUse()) {
                try {
                    webContext.inject(appInstance);
                } catch (final Exception e) {
                // not important since not required by the spec
                }
            }
        } catch (final Exception e) {
            throw new OpenEJBRestRuntimeException("can't create class " + app, e);
        }
        final String path = appPrefix(webApp, appClazz);
        if (path != null) {
            appPrefix += path;
        }
        final Set<Class<?>> classes = appInstance.getClasses();
        final Set<Object> singletons = appInstance.getSingletons();
        // look for providers
        for (final Class<?> clazz : classes) {
            if (isProvider(clazz)) {
                additionalProviders.add(clazz);
            }
        }
        for (final Object obj : singletons) {
            if (obj != null && isProvider(obj.getClass())) {
                additionalProviders.add(obj);
            }
        }
        for (final Object o : singletons) {
            if (o == null || additionalProviders.contains(o)) {
                continue;
            }
            if (hasEjbAndIsNotAManagedBean(restEjbs, o.getClass().getName())) {
                // no more a singleton if the ejb is not a singleton...but it is a weird case
                deployEJB(appInfo.appId, webApp.contextRoot, appPrefix, restEjbs.get(o.getClass().getName()).context, additionalProviders, appInfo.services);
            } else {
                pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
                deploySingleton(appInfo.appId, webApp.contextRoot, appPrefix, o, appInstance, classLoader, additionalProviders, new ServiceConfiguration(PojoUtil.findConfiguration(pojoConfigurations, o.getClass().getName()), appInfo.services));
            }
        }
        for (final Class<?> clazz : classes) {
            if (additionalProviders.contains(clazz)) {
                continue;
            }
            if (hasEjbAndIsNotAManagedBean(restEjbs, clazz.getName())) {
                deployEJB(appInfo.appId, webApp.contextRoot, appPrefix, restEjbs.get(clazz.getName()).context, additionalProviders, appInfo.services);
            } else {
                pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
                deployPojo(appInfo.appId, webApp.contextRoot, appPrefix, clazz, appInstance, classLoader, injections, context, owbCtx, additionalProviders, new ServiceConfiguration(PojoUtil.findConfiguration(pojoConfigurations, clazz.getName()), appInfo.services));
            }
        }
        useApp = useApp || classes.size() + singletons.size() > 0;
        LOGGER.info("REST application deployed: " + app);
    }
    if (!useApp) {
        if (webApp.restApplications.isEmpty() || webApp.restApplications.size() > 1) {
            appPrefix = webApp.contextRoot;
        }
        // else keep application prefix
        final Set<String> restClasses = new HashSet<>(webApp.restClass);
        restClasses.addAll(webApp.ejbRestServices);
        for (final String clazz : restClasses) {
            if (restEjbs.containsKey(clazz)) {
                final BeanContext ctx = restEjbs.get(clazz).context;
                if (hasEjbAndIsNotAManagedBean(restEjbs, clazz)) {
                    deployEJB(appInfo.appId, webApp.contextRoot, appPrefix, restEjbs.get(clazz).context, additionalProviders, appInfo.services);
                } else {
                    deployPojo(appInfo.appId, webApp.contextRoot, appPrefix, ctx.getBeanClass(), null, ctx.getClassLoader(), ctx.getInjections(), context, owbCtx, additionalProviders, new ServiceConfiguration(ctx.getProperties(), appInfo.services));
                }
            } else {
                try {
                    final Class<?> loadedClazz = classLoader.loadClass(clazz);
                    pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
                    deployPojo(appInfo.appId, webApp.contextRoot, appPrefix, loadedClazz, null, classLoader, injections, context, owbCtx, additionalProviders, new ServiceConfiguration(PojoUtil.findConfiguration(pojoConfigurations, loadedClazz.getName()), appInfo.services));
                } catch (final ClassNotFoundException e) {
                    throw new OpenEJBRestRuntimeException("can't find class " + clazz, e);
                }
            }
        }
    }
}
Also used : URISyntaxException(java.net.URISyntaxException) UncheckedIOException(java.io.UncheckedIOException) ServiceException(org.apache.openejb.server.ServiceException) UnsupportedEncodingException(java.io.UnsupportedEncodingException) MalformedURLException(java.net.MalformedURLException) IOException(java.io.IOException) BeanContext(org.apache.openejb.BeanContext) ServiceConfiguration(org.apache.openejb.assembler.classic.util.ServiceConfiguration) IdPropertiesInfo(org.apache.openejb.assembler.classic.IdPropertiesInfo) MetaAnnotatedClass(org.apache.xbean.finder.MetaAnnotatedClass) Application(javax.ws.rs.core.Application) HashSet(java.util.HashSet)

Example 28 with Application

use of javax.ws.rs.core.Application in project tomee by apache.

the class RESTService method afterApplicationCreated.

/**
 * Deployment of JAX-RS services starts in response to a AfterApplicationCreated event
 * after normal deployment is done
 * @param appInfo the ear (real or auto-created) in which the webapp is contained
 * @param webApp the webapp containing EJB or Pojo rest services to deploy
 */
public void afterApplicationCreated(final AppInfo appInfo, final WebAppInfo webApp) {
    if ("false".equalsIgnoreCase(appInfo.properties.getProperty("openejb.jaxrs.on", "true"))) {
        return;
    }
    final WebContext webContext = containerSystem.getWebContextByHost(webApp.moduleId, webApp.host != null ? webApp.host : virtualHost);
    if (webContext == null) {
        return;
    }
    if (!deployedWebApps.add(webApp)) {
        return;
    }
    final Map<String, EJBRestServiceInfo> restEjbs = getRestEjbs(appInfo, webApp.moduleId);
    final ClassLoader classLoader = getClassLoader(webContext.getClassLoader());
    final Collection<Injection> injections = webContext.getInjections();
    final WebBeansContext owbCtx;
    if (webContext.getWebbeansContext() != null) {
        owbCtx = webContext.getWebbeansContext();
    } else {
        owbCtx = webContext.getAppContext().getWebBeansContext();
    }
    Context context = webContext.getJndiEnc();
    if (context == null) {
        // usually true since it is set in org.apache.tomee.catalina.TomcatWebAppBuilder.afterStart() and lookup(comp) fails
        context = webContext.getAppContext().getAppJndiContext();
    }
    final ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader(classLoader);
    final Collection<Object> additionalProviders = new HashSet<>();
    addAppProvidersIfNeeded(appInfo, webApp, classLoader, additionalProviders);
    // done lazily
    Collection<IdPropertiesInfo> pojoConfigurations = null;
    try {
        boolean deploymentWithApplication = "true".equalsIgnoreCase(appInfo.properties.getProperty(OPENEJB_USE_APPLICATION_PROPERTY, APPLICATION_DEPLOYMENT));
        if (deploymentWithApplication) {
            Class<?> appClazz;
            for (final String app : webApp.restApplications) {
                Application application;
                boolean appSkipped = false;
                String prefix = "/";
                try {
                    appClazz = classLoader.loadClass(app);
                    application = Application.class.cast(appClazz.newInstance());
                    if (owbCtx != null && owbCtx.getBeanManagerImpl().isInUse()) {
                        try {
                            webContext.inject(application);
                        } catch (final Exception e) {
                        // not important since not required by the spec
                        }
                    }
                } catch (final Exception e) {
                    throw new OpenEJBRestRuntimeException("can't create class " + app, e);
                }
                application = wrapApplication(appInfo, application);
                final Set<Class<?>> classes = new HashSet<>(application.getClasses());
                final Set<Object> singletons = application.getSingletons();
                if (classes.size() + singletons.size() == 0) {
                    appSkipped = true;
                } else {
                    for (final Class<?> clazz : classes) {
                        if (isProvider(clazz)) {
                            additionalProviders.add(clazz);
                        } else if (!hasEjbAndIsNotAManagedBean(restEjbs, clazz.getName())) {
                            pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
                            if (PojoUtil.findConfiguration(pojoConfigurations, clazz.getName()) != null) {
                                deploymentWithApplication = false;
                                logOldDeploymentUsage(clazz.getName());
                            }
                        }
                    }
                    if (deploymentWithApplication) {
                        // don't do it if we detected we should use old deployment
                        for (final Object o : singletons) {
                            final Class<?> clazz = o.getClass();
                            if (isProvider(clazz)) {
                                additionalProviders.add(o);
                            } else if (!hasEjbAndIsNotAManagedBean(restEjbs, clazz.getName())) {
                                pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
                                if (PojoUtil.findConfiguration(pojoConfigurations, clazz.getName()) != null) {
                                    deploymentWithApplication = false;
                                    logOldDeploymentUsage(clazz.getName());
                                }
                            }
                        }
                    }
                }
                if (deploymentWithApplication) {
                    // don't do it if we detected we should use old deployment
                    final String path = appPrefix(webApp, appClazz);
                    if (path != null) {
                        prefix += path + wildcard;
                    } else {
                        prefix += wildcard;
                    }
                }
                if (deploymentWithApplication) {
                    // don't do it if we detected we should use old deployment
                    if (appSkipped || application == null) {
                        application = !InternalApplication.class.isInstance(application) ? new InternalApplication(application) : application;
                        for (final String clazz : webApp.restClass) {
                            try {
                                final Class<?> loaded = classLoader.loadClass(clazz);
                                if (!isProvider(loaded)) {
                                    pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
                                    if (PojoUtil.findConfiguration(pojoConfigurations, loaded.getName()) != null) {
                                        deploymentWithApplication = false;
                                        logOldDeploymentUsage(loaded.getName());
                                        break;
                                    }
                                    application.getClasses().add(loaded);
                                } else {
                                    additionalProviders.add(loaded);
                                }
                            } catch (final Exception e) {
                                throw new OpenEJBRestRuntimeException("can't load class " + clazz, e);
                            }
                        }
                        if (deploymentWithApplication) {
                            addEjbToApplication(application, restEjbs);
                            if (!prefix.endsWith(wildcard)) {
                                prefix += wildcard;
                            }
                        }
                    }
                    if (!application.getClasses().isEmpty() || !application.getSingletons().isEmpty()) {
                        pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
                        deployApplication(appInfo, webApp.contextRoot, restEjbs, classLoader, injections, owbCtx, context, additionalProviders, pojoConfigurations, application, prefix);
                    }
                }
                if (!deploymentWithApplication) {
                    fullServletDeployment(appInfo, webApp, webContext, restEjbs, classLoader, injections, owbCtx, context, additionalProviders, pojoConfigurations);
                }
            }
            if (webApp.restApplications.isEmpty()) {
                final Application application = new InternalApplication(null);
                for (final String clazz : webApp.restClass) {
                    try {
                        final Class<?> loaded = classLoader.loadClass(clazz);
                        if (!isProvider(loaded)) {
                            pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
                            if (PojoUtil.findConfiguration(pojoConfigurations, loaded.getName()) != null) {
                                deploymentWithApplication = false;
                                logOldDeploymentUsage(loaded.getName());
                                break;
                            }
                            application.getClasses().add(loaded);
                        } else {
                            additionalProviders.add(loaded);
                        }
                    } catch (final Exception e) {
                        throw new OpenEJBRestRuntimeException("can't load class " + clazz, e);
                    }
                }
                addEjbToApplication(application, restEjbs);
                if (deploymentWithApplication) {
                    if (!application.getClasses().isEmpty() || !application.getSingletons().isEmpty()) {
                        final String path = appPrefix(webApp, application.getClass());
                        final String prefix;
                        if (path != null) {
                            prefix = "/" + path + wildcard;
                        } else {
                            prefix = "/" + wildcard;
                        }
                        pojoConfigurations = PojoUtil.findPojoConfig(pojoConfigurations, appInfo, webApp);
                        deployApplication(appInfo, webApp.contextRoot, restEjbs, classLoader, injections, owbCtx, context, additionalProviders, pojoConfigurations, application, prefix);
                    }
                } else {
                    fullServletDeployment(appInfo, webApp, webContext, restEjbs, classLoader, injections, owbCtx, context, additionalProviders, pojoConfigurations);
                }
            }
        } else {
            fullServletDeployment(appInfo, webApp, webContext, restEjbs, classLoader, injections, owbCtx, context, additionalProviders, pojoConfigurations);
        }
    } finally {
        Thread.currentThread().setContextClassLoader(oldLoader);
    }
}
Also used : WebContext(org.apache.openejb.core.WebContext) WebBeansContext(org.apache.webbeans.config.WebBeansContext) AppContext(org.apache.openejb.AppContext) BeanContext(org.apache.openejb.BeanContext) Context(javax.naming.Context) WebContext(org.apache.openejb.core.WebContext) Injection(org.apache.openejb.Injection) URISyntaxException(java.net.URISyntaxException) UncheckedIOException(java.io.UncheckedIOException) ServiceException(org.apache.openejb.server.ServiceException) UnsupportedEncodingException(java.io.UnsupportedEncodingException) MalformedURLException(java.net.MalformedURLException) IOException(java.io.IOException) WebBeansContext(org.apache.webbeans.config.WebBeansContext) IdPropertiesInfo(org.apache.openejb.assembler.classic.IdPropertiesInfo) MetaAnnotatedClass(org.apache.xbean.finder.MetaAnnotatedClass) Application(javax.ws.rs.core.Application) HashSet(java.util.HashSet)

Example 29 with Application

use of javax.ws.rs.core.Application in project swagger-core by swagger-api.

the class JaxrsApplicationScannerTest method setUp.

@BeforeMethod
public void setUp() {
    scanner = new JaxrsApplicationScanner();
    scanner.setApplication(new Application() {

        @Override
        public Set<Class<?>> getClasses() {
            return Collections.singleton(ResourceInPackageA.class);
        }
    });
}
Also used : Set(java.util.Set) Application(javax.ws.rs.core.Application) ResourceInPackageA(com.my.project.resources.ResourceInPackageA) BeforeMethod(org.testng.annotations.BeforeMethod)

Example 30 with Application

use of javax.ws.rs.core.Application in project swagger-core by swagger-api.

the class JaxrsApplicationAndResourcePackagesAnnotationScannerTest method shouldScanOnlyApplicationClasses.

@Test(description = "scan classes from Application")
public void shouldScanOnlyApplicationClasses() throws Exception {
    SwaggerConfiguration config = new SwaggerConfiguration().openAPI(new OpenAPI().info(new Info().description("TEST INFO DESC")));
    Application application = new Application() {

        @Override
        public Set<Class<?>> getClasses() {
            return Collections.singleton(ResourceInPackageA.class);
        }
    };
    OpenApiContext ctx = new GenericOpenApiContext<>().openApiConfiguration(config).openApiReader(new Reader(config)).openApiScanner(scanner.application(application).openApiConfiguration(config)).init();
    OpenAPI openApi = ctx.read();
    assertNotNull(openApi);
    assertEquals(openApi.getPaths().keySet(), Arrays.asList("/packageA"));
}
Also used : Reader(io.swagger.v3.jaxrs2.Reader) GenericOpenApiContext(io.swagger.v3.oas.integration.GenericOpenApiContext) Info(io.swagger.v3.oas.models.info.Info) OpenAPI(io.swagger.v3.oas.models.OpenAPI) Application(javax.ws.rs.core.Application) SwaggerConfiguration(io.swagger.v3.oas.integration.SwaggerConfiguration) GenericOpenApiContext(io.swagger.v3.oas.integration.GenericOpenApiContext) OpenApiContext(io.swagger.v3.oas.integration.api.OpenApiContext) Test(org.testng.annotations.Test)

Aggregations

Application (javax.ws.rs.core.Application)63 HashSet (java.util.HashSet)14 HashMap (java.util.HashMap)12 ApplicationInfo (org.apache.cxf.jaxrs.model.ApplicationInfo)12 JAXRSServerFactoryBean (org.apache.cxf.jaxrs.JAXRSServerFactoryBean)9 Test (org.junit.Test)9 ArrayList (java.util.ArrayList)8 IOException (java.io.IOException)7 Set (java.util.Set)7 ServletException (javax.servlet.ServletException)7 Annotation (java.lang.annotation.Annotation)5 List (java.util.List)5 Map (java.util.Map)5 ApplicationPath (javax.ws.rs.ApplicationPath)4 WebApplicationException (javax.ws.rs.WebApplicationException)4 ClassResourceInfo (org.apache.cxf.jaxrs.model.ClassResourceInfo)4 DeploymentInfo (io.undertow.servlet.api.DeploymentInfo)3 Method (java.lang.reflect.Method)3 Type (java.lang.reflect.Type)3 Collection (java.util.Collection)3