Is it possible to use jpa repository inside a service anotated with commetd @service anotation?

40 views
Skip to first unread message

Yosvany Hurtado

unread,
Aug 6, 2020, 12:44:08 PM8/6/20
to cometd-users
Hi guys, just landing here because I started a POC project with cometD using the spring boot example as starting point.

The example works ok, as expected.
Because i want to persist some messages in database for future analysis, I create other two clases:
- A simple jpa repository interface
  @Repository
public interface DBRepository extends JpaRepository<TestDAO, Long> {
}
- A simple Dao 
@Entity
public class TestDAO {

@Id
@GeneratedValue
private Long id;
....
}

And added some code to the ChatService class:

@Service("chat")
public class ChatService {
private final ConcurrentMap<String, Map<String, String>> _members = new ConcurrentHashMap<>();

private final static Logger logger = LoggerFactory.getLogger(ChatService.class);

@Inject
private BayeuxServer _bayeux;

@Session
private ServerSession _session;

@Autowired
private DBRepository repository;

@Configure("/service/demo")
protected void configureServiceDemo(ConfigurableServerChannel channel) {
logger.debug("configure: /service/demo");
channel.setPersistent(true);
channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH);

}

@Listener("/chat/demo")
public void handleChatDemo(ServerSession client, ServerMessage message) {
logger.info("Listener service/demo .. " );
TestDAO dao = new TestDAO();
dao.setMessage("test");
logger.debug("saving ....");
this.repository.save(dao);  <-- fails with nullpointer, repository never gets injected

}

....
}

I suppose because this service isn't managed by sprint boot, the framework never inject the repository and I wander how can I get that repository working on this cometd service.

Or may be i'm doing things wrong and it's not how it is supose to work.... in any case guys, can anyone give me a hand? 

This is just the kickoff that i need to start with the project.

There is a repo in github with the code: https://github.com/yosvany/demo-cometd.git

I'm sorry for my english.

Cheers!


Glenn Thompson

unread,
Aug 6, 2020, 4:11:09 PM8/6/20
to cometd...@googlegroups.com
Hi Yosvany,

Just another CometD user here. But the app I'm working on is very heavy on Spring and Spring Boot.  As far as I have found there is no automatic Spring Bean to CometD @Service Injection.  You have to write a bit of glue.  Here is how I do it:  I'm including way more than you strictly need. I can't guarantee that there isn't a much better approach.  I've meant to post my approach for some time to find out.  But I kept telling myself I need to build an example that I can share.  My app can not be shared.  And frankly it's too big to be useful. So I cut and pasted and edited parts of my app.

I need to give back to this community so I'll wing it for you. Project committers, if I am giving bad advice, please say so.

Here is how I bridge between the SpringBoot/Spring Bean World and the CometD @Service world. Again, you may not need all of what I'm doing but I have extensions that need access to the Bayeux Server.   So In essence I have Spring Beans that need to know about the BayeuxServerImpl underneath and I need the Bayeux server involved in the injection of Spring Beans into Cometd @Service beans. The @Service stereotype exists in both worlds so I'm careful to be specific.

Hope this isn't too much too fast:-) But here we go . . .

I have a boot application called CometDHubApplcation:

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
@EnableWebSecurity
public class CometDHubApplication implements ServletContextInitializer {
    private static Logger log = LoggerFactory.getLogger(CometDHubApplication.class);

    // This get built under Spring control

    @Autowired
    BayeuxServerImpl bayeux;

    /**
     * To run from Maven:
     * mvn spring-boot:run -Dspring-boot.run.profiles=local
     *
     * @param args
     */
    public static void main(String[] args) {
        log.info("Running {}", CometDHubApplication.class.getName());
        SpringApplication.run(CometDHubApplication.class, args);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.springframework.boot.web.servlet.ServletContextInitializer#onStartup(
     * javax.servlet.ServletContext)
     */
    @Override
    public void onStartup(ServletContext servletContext) {
        /*
         * This cross referencing between bayeux servlet and the servletContext
         * are imperative. Don't remove.
         *
         * NOTE that it occurs after the bayeux is constructed. So bayeux is not
         * fully initialized or running yet.
         */

        /*
         * ServletContext meet Bayeux. Bayeux meet servletContext.
         */
        servletContext.setAttribute(BayeuxServer.ATTRIBUTE, bayeux);

        /*
         * This is required in order to locate Cometd Annotated "@Service"
         * service instances. These are not the Spring Service stereotypes.
         *
         */
        bayeux.setOption(ServletContext.class.getName(), servletContext);
    }
}

My application is quite involved so again I'm only including the important parts: My primary Boot configuration file is CometDHubConfiguration.  I layer my config so there are others.

 @Configuration
/*
 * TODO Need to test scan of service to see if it has value. For now using
 * Cometd scanning.
 */
@ComponentScan({"things to scan"})
public class CometDHubConfiguration {
    private static Logger log = LoggerFactory.getLogger(CometDHubConfiguration.class);

    @Autowired
    /* Used for getting secrets only */
    private Environment env;

    @Value("${ws.cometdURLMapping:/cometd/*}")
    private String bayeuxMapping;

    // Some of our internal stuff goes here.
    . . . 

    /**
     * Factory for Jetty.
     *
     * @return
     */
    @Bean
    public ConfigurableServletWebServerFactory webServerFactory() {
        JettyServletWebServerFactory servletFactory = new JettyServletWebServerFactory();
        log.info("Returning JettyServletWebServerFactory");
        return servletFactory;
    }

