On Sun, Dec 01, 2024 at 06:44:19PM -0500, apache-karaf-...@pyr3x.com.INVALID wrote: > On Wed, Nov 27, 2024 at 12:19:02PM -0500, apache-karaf-...@pyr3x.com.INVALID > wrote: > > On Wed, Nov 27, 2024 at 11:18:45AM -0500, > > apache-karaf-...@pyr3x.com.INVALID wrote: > > > Hello, > > > > > > There seems to be an issue with the Jackson JSON processing. > > > > > > From a clean karaf 4.4.6 install I do: > > > > > > feature:install scr > > > feature:repo-add aries.jax.rs.whiteboard > > > feature:repo-add cxf > > > feature:install aries-jax-rs-whiteboard-jackson > > > > > > I then deploy a simple bundle: > > > > > > @Component(service = TestResource.class, scope = ServiceScope.PROTOTYPE) > > > @JaxrsResource > > > //@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + > > > "=" + MyApplication.NAME + ")") > > > public class TestResource { > > > > > > @GET > > > @Path("hello") > > > @Produces(MediaType.APPLICATION_JSON) > > > public Woot sayHello(){ > > > return new Woot(); > > > } > > > > > > @GET > > > @Path("hello2") > > > @Produces(MediaType.TEXT_PLAIN) > > > public String sayHello2(){ > > > return "test\n"; > > > } > > > } > > > > > > curl localhost:8181/hello2 returns 'test' > > > > > > the second curl should return the json of Woot which is just a simple > > > class with a getName(), however, the first invocation will return the > > > following. The second invocation does what is expected. > > > > > > Caused by: java.lang.ClassNotFoundException: > > > com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector not found by > > > com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider [62] > > > at > > > org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1591) > > > ~[?:?] > > > at > > > org.apache.felix.framework.BundleWiringImpl.access$300(BundleWiringImpl.java:79) > > > ~[?:?] > > > at > > > org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:1976) > > > ~[?:?] > > > at java.lang.ClassLoader.loadClass(ClassLoader.java:525) ~[?:?] > > > at > > > com.fasterxml.jackson.jaxrs.json.JsonMapperConfigurator._resolveIntrospector(JsonMapperConfigurator.java:126) > > > ~[?:?] > > > at > > > com.fasterxml.jackson.jaxrs.json.JsonMapperConfigurator._resolveIntrospectors(JsonMapperConfigurator.java:101) > > > ~[?:?] > > > at > > > com.fasterxml.jackson.jaxrs.cfg.MapperConfiguratorBase._setAnnotations(MapperConfiguratorBase.java:120) > > > ~[?:?] > > > at > > > com.fasterxml.jackson.jaxrs.json.JsonMapperConfigurator.getDefaultMapper(JsonMapperConfigurator.java:51) > > > ~[?:?] > > > at > > > com.fasterxml.jackson.jaxrs.base.ProviderBase.locateMapper(ProviderBase.java:925) > > > ~[?:?] > > > at > > > com.fasterxml.jackson.jaxrs.base.ProviderBase._endpointForWriting(ProviderBase.java:697) > > > ~[?:?] > > > at > > > com.fasterxml.jackson.jaxrs.base.ProviderBase.writeTo(ProviderBase.java:572) > > > ~[?:?] > > > at > > > org.apache.cxf.jaxrs.utils.JAXRSUtils.writeMessageBody(JAXRSUtils.java:1651) > > > ~[!/:3.6.4] > > > at > > > org.apache.cxf.jaxrs.interceptor.JAXRSOutInterceptor.serializeMessage(JAXRSOutInterceptor.java:249) > > > ~[!/:3.6.4] > > > > > > This assumes using the default whiteboard. If I create an application > > > and set the following: > > > > > > @Component(service = Application.class, property = { > > > "servlet.init.hide-service-list-page=false" }) > > > @JaxrsName(MyApplication.NAME) > > > @JaxrsApplicationBase("/test") > > > public class MyApplication extends Application { > > > > > > public static final String NAME = "my-app"; > > > } > > > > > > And then I uncomment the JaxRsApplicationSelect, it will report there > > > are no message body writers at all. I had assumed that bringing in the > > > aries-jax-rs-whiteboard-jackson feature would register the provider to > > > all contexts. It seems only to work with the default context but only > > > after the first invocation fails. > > > > > > A side note when deploying multiple applications I have to set the > > > > > > org.apache.cxf.osgi.http.transport.disable=true > > > > > > This is neither here nor there I just found it on a forum. I think there > > > should be more examples. > > > > > > I have also noticed that bringing in that aries whiteboard jackson > > > relies on cxf-jaxrs which brings in different versions of the jackson > > > libraries. Perhaps this should be removed. > > > > > > Any ideas how to resolve this for the case of the default whiteboard and > > > registering the provider when not deployed to the default using > > > Application? > > > > > > -- > > > Chaz > > > > Upon further testing, if I had a @XmlRootElement to the Woot class, I no > > longer have an issue with finding the message body writer when deploying > > using an Application, however, using the default whiteboard still > > produces that issue of having to invoke it once first. > > > > -- > > Chaz > > In order to get Application to work correctly with JSON I had to: > > @Override > public Set<Class<?>> getClasses() { > return Set.of(JacksonJaxbJsonProvider.class); > } > > I've also had to remove the @JSONRequired annotation from the Resource. > This _should_ work from what I've looked at: > > [javax.ws.rs.ext.MessageBodyReader, javax.ws.rs.ext.MessageBodyWriter] > ---------------------------------------------------------------------- > jackson.jaxb.version = 2.17.1 > jackson.jaxrs.json.version = 2.17.1 > service.id = 264 > osgi.jaxrs.extension = true > service.bundleid = 68 > service.scope = prototype > *** osgi.jaxrs.media.type = application/json > osgi.jaxrs.name = jaxb-json > service.ranking = -2147483648 > Provided by : > Apache Aries JAX-RS Jackson JAXB-JSON (68) > Used by: > Apache Aries JAX-RS Whiteboard (69) > > Installing the feature aries jaxrs jackson feature installs this and > that has the media type property set that should be picked up by the > @JSONRequired, however, it does not appear to do this. I've also tried > doing the @JaxrsExtensionSelect("(osgi.jaxrs.name=jaxb-json)") on my > Resource but this didn't work either. > > -- > Chaz
All, For anyone interested, I was able to resolve this issue using configadmin. I am now able to use the @JSONRequired annotations as expected. I used the configurator method deployed with my bundle to configure the Aries JAX-RS integrations for Jackson and Shiro. The shiro integration took a while to understand by reading the tests as it was undocumented. I also implemented a JaasRealm and registered it to make it possible for shiro to authentication/authorize based on the default karaf JAAS realm. I'm not sure this is the best implementation as I use a static map to hold the authorization information from the subject and look it up, but it's working for now. { ":configurator:resource-version" : 1, "org.apache.aries.jax.rs.jackson": { "osgi.jaxrs.application.select": "(osgi.jaxrs.name=api)" }, "org.apache.aries.jax.rs.shiro.authentication": { "osgi.jaxrs.application.select": "(osgi.jaxrs.name=api)" }, "org.apache.aries.jax.rs.shiro.authorization": { "osgi.jaxrs.application.select": "(osgi.jaxrs.name=api)" } } @ObjectClassDefinition( localization = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME, name = "%configuration.name", description = "%configuration.description") @interface Configuration { @AttributeDefinition(description = "%configuration.realm") String realm() default "karaf"; } /** * TODO. */ @Component(immediate = true, service = Realm.class) @Designate(ocd = Configuration.class) @Slf4j public class JaasRealm extends AuthorizingRealm { /** * {@link Map} containing authenticated usernames and their roles. */ protected static final Map<String, Set<String>> NAME_TO_ROLES_MAP = new ConcurrentHashMap<>(); /** * Localization for bundle, configuration, log, and other strings. */ protected transient ResourceBundle rbundle = ResourceBundleHelper.getOsgiBundleResourceBundle(JaasRealm.class); /** * JAAS realm to authenticate/authorize users with. */ protected String realm; /** * DS method to activate the component. * * @param configuration {@link Configuration} for this component. * @throws Exception If there was an error activating this component. */ @Activate protected void activate(final Configuration configuration) throws Exception { if (log.isInfoEnabled()) { log.info(rbundle.getString("log.starting")); } init(configuration); } /** * DS method to deactivate the component. * * @param configuration {@link Configuration} for this component. * @throws Exception If there was an error deactivating this component. */ @Deactivate protected void deactivate(final Configuration configuration) throws Exception { if (log.isInfoEnabled()) { log.info(rbundle.getString("log.stopping")); } } /** * DS method to modify the component. * * @param configuration New {@link Configuration} for this component. * @throws Exception If there was an error modifying this component. */ @Modified protected void modified(final Configuration configuration) throws Exception { if (log.isInfoEnabled()) { log.info(rbundle.getString("log.changing_configuration")); } init(configuration); } @Override protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token) { final String username = (String) token.getPrincipal(); final String password = new String((char[]) token.getCredentials()); final Subject subject = new Subject(); final UserPassCallbackHandler handler = new UserPassCallbackHandler(username, password); try { final LoginContext loginContext = new LoginContext(realm, subject, handler); loginContext.login(); NAME_TO_ROLES_MAP.put(username, subject.getPrincipals() .stream() .map(Principal::getName) .collect(Collectors.toSet())); return new SimpleAuthenticationInfo(username, password, getName()); } catch (LoginException ex) { throw new AuthenticationException(rbundle.getString("log.auth_failed") + username, ex); } } @Override protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) { final String username = (String) principals.getPrimaryPrincipal(); return new SimpleAuthorizationInfo(NAME_TO_ROLES_MAP.get(username)); } /** * Helper to initialize configuration of service. * * @param configuration {@link Configuration} for this component. */ protected void init(final Configuration configuration) { realm = configuration.realm(); } -- Chaz