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

Reply via email to