    /**
     * Cometd servlet registration and configuration
     *
     * Note that this servlet should always be loaded first.
     *
     * @return
     */
    @Bean(name = "cometd")
    public ServletRegistrationBean<HttpServlet> cometdServletReg() {
        ServletRegistrationBean<HttpServlet> regBean = new ServletRegistrationBean<>(annotationCometDServlet(),
                bayeuxMapping);

        /*
         * This is somehow defaulted but I felt it best to be explicit. As we
         * use it to locate the MyApp cometd servlet.
         */
        regBean.setName("MyAppAnnotationCometDServlet");

        /*
         * TODO *Still* bothered by the fact I have to list Services here. This
         * technique is used in all the examples I saw. So MyApp
         * routing capability now counts on it as well. so be aware of that
         * should you find a better way.
         * 
         * Hope this doesn confuse but I left this here to help understand why I was doing things the way I did.
         * 
         * To use the MyApp framework in a more general way, the
         * "services" (essentially specifically channel configuration )
         * need to be separate from the hub. Yet they are dependent on the hub
         * or at least the special "routing and auth" behaviors implemented
         * there. Even if there is another artifact between them. So by
         * definition we can't reference the services classes directly in
         * the lib module. So we make the hub a straight jar and have a boot app
         * with the services such that all the references can be resolved later.
         *  
         * I haven't included real stuff her but I hope the names help you understand.
         * This init parameter is used in MyAppAnnotationCometDServlet below that extends CometD's annotation servlet.
         */
        regBean.addInitParameter(MyAppCometdConstants.COMETD_SERVICES_INIT_PARAMETER_KEY,
                HelloService.class.getName() + ", " + ChatService.class.getName() + ", " + EchoRPC.class.getName()
                        + ", " + Monitor.class.getName() + ", " + MyAppServiceOne.class.getName());
        regBean.addInitParameter("ws.cometdURLMapping", bayeuxMapping);
        regBean.setAsyncSupported(true);
        regBean.setLoadOnStartup(1);
        return regBean;
    }

    /**
     *
     * MyAppAnnotationCometDServlet construction.
     *
     * In order for proper "Spring Framework" wiring to occur, servlets must be
     * constructed within the context of the bean factory. This means through
     * scanning or Bean annotation. To programmatically configure(vs web.xml)
     * the servlet itself you have to use a ServletRegistrationBean. We felt
     * this was the most straight forward way to do it in the context of a potentially
     * mult-servlet web application.
     *
     * For services labeled with CometD's Service annotation the
     * MyAppAnnotationCometDServlet uses MyAppInjectables created below to inject Beans into
     * those services via the standard Inject annotation used by CometD @Service annotated services.
     *
     * @return
     */
    @Bean()
    public AnnotationCometDServlet annotationCometDServlet() {
        return new MyAppAnnotationCometDServlet();
    }

    /**
     * Born in the Bayuex!
     **/
    @Bean
    public BayeuxServerImpl bayeuxServerImpl() {
        log.info("Creating BayeuxServerImpl");
        BayeuxServerImpl bayeux = new BayeuxServerImpl();
        bayeux.setTransports(new WebSocketTransport(bayeux), new AsyncJSONTransport(bayeux),
                new JSONPTransport(bayeux));
        bayeux.setAllowedTransports("websocket", "long-polling", "callback-polling");
        bayeux.setOption("ws.cometdURLMapping", bayeuxMapping);

        /*
         * Add extensions
         */
        bayeux.addExtension(new TimesyncExtension());
        bayeux.addExtension(new AcknowledgedMessagesExtension());
        bayeux.addExtension(new TimestampExtension("HH:mm:ss.SSS"));
        . . .  More of our custom stuff.

        This is more local customization stuff that I left in place because should you need it, the email referenced in the ticket may help.
        /*
         * Note: Per Simone Bordet Extensions can't support this properly at the moment. He
         * asked me to file a bug which is #878
         *
         * He gave me a work around for now. See
         * FilteringBroadcastToPublisherMessageListener
         */

        Note that I'm not providing the methods constructing my apps internal capabilities.

        // bayeux.addExtension(new ReturnToSenderExtensionOutgoing());
        // bayeux.addExtension(new ReturnToSenderExtensionSend());
        FilteringBroadcastToPublisherSessionListener filteringBroadcastToPublisherSessionListener = filteringBroadcastToPublisherSessionListener();
        bayeux.addListener(filteringBroadcastToPublisherSessionListener);

        MyAppMsgReqRespRoutingSessionListener myAppMsgReqRespRoutingSessionListener = myAppMsgReqRespRoutingSessionListener();
        Note that my listener now has a reference to the Bayeux Server.
        myAppMsgReqRespRoutingSessionListener.setBayeux(bayeux);
       Now Bayuex will know about my listener.  Circular reference but lives for the life of the app.
       bayeux.addListener(myAppMsgReqRespRoutingSessionListener);

        * * * End custom stuff

        /*
         * Add our custom security.
         * Not providing custom security method called here
         */
        bayeux.setSecurityPolicy(appAuthenticator());

        /*
         * Clamp down the security. By default we authorize NOTHING.
         */
        // Deny unless granted
        bayeux.createChannelIfAbsent("/**",
                (ServerChannel.Initializer) channel -> channel.addAuthorizer(GrantAuthorizer.GRANT_NONE));

        /*
         * We *will* allow anybody to handshake. The handshake is authenticated
         * by @see AppAuthenticator which delegates to individual authenticators
         * per "app" basis which is basically a application in the MyApp family of
         * applications.
         */
        bayeux.createChannelIfAbsent(ServerChannel.META_HANDSHAKE,
                (ServerChannel.Initializer) channel -> channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH));

