On Sun, Dec 01, 2024 at 06:44:19PM -0500, [email protected]
wrote:
> On Wed, Nov 27, 2024 at 12:19:02PM -0500, [email protected]
> wrote:
> > On Wed, Nov 27, 2024 at 11:18:45AM -0500,
> > [email protected] 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