        return bayeux;
    }

* * *  End of Config Class.

    /**
     * Note that @see MyAppInjectables takes Object... aka Object[] so you can
     * inject anything you want and be sure to make them beans if you need deeply wired Spring
     * objects to be injected into cometd services.
     *
     * @return
     */
    @Bean()
    public MyAppInjectables myAppInjectables() {
        // I inject these 2 but they ahve other things wired into them.  I prefer to keep this boundary small.
        MyAppInjectables myAppInjectables = new MyAppInjectables(myAppBaseChannelAuthorizer(), myAppServiceUtil());
        return myAppInjectables;
    }

    @Bean()
    public MyAppBaseChannelAuthorizer myAppBaseChannelAuthorizer() {
        MyAppBaseChannelAuthorizer myAppBaseChannelAuthorizer = new MyAppBaseChannelAuthorizer();
        // Note that anywhere I call for the construction of the bayeuxServerImpl Spring will ensure it is a singleton because it's annotated as a Bean and all the deep wiring occurs.
        myAppBaseChannelAuthorizer.setBayeux(bayeuxServerImpl());
        return myAppBaseChannelAuthorizer;
    }

    @Bean()
    public MyAppServiceUtil myAppServiceUtil() {
        return new MyAppServiceUtil();
    }

*** you don't need this Unless you want to have a separate non cometd servlet brought into the application that is Bayeux aware 
    /**
     * InternalServlet servlet registration and configuration
     *
     * Note that this servlet should not be loaded before cometd.
     *
     * @return
     */
    @Bean(name = "internal")
    public ServletRegistrationBean<HttpServlet> internalServletReg() {
        ServletRegistrationBean<HttpServlet> regBean = new ServletRegistrationBean<>(internalCometDServlet(),
                "/internal");
        regBean.setAsyncSupported(true);
        regBean.setLoadOnStartup(2);
        return regBean;
    }

    /**
     *
     * InternalCometDServlet construction.
     *
     * In order for proper spring wiring to occur, servlets must be constructed
     * within the context of the bean factory. This means through scanning or
     * Bean annotation. To programmatically configure(vs web.xml) the servlet
     * itself you have to use a ServletRegistrationBean. We felt this was the
     * most straight forward way to do it in the context of a potentially mult-servlet web
     * application in Boot style
     *
     * @return
     */
    @Bean()
    public InternalCometDServlet internalCometDServlet() {
        return new InternalCometDServlet();
    }
*** end you do not need this 
}

/**
 * MyApp wrapper for AnnotationCometDServlet that adds injectables from the Spring
 * bean factory.
 *  Note that this extends CometD class
 */
public class MyAppAnnotationCometDServlet extends AnnotationCometDServlet {
    private static final Logger log = LoggerFactory.getLogger(MyAppAnnotationCometDServlet.class);
    private static final long serialVersionUID = 1L;

    // Spring fills these in for us.

    @Autowired
    BayeuxServerImpl bayeux;

    @Autowired
    private MyAppInjectables injectables;

    private ServletContext servletContext;
 
    // Don't need but I enforce Methods that all our services must implement but this could be marker interface so I left it
    private Set<MyAppReqRespService> myAppReqRespServices = new HashSet<>();

    /*
     *  !!!!!!! important
     * This is the last hook in the servlet api before the servlet container
     * starts the servlet. This is the only place during startup that we can get
     * at the CometD Services instantiated by the BayeuxServerImpl. the
     * super.init() call insures it. Previously I had this lazy detection in the
     * MyAppMsgReqRespRoutingMsgListener which had to do a lazy check on every
     * onMessage call. Now anything with a reference to the BayeuxServerImpl can
     * get the myAppReqRespServices set once the servlet is up and running.
     *
     * (non-Javadoc)
     *
     * @see org.cometd.annotation.AnnotationCometDServlet#init()
     */
    @Override
    public void init() throws ServletException {
        super.init();
        if (log.isDebugEnabled()) {
            log.debug("super init has completed");
        }
        servletContext = getServletContext();
        initReqRespSvcs();
        bayeux.setOption(MyAppCometdConstants.REQ_RESP_SERVICES_BAYEUX_OPTION_KEY, myAppReqRespServices);
        if (log.isDebugEnabled()) {
            log.debug("Found {} MyAppReqRespService implementations.", myAppReqRespServices.size());
        }
    }

    /**
     *  Don't need but I enforce Methods that all our services must implement but could be marker interface so I left it
     * Accessor.
     *
     * Get the Services found that implement the MyAppReqRespService interface
     *
     * @return
     */
    public final Set<MyAppReqRespService> getMyAppReqRespServices() {
        return myAppReqRespServices;
    }

    /*
     * This method is key for allowing stuff to be injected from Spring Beans
     * factory using the Cometd injection annotations. A little bit of a hack but it
     * provides the bridge between the 2 injection mechanisms. Important so you
     * can run in spring boot.
     *
     * (non-Javadoc)
     *
     * @see org.cometd.annotation.AnnotationCometDServlet#
     * newServerAnnotationProcessor(org.cometd.bayeux.server.BayeuxServer)
     */
    @Override
    protected ServerAnnotationProcessor newServerAnnotationProcessor(BayeuxServer bayeuxServer) {
        return new ServerAnnotationProcessor(bayeuxServer, injectables.getInjectables());
    }

    protected final void initReqRespSvcs() {
        /*
         * The application sets this. In Configuration look for something
         * likeServletRegistrationBean<HttpServlet> cometdServletReg()
         */
        ServletRegistration servletReg = servletContext.getServletRegistration("myAppAnnotationCometDServlet");
        String commaSepServices = servletReg.getInitParameter(MyAppCometdConstants.COMETD_SERVICES_INIT_PARAMETER_KEY);
        myAppReqRespServices = Arrays.stream(commaSepServices.split(","))
                                   .map(this::svcNmToMyAppReqRespService)
                                   .filter(svc -> {
                                       return svc != null;
                                   })
                                   .collect(Collectors.toSet());
    }
    
    // Prolly don't need this either.

    private final MyAppReqRespService svcNmToMyAppReqRespService(String fullName) {
        Object svc = servletContext.getAttribute(fullName.trim());
        if (svc != null && svc instanceof MyAppReqRespService) {
            return (MyAppReqRespService) svc;
        }
        return null;
    }
}

/**
 * Simple holder class for multiple classes that can/should be injected into the
 * services. They are passed to ServerAnnotationProcessor by the
 * MyAppAnnotationCometDServlet;
 */
public class MyAppInjectables {

    private Object[] injectables = new Object[0];

    public MyAppInjectables(Object... injectables) {
        this.injectables = injectables;
    }

    /**
     * @return
     */
    public Object[] getInjectables() {
        return injectables;
    }

}

* * * Not required see above
public interface MyAppReqRespService {
 . . . My app required methods.
}

* * * All my services extend this which has more to do with a meta protocol I created vs a real need so I hope this doesn't cause brain pain. It's included here because all this is injected by CometD not Spring. So if you don't have a base class these would be in your service class. And you could inject your DAOs into your service class by adding them to MyAppInjectables.

public abstract class MyAppServiceBase implements MyAppReqRespService {
    private static final Logger log = LoggerFactory.getLogger(MyAppServiceBase.class);

    @Inject
    protected BayeuxServer bayeux;

    @Inject
    protected MyAppBaseChannelAuthorizer chanAuth;

    @Session
    protected LocalSession myLocalSession;

    @Session
    protected ServerSession myServerSession;

 . . . Custom stuff omitted

 . . .  this could also me ommited
    /**
     * left here for learning exercise :-) Normal CometD services wouldn't do this:-)  This is a heart beat message from the server 
     * For debugging mostly but could also be used for periodic server initiated
     * messaging.
     *
     * Will only register one MessageMaker per channel. So if you have 100
     * subscribers this basically returns.
     *
     * @param channelName
     * @param message
     */
    public void scheduleSendToAllServerSessionsOnChannel(String channelName, MessageMaker message) {
        if (backgroundExecutor == null) {
            synchronized (this) {
                this.backgroundExecutor = Executors.newScheduledThreadPool(1);
            }
        }

        if (!schedChannels.containsKey(channelName)) {
            /*
             * We don't know about the channel so schedule a pinger.
             */
            ScheduledFuture<?> task = backgroundExecutor.scheduleAtFixedRate(() -> {

                ServerChannel svrChan = bayeux.getChannel(channelName);
                if (svrChan != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("Channel {} exists; sending to all subscribers.", channelName);
                    }
                    ClientSessionChannel clntChan = myLocalSession.getChannel(channelName);
                    clntChan.publish(message.makeMessage());
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Channel {} doesn't exist; can't send to subscribers.", channelName);
                    }
                }
            }, 15, 60, TimeUnit.SECONDS);
            schedChannels.put(channelName, task);
        }
    }

    /**
     * Unschedule sends.
     *
     * @param channelName
     */
    public void unscheduleSendToAllServerSessionsOnChannel(String channelName) {
        if (schedChannels.containsKey(channelName)) {
            ScheduledFuture<?> task = schedChannels.get(channelName);
            task.cancel(true);
            schedChannels.remove(channelName);
        }
    }
}

The actual CometD Service Finally!!!!! Most of which is debug.  Because for the most part Services are configure and forget unless you start extending things or doing RPC stuff.

@Service("myAppSvcOne")
public class MyAppServiceOne extends MyAppServiceBase implements BayeuxServer.ChannelListener {
    private static final Logger log = LoggerFactory.getLogger(TinSyncService.class);

    public static final String SVC_ONE_ROOT_CHANNEL = "/myapp/svcone/";

    @Inject
    private MyAppServiceUtil myAppServiceUtil;


    public MyAppServiceOne() {
        if (log.isDebugEnabled()) {
            log.debug("MyAppServiceOne is being built.");
        }
    }

    /*
     * Note Parameter/template channels do not seem to be supported on Configure
     */
    @Configure(SVC_ONE_ROOT_CHANNEL + "**")
    public void configureSvcOnec(ConfigurableServerChannel channel) {
        log.info("Configuring {} ", channel.getChannelId());
        
        /* svc one channels shall not publish to the original sender.  This is all custom stuff. */
        // note using injected util class.
        myAppServiceUtil.disableBroadcastToPublisherViaListener(channel.getChannelId());
        . . .  more stuff working with filters.
        
 
        /*
         * some custom auth stuff we have
         */
        AppAuthorizations appAuths = AppAuthorizations.builder(channel.getChannelId())
                                                      .addPublishers("appone", "apptwo")
                                                      .addSubscribers("appone", "apptwo", "appthree")
                                                      .build();
        myAppBaseChannelAuthorizer.addChannelAppAuthorizations(appAuths);

        //  Normal configure stuff
        DataFilterMessageListener filter = new DataFilterMessageListener(new NoMarkupFilter());
        channel.setPersistent(true);
        channel.addListener(filter);
        channel.setLazy(false);
        channel.addAuthorizer(chanAuth);

        bayeux.addListener(this);
    }

    @Listener(SVC_ONE_ROOT_CHANNEL + "{user}")
    public void monitorTinSync(ServerSession sendersSvrSession, ServerMessage message, @Param("user") String user) {
        if (log.isDebugEnabled()) {
            log.debug("Received data: {} on {} and ext: {} from user {}", message.getData(), message.getChannel(),
                    message.getExt(), user);
        }
        // Never do this live:-) unless you like slow stuff
        if (log.isDebugEnabled()) {
            sendersSvrSession.getSubscriptions()
                             .stream()
                             .forEach(svrChan -> {
                                 log.debug("Channel {}:", svrChan.getChannelId()
                                                                 .getId());
                                 svrChan.getSubscribers()
                                        .stream()
                                        .forEach(svrSess -> {
                                            log.debug("\tSubscriber {}:", svrSess.getId());
                                        });
                             });
        }
    }

    /*
     * not needed
     * (non-Javadoc)
     *
     * @see org.cometd.bayeux.server.ConfigurableServerChannel.Initializer#
     * configureChannel(org.cometd.bayeux.server.ConfigurableServerChannel)
     */
    @Override
    public void configureChannel(ConfigurableServerChannel channel) {
        // Handled in @Configure annotated method above.
    }

    /*
     * More debug stuff to help learn things.
     * (non-Javadoc)
     *
     * @see
     * org.cometd.bayeux.server.BayeuxServer.ChannelListener#channelAdded(org.
     * cometd.bayeux.server.ServerChannel)
     */
    @Override
    public void channelAdded(ServerChannel channel) {
        if (channel.isWild()) {
            return;
        }
        // this schedules those pings in the base class.  Again learning stuff:-)
        if (channel.getId()
                   .startsWith(SVC_ONE_ROOT_CHANNEL)) {
            this.scheduleSendToAllServerSessionsOnChannel(channel.getId(),
                    new PingMessageMaker("Ping from TinSync Service."));
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.cometd.bayeux.server.BayeuxServer.ChannelListener#channelRemoved(java
     * .lang.String)
     */
    @Override
    public void channelRemoved(String channelId) {
        if (new ChannelId(channelId).isWild()) {
            return;
        }
        // this schedules those pings in the base class.  Again learning stuff:-)
        if (channelId.startsWith(SVC_ONE_ROOT_CHANNEL)) {
            log.debug("Channel {} doesn't exist; removing pinger.", channelId);
            this.unscheduleSendToAllServerSessionsOnChannel(channelId);
        }
    }
}


*** separate servlet to live in your app if you need that sort of crazy thing:-)
/**
 * This servlet demonstrates having a separate *non* comet *Service* annotated
 * servlet with direct access to the BayeuxServer implementation.
 *
 * I(GAT) believe we don't need it in lieu of the CometD Service annotations.
 * But I'm leaving it here in case we need lower level access to the Bayeux
 * Server.
 *
 */
@Component
public class InternalCometDServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static final Logger logger = LoggerFactory.getLogger(InternalCometDServlet.class);

    @Override
    public void init() throws ServletException {
        logger.info("Initializing {}", InternalCometDServlet.class.toString());
        super.init();
        final BayeuxServerImpl bayeux = (BayeuxServerImpl) getServletContext().getAttribute(BayeuxServer.ATTRIBUTE);

        if (bayeux == null) {
            throw new UnavailableException("No BayeuxServer!");
        }
       
        if (logger.isDebugEnabled()) {
            logger.debug("Creating /foo/bar/baz");
        }

        bayeux.createChannelIfAbsent("/foo/bar/baz", new ConfigurableServerChannel.Initializer.Persistent());

        if (logger.isDebugEnabled()) {
            logger.debug(bayeux.dump());
        }
    }
}

--
--
You received this message because you are subscribed to the Google Groups "cometd-users" group.
To post to this group, send email to cometd...@googlegroups.com
To unsubscribe from this group, send email to cometd-users...@googlegroups.com
For more options, visit this group at http://groups.google.com/group/cometd-users
 
Visit the CometD website at http://www.cometd.org

---
You received this message because you are subscribed to the Google Groups "cometd-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cometd-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/cometd-users/2fddcd7c-b954-4f94-83e0-be0bb6b04fa6n%40googlegroups.com.

Yosvany Hurtado

unread,
Aug 6, 2020, 6:45:36 PM8/6/20
to cometd-users
Dude, you are my hero! Thank you!

The example is working great, I had to delete some stuff from your post, but there was really good gems that helped a lot!

Again, thank you Glenn.

I will update that simple example on github next week just in case some one find it helpful.

Cheers!

Glenn Thompson

unread,
Aug 6, 2020, 6:55:27 PM8/6/20
to cometd...@googlegroups.com
My pleasure.  

I really love CometD and often feel guilty that I haven't contributed back.  So thank you for finding it useful.

Glenn

Simone Bordet

unread,
Aug 7, 2020, 2:48:34 AM8/7/20
to cometd-users
Hi,

On Fri, Aug 7, 2020 at 12:45 AM Yosvany Hurtado <yhur...@gmail.com> wrote:
>
> Dude, you are my hero! Thank you!
>
> The example is working great, I had to delete some stuff from your post, but there was really good gems that helped a lot!
>
> Again, thank you Glenn.
>
> I will update that simple example on github next week just in case some one find it helpful.

There indeed are some creative ideas in there.
I was writing something similar, but Glenn beat me at that.

Can you please post your solution as well?

I will modify the Spring Boot examples and maybe document it better.

--
Simone Bordet
----
http://cometd.org
http://webtide.com
Developer advice, training, services and support
from the Jetty & CometD experts.

Yosvany Hurtado

unread,
Aug 7, 2020, 4:43:01 AM8/7/20
to cometd-users
Hi there

The repository is updated with a minimal working example using cometD with spring boot and JPA : https://github.com/yosvany/demo-cometd.git


Thank you for the help! 

Glenn Thompson

unread,
Aug 7, 2020, 9:49:22 AM8/7/20
to cometd...@googlegroups.com
Is there more you need from me?

I'm doing some other stuff as well.  I've been reluctant to mention it mainly because I don't have all of it fully abstracted in a clean way.  But you might have noticed in some of the ciode I sent that I have request/response extensions.  Using some behind the scenes ext data and a wrapper set of client libraries in javascript/java/C#, I added client to client request response semantics and not just one client to another.  It can be one to all or one to a name(s) classifier that I call application which also ties into my auth. It not in production yet so I haven't been able to circle back and do some more cleaning.  If you guys think it's useful for others I can possibly figure a way to contribute something.  The routing was tricky but the filter code Simone posted, opened the door for it. Basically I use filters to control dequeuing.  But I do more.  Like I know when all respondents have responded or if they have dropped off.

Glenn

--
--
You received this message because you are subscribed to the Google Groups "cometd-users" group.
To post to this group, send email to cometd...@googlegroups.com
To unsubscribe from this group, send email to cometd-users...@googlegroups.com
For more options, visit this group at http://groups.google.com/group/cometd-users
 
Visit the CometD website at http://www.cometd.org

---
You received this message because you are subscribed to the Google Groups "cometd-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cometd-users...@googlegroups.com.

Simone Bordet

unread,
Aug 7, 2020, 9:58:26 AM8/7/20
to cometd-users
Hi,

On Fri, Aug 7, 2020 at 3:49 PM Glenn Thompson <gatma...@gmail.com> wrote:
>
> Is there more you need from me?
>
> I'm doing some other stuff as well. I've been reluctant to mention it mainly because I don't have all of it fully abstracted in a clean way. But you might have noticed in some of the ciode I sent that I have request/response extensions. Using some behind the scenes ext data and a wrapper set of client libraries in javascript/java/C#, I added client to client request response semantics and not just one client to another.

Can you define more precisely what is "client to client request
response semantics"?

Glenn Thompson

unread,
Aug 7, 2020, 11:29:33 AM8/7/20
to cometd...@googlegroups.com
Hi Simone,

So it's basically RPC between clients vs client  -> server.  To do it I did have to stash a little state in a publishers session on the server side.  I use JWT with private shared secrets for the apps. The cometd hub has all the app secrets.  I rather use user certs given I'm already using TLS.  But that's hard to get approved by security for some reason.

Doing comms using clientId to clientId require query or connection broadcast do I have it but won't be using it unless I absolutely have to.  Instead I request based on an appId.  I enforce my own authorization but that needs more work.

Hopefully, what follows will give you a feel for the "routing ext data" I use to do this stuff.  And hopefully you don't hate it:-)


Routing details:

/* Publish to all subscribers. */

var ext = {

       "myA.rtdt" : {

              "typ" : "pall"

       }

};

 

/* Publish to application(s) */

var ext = {

              "myA.rtdt" : {

                     "typ" : "papp",

                     "tApp" : ["<tgtApp>"]

              }

};

 

/* request all subscribers  */

var ext = {

       "myA.rtdt" : {

              "typ" : "all",

              "rspToId" : "<clientId>"// Added by Base Authorizer class to prevent spoofing

              "mSeq" : "<seq_num>" // Added by client. Unique only per client.

       }

};

 

/* Request to all subscribers authenticated as a particular application(s) */

var ext = {

       "myA.rtdt" : {

              "typ" : "app",

              "tApp" : ["<tgtApp>"],

              "rspToId" : "<clientId>"// Added by Base Authorizer class to prevent spoofing

              "mSeq" : <seq_num> // Added by client. Unique only per client.

       }

};

 

/* Client to client request. Discourage use. */

var ext = {

       "myA.rtdt" : {

              "typ" : "clnt",

              "tId" : "<tgtClientId>",

              "rspToId" :"<clientId>"// Added by Base Authorizer class to prevent spoofing

              "mSeq" : <seq_num> // Added by client. Unique only per client.

       }

};

 

/* Response to any of the three request messages */

var ext = {

       "myA.rtdt" : {

              "typ" : "rsp",

              "tId" : "<tgtClientId>"// Passed through from original request.

              "rspFrId" : "<clientId>"// Added by Base Authorizer.

              "rspToSeq" : <respToSeq>, // The sequence number of the msg being responded to. Originally received in request.

              "rem" : <number>, // Added by the message listener on the outbound(targeted) spur. Indicates anticipated remaining responses. Server knows and adjusts if clients have dropped since request was made. <-tricky code.

              "mSeq" :<seq_num> // Added by client. Unique only per client.

       }

}


--
--
You received this message because you are subscribed to the Google Groups "cometd-users" group.
To post to this group, send email to cometd...@googlegroups.com
To unsubscribe from this group, send email to cometd-users...@googlegroups.com
For more options, visit this group at http://groups.google.com/group/cometd-users

Visit the CometD website at http://www.cometd.org

---
You received this message because you are subscribed to the Google Groups "cometd-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cometd-users...@googlegroups.com.

Simone Bordet

unread,
Aug 8, 2020, 4:03:42 AM8/8/20
to cometd-users
Hi,

On Fri, Aug 7, 2020 at 5:29 PM Glenn Thompson <gatma...@gmail.com> wrote:
>
> Hi Simone,
>
> So it's basically RPC between clients vs client -> server.

CometD does have client-to-server RPC.

Since you cannot really do client-to-client (you have to pass from the
server anyway), your scenarios below, IIUC, are all implementable with
CometD already, with the right choice of channels.

You seem to be needing server-to-client RPC?
Is that what you mean by "request to subscribers", as opposed to
"publish to subscribers"?
That could be an addition to CometD.

I would not use session ids (i.e. clientId) as identifiers because
they are transient - it is typically better to use unique userIds to
identify a user.

The point is that routing is best done using CometD and its channels,
rather than via a big switch statement at the application level - use
CometD as the router because that's what it is: a router/broker/etc.

Glenn Thompson

unread,
Aug 8, 2020, 4:58:31 PM8/8/20
to cometd...@googlegroups.com
Hi Simone,

I posted a png that may help explain what I'm doing. let me know if you can't see it.

First I need to explain my situation.  I live in an architectural hell.  I use all kinds of metaphors to describe it but the most current is Jurassic Park meets Hoarders. This is a metaphor rich environment.

I have to say up front that I hate the code that I'm writing deep in my gut.  Please don't think poorly of me:-) I work in an environment with lots of completely separate web apps and they have very little if any server side shared services.  So most of the apps are siloed architecturally and organizationally/politically.  There are some shared backend services that make calls back to main frames. However if additional capabilities are needed, a toll must be paid and you better not be in a hurry.  As an example, the app I currently work on is Java/Struts1/JSP/tiles based. That's the good part, besides tag-libs and scriptlets scattered all over, the pages make use of activeX.  This activeX component connects to some C++ code that performs 2 basic functions. 1. Client side(browser) to Client side(browser) cross application eventing.  2. Client side(browser) calls to a desktop mainframe green screen emulator (including screen scraping).  We need to be HTML5 compliant.  We are not. I can't begin to give enough detail to explain my choices.  But we need to message "web client" to "web client" as well as message a dot net client.  That messaging is "intra end user" and crosses at least 6 web applications all of which are in a severe state of decay.  I believe in the code I posted before I left in the code where I load the reload module. I need it to survive the present. I chose CometD because like all co-dependents I have the belief that I can fix this place and I knew I could make CometD serve my current needs while supporting a future code state. 

So yeah  . . .  I really don't want to say much more.  It's hell out here.

Back to the diagram.

Clockwise starting from the upper left:

  1. Publish:  Nothing new there.  I do have a small ext object to help my filters do their job.  But it isn't strictly necessary.  I added application filtered publishes now as well, so I can limit data going out on spurs that don't need them. I know this could be done with /root/{user}/app1 /root/{user}/app2
  2. Request all:  Think of this like a poll.  The irony doesn't escape me.  Old school polling over long polling/websockets. We spared no expense! What could go wrong!? LOL
  3. Application request:  Basically this was the original use case that had me scratching my head. I already have templated channels split out by unique user.  Imagine the subscription management that allows this.  Not to mention the number of channels.  And yes I understand they are ephemeral. I've added support for more than one app in a request which I really wanted to avoid. My plan is/was to have functional based channels under a specific user. But something came up that concerned me. 
  4.  Client to Client requests: I have them, I want to avoid them.
It's important to note that I loosely keep track of targeted outbound message spurs when a request type message is received by the server.  It allows me to tell the requester when all anticipated responses have been received. I also handle disconnects as well as time out expected responses.

Hope that explains it.

Simone this is kinda your fault:-) You showed me how to manage blocking a message from being dequeued using session filters.

I hope you enjoyed the glimpse into my life.  I can tell you that humor helps to keep what's left of my sanity.

Glenn


--
--
You received this message because you are subscribed to the Google Groups "cometd-users" group.
To post to this group, send email to cometd...@googlegroups.com
To unsubscribe from this group, send email to cometd-users...@googlegroups.com
For more options, visit this group at http://groups.google.com/group/cometd-users

Visit the CometD website at http://www.cometd.org

---
You received this message because you are subscribed to the Google Groups "cometd-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cometd-users...@googlegroups.com.

Simone Bordet

unread,
Aug 10, 2020, 2:54:02 PM8/10/20
to cometd-users
Hi,

[edited subject to start a new thread]

On Sat, Aug 8, 2020 at 10:58 PM Glenn Thompson <gatma...@gmail.com> wrote:
>
> Hi Simone,
>
> I posted a png that may help explain what I'm doing. let me know if you can't see it.
> https://drive.google.com/file/d/1c19yyTKv-8t2Q9t7BQNcBjlaU6IfQiUx/view?usp=sharing
>
> First I need to explain my situation. I live in an architectural hell. I use all kinds of metaphors to describe it but the most current is Jurassic Park meets Hoarders. This is a metaphor rich environment.
>
> I have to say up front that I hate the code that I'm writing deep in my gut. Please don't think poorly of me:-) I work in an environment with lots of completely separate web apps and they have very little if any server side shared services. So most of the apps are siloed architecturally and organizationally/politically.

Ah! Microservices :D

> There are some shared backend services that make calls back to main frames. However if additional capabilities are needed, a toll must be paid and you better not be in a hurry. As an example, the app I currently work on is Java/Struts1/JSP/tiles based.

Solid.

> That's the good part, besides tag-libs and scriptlets scattered all over, the pages make use of activeX. This activeX component connects to some C++ code that performs 2 basic functions. 1. Client side(browser) to Client side(browser) cross application eventing. 2. Client side(browser) calls to a desktop mainframe green screen emulator (including screen scraping). We need to be HTML5 compliant. We are not. I can't begin to give enough detail to explain my choices. But we need to message "web client" to "web client" as well as message a dot net client. That messaging is "intra end user" and crosses at least 6 web applications all of which are in a severe state of decay. I believe in the code I posted before I left in the code where I load the reload module. I need it to survive the present. I chose CometD because like all co-dependents I have the belief that I can fix this place and I knew I could make CometD serve my current needs while supporting a future code state.
>
> So yeah . . . I really don't want to say much more. It's hell out here.
>
> Back to the diagram.
>
> Clockwise starting from the upper left:
>
> Publish: Nothing new there. I do have a small ext object to help my filters do their job. But it isn't strictly necessary. I added application filtered publishes now as well, so I can limit data going out on spurs that don't need them. I know this could be done with /root/{user}/app1 /root/{user}/app2
> Request all: Think of this like a poll. The irony doesn't escape me. Old school polling over long polling/websockets. We spared no expense! What could go wrong!? LOL

This is actually reverse-RPC, which is a feature that CometD lacks
natively, although it can obviously be implemented with /service
channels, with some unique ID for the messages.
How do you actually implement it?
This may be an interesting addition to CometD.

> Application request: Basically this was the original use case that had me scratching my head. I already have templated channels split out by unique user. Imagine the subscription management that allows this. Not to mention the number of channels. And yes I understand they are ephemeral. I've added support for more than one app in a request which I really wanted to avoid. My plan is/was to have functional based channels under a specific user. But something came up that concerned me.

Not sure why the user is required here, but I think upon receiving a
message, you analyze it and re-publish it (or a modified version) to
only AppC1 and AppC2 (if they understand CometD). Otherwise it's a RPC
call to only those 2 apps, and then you coalesce the results into a
CometD reply.

> Client to Client requests: I have them, I want to avoid them.

Trivial for CometD - they are useful for private chats (think
WhatsApp) but if you're not in that space, then they're not needed.

> It's important to note that I loosely keep track of targeted outbound message spurs when a request type message is received by the server. It allows me to tell the requester when all anticipated responses have been received. I also handle disconnects as well as time out expected responses.
>
> Hope that explains it.
>
> Simone this is kinda your fault:-) You showed me how to manage blocking a message from being dequeued using session filters.

Sorry :)
But in the above description of your system - surely simplified - I
don't see why you'd need that?

> I hope you enjoyed the glimpse into my life. I can tell you that humor helps to keep what's left of my sanity.

It's just a matter of writing one more microservice. That'll solve it.

Let us know your "if I had this CometD feature, my life would be so
much simpler".
Most of the CometD features are actually derived from real world use
cases, so this kind of input is important.

Thanks!

Glenn Thompson

unread,
Aug 10, 2020, 7:14:40 PM8/10/20
to cometd...@googlegroups.com


> Publish:  Nothing new there.  I do have a small ext object to help my filters do their job.  But it isn't strictly necessary.  I added application filtered publishes now as well, so I can limit data going out on spurs that don't need them. I know this could be done with /root/{user}/app1 /root/{user}/app2
> Request all:  Think of this like a poll.  The irony doesn't escape me.  Old school polling over long polling/websockets. We spared no expense! What could go wrong!? LOL

This is actually reverse-RPC, which is a feature that CometD lacks
natively, although it can obviously be implemented with /service
channels, with some unique ID for the messages.
How do you actually implement it?
This may be an interesting addition to CometD.

> Application request:  Basically this was the original use case that had me scratching my head. I already have templated channels split out by unique user.  Imagine the subscription management that allows this.  Not to mention the number of channels.  And yes I understand they are ephemeral. I've added support for more than one app in a request which I really wanted to avoid. My plan is/was to have functional based channels under a specific user. But something came up that concerned me.

Not sure why the user is required here, but I think upon receiving a
message, you analyze it and re-publish it (or a modified version) to
only AppC1 and AppC2 (if they understand CometD). Otherwise it's a RPC
call to only those 2 apps, and then you coalesce the results into a
CometD reply.
 

>  Client to Client requests: I have them, I want to avoid them.

Trivial for CometD - they are useful for private chats (think
WhatsApp) but if you're not in that space, then they're not needed.
 
All of the requests are implemented in roughly the same way and can be done on the same channel.  The same channel wasn't the original goal.  This is all done on broadcast channels.  The reason I wanted to use broadcast channels is that CometD manages the subscriber and I just implement listeners to handle certain events and to block outbound messages.

Recall that you helped me with blocking broadcasting to sender on a per channel basis.  I use the same basic approach with smarter Listeners.
So I have a SessionListener that installs a message listener that handles the blocking of outbound messages that don't match the routing details that I emailed previously.  I stash a little state in the session of the requestor as well. 

I also have a custom authorizer that rejects bad requests before they go through further server side processing

Glenn
Reply all
Reply to author
Forward
0 new messages