• We collect Confluence feedback from various sources, and we evaluate what we've collected when planning our product roadmap. To understand how this piece of feedback will be reviewed, see our Implementation of New Features Policy.

      NOTE: This suggestion is for Confluence Server. Using Confluence Cloud? See the corresponding suggestion.

      Currently job plugin modules are not dependency-injected (or "autowired"). They are instantiated by Quartz directly instead, because the module descriptor constructs the JobDetail bean this way:

      JobDetail jobDetail = new JobDetail();
      jobDetail.setGroup(getPluginKey());
      jobDetail.setName(getKey());
      jobDetail.setJobClass(getModuleClass());
      JobDataMap jobDetailMap = new JobDataMap();
      jobDetailMap.put("runOncePerCluster", String.valueOf(perClusterJob));
      jobDetail.setJobDataMap(jobDetailMap);
      return jobDetail;
      

      Although I haven't tested it, it should be possible to construct a delegating job class and pass the autowired plugin Job to it via the JobDataMap. I think this is how the MethodInvokingJobDetailFactoryBean works in Spring.

            [CONFSERVER-20162] Autowire job plugin modules

            Matt Ryall added a comment - - edited

            I've implemented my patch above in Confluence 4.0.1.

            After this change, job plugin module classes are required to be stateless. A single instance of the Job class is reused for all invocations of the scheduled job, so maintaining state in the object would introduce race conditions.

            This is consistent with servlet, macros, event handlers, and many other plugin modules, so shouldn't be a surprise to plugin developers. However, it is inconsistent with the previous behaviour, which created a new instance of the Job class with each invocation.

            Matt Ryall added a comment - - edited I've implemented my patch above in Confluence 4.0.1. After this change, job plugin module classes are required to be stateless. A single instance of the Job class is reused for all invocations of the scheduled job, so maintaining state in the object would introduce race conditions. This is consistent with servlet, macros, event handlers, and many other plugin modules, so shouldn't be a surprise to plugin developers. However, it is inconsistent with the previous behaviour, which created a new instance of the Job class with each invocation.

            Matt Ryall added a comment -

            My AtlasCamp demo plugin has an example of how to do this with a JobDetail bean. This one gets the TaskQueueManager and SystemInformationService injected:

            Matt Ryall added a comment - My AtlasCamp demo plugin has an example of how to do this with a JobDetail bean. This one gets the TaskQueueManager and SystemInformationService injected: https://bitbucket.org/mryall/atlascamp-demo-plugin/ https://bitbucket.org/mryall/atlascamp-demo-plugin/changeset/08530f4d2d9f

            Scott Dudley [Inactive] added a comment - - edited

            There's a much more lightweight solution that works for me and which I've tested with Confluence 3.1 through 3.5. There's also no need to bundle any hibernate JARs as mentioned in the previous forum post.

            I haven't had a need to test this with Confluence 3.0 so YMMV in that particular case.

            Steps:

            1- Ditch all of the JobDetail stuff mentioned in this issue (or in Don Willis's KB linked article). Write your job as a normal job, and write your triggers as normal triggers.

            2- Implement your own PluginContainerManager per Dan Hardiker's 24/Mar/09 comment on PLUG-280.

            3- Make sure that you have another <component> bean, somewhere in your plugin, which is set up to get autowired with your own PluginContainerManager object (eg. implement a setPluginContainerManager(...) method). You don't need to do anything with the autowired PCM object in this case—this just makes sure that the singleton will have been created by the time your job gets to it.

            4- In your job's doExecute() method, wire the required components as follows:

                    MyPluginContainerManager pm = MyPluginContainerManager.getInstance();
            
                    if (pm==null)
                    {
                        log.debug("Deferring cron execution until we have plugin context");
                        return;
                    }
            
                    TransactionTemplate transactionTemplate = pm.getComponent("transactionTemplate", TransactionTemplate.class);
                    MyComponent1 myComponent1 = pm.getComponent("startupAndShutdown", MyComponent1.class);
            

            Scott Dudley [Inactive] added a comment - - edited There's a much more lightweight solution that works for me and which I've tested with Confluence 3.1 through 3.5. There's also no need to bundle any hibernate JARs as mentioned in the previous forum post. I haven't had a need to test this with Confluence 3.0 so YMMV in that particular case. Steps: 1- Ditch all of the JobDetail stuff mentioned in this issue (or in Don Willis's KB linked article). Write your job as a normal job, and write your triggers as normal triggers. 2- Implement your own PluginContainerManager per Dan Hardiker's 24/Mar/09 comment on PLUG-280 . 3- Make sure that you have another <component> bean, somewhere in your plugin, which is set up to get autowired with your own PluginContainerManager object (eg. implement a setPluginContainerManager(...) method). You don't need to do anything with the autowired PCM object in this case—this just makes sure that the singleton will have been created by the time your job gets to it. 4- In your job's doExecute() method, wire the required components as follows: MyPluginContainerManager pm = MyPluginContainerManager.getInstance(); if (pm== null ) { log.debug( "Deferring cron execution until we have plugin context" ); return ; } TransactionTemplate transactionTemplate = pm.getComponent( "transactionTemplate" , TransactionTemplate.class); MyComponent1 myComponent1 = pm.getComponent( "startupAndShutdown" , MyComponent1.class);

            @Micheal - Danke, works like a charm.

            Ulrich Kuhnhardt added a comment - @Micheal - Danke, works like a charm.

            We too run into the problem of trying to inject Spring components into a Job module. The solution we came up with is documented here:
            http://forums.atlassian.com/thread.jspa?messageID=257343166&#257343166

            Michael Rieger added a comment - We too run into the problem of trying to inject Spring components into a Job module. The solution we came up with is documented here: http://forums.atlassian.com/thread.jspa?messageID=257343166&#257343166

            Matt Ryall added a comment -

            Sorry, Uli, not sure where to go from there. Some ideas:

            • try making it a v1 plugins instead
            • don't worry about this hack and use ContainerManager to get references to the required Spring beans
            • wait until we resolve this issue.

            Sorry, none are good options, but I don't have any better ideas at the moment.

            Matt Ryall added a comment - Sorry, Uli, not sure where to go from there. Some ideas: try making it a v1 plugins instead don't worry about this hack and use ContainerManager to get references to the required Spring beans wait until we resolve this issue. Sorry, none are good options, but I don't have any better ideas at the moment.

            Ulrich Kuhnhardt added a comment - - edited

            Fantastic Matt,

            that worked fine.

            However, with Confluence version < 3.3 I'm still experiencing a problem initialising the triggers:

            [INFO] [talledLocalContainer] 2011-03-28 14:49:19,472 ERROR [main] [atlassian.plugin.manager.DefaultPluginManager] enablePluginModules There was an error loading the descriptor 'Write Statistics Trigger' of plugin 'com.adaptavist.confluence.adaptavist-plugin-bubbles'. Disabling.
            [INFO] [talledLocalContainer] java.lang.UnsupportedOperationException
            [INFO] [talledLocalContainer] 	at com.atlassian.plugin.osgi.factory.descriptor.ComponentModuleDescriptor.getModule(ComponentModuleDescriptor.java:17)
            [INFO] [talledLocalContainer] 	at com.atlassian.plugin.osgi.factory.descriptor.ComponentModuleDescriptor.getModule(ComponentModuleDescriptor.java:12)
            [INFO] [talledLocalContainer] 	at com.atlassian.confluence.plugin.descriptor.TriggerModuleDescriptor.getJobDetail(TriggerModuleDescriptor.java:127)
            [INFO] [talledLocalContainer] 	at com.atlassian.confluence.plugin.descriptor.TriggerModuleDescriptor.enabled(TriggerModuleDescriptor.java:87)
            [INFO] [talledLocalContainer] 	at com.atlassian.plugin.manager.DefaultPluginManager.notifyModuleEnabled(DefaultPluginManager.java:1159)
            [INFO] [talledLocalContainer] 	at com.atlassian.confluence.plugin.ConfluencePluginManager.notifyModuleEnabled(ConfluencePluginManager.java:116)
            [INFO] [talledLocalContainer] 	at com.atlassian.plugin.manager.DefaultPluginManager.enablePluginModules(DefaultPluginManager.java:987)
            [INFO] [talledLocalContainer] 	at com.atlassian.plugin.manager.DefaultPluginManager.addPlugins(DefaultPluginManager.java:558)
            [INFO] [talledLocalContainer] 	at com.atlassian.plugin.manager.DefaultPluginManager.init(DefaultPluginManager.java:153)
            [INFO] [talledLocalContainer] 	at com.atlassian.confluence.plugin.ConfluencePluginManager.init(ConfluencePluginManager.java:164)
            [INFO] [talledLocalContainer] 	at com.atlassian.confluence.plugin.ConfluencePluginManager.processConfluenceReadyEvent(ConfluencePluginManager.java:273)
            [INFO] [talledLocalContainer] 	at com.atlassian.confluence.plugin.ConfluencePluginManager.onApplicationEvent(ConfluencePluginManager.java:178)
            

            Here is the section from my atlassian-plugin.xml that follows your recommendation.

                <component
                        key="writestatsJobDetail"
                        name="Write Statistics Job Delegate"
                        class="com.adaptavist.confluence.bubbles.userstats.WriteStatsJobDetail" />
            
                <trigger key="writestats-trigger" name="Write Statistics Trigger">
                    <job key="writestatsJobDetail" />
                    <schedule cron-expression="0 0/5 * * * ?" />
                </trigger>
            

            Take 1

            It almost looks like UPM is looking for a job Module when processing the trigger module. But since trigger >> job is no longer a job (it's a component) this may fail in confluence < 3.3

            Take 2

            Confluence 3.1 uses atlassian-plugins-osgi-2.4.0 which implements

            ComponentModuleDescriptor#getModule()
                {
                    throw new UnsupportedOperationException();
                }
            

            Confluence 3.4.6 uses atlassian-plugins-osgi-2.6.4 which has an implementation of getModule() like

            ComponentModuleDescriptor#getModule()
                {
                    return (Object) new BeanPrefixModuleFactory().createModule(getKey(), this);
                }
            

            I think the version of atlassian-plugins-osgi that comes with confluence 3.1 and 3.2 is not capable of referencing spring modules when initialising the trigger module.

            One note: The sources of confluence 3.4.6 don't show that the provided patch has been applied (yet) in this version. However the job is triggered as specified by the trigger.

            Ulrich Kuhnhardt added a comment - - edited Fantastic Matt, that worked fine. However, with Confluence version < 3.3 I'm still experiencing a problem initialising the triggers: [INFO] [talledLocalContainer] 2011-03-28 14:49:19,472 ERROR [main] [atlassian.plugin.manager.DefaultPluginManager] enablePluginModules There was an error loading the descriptor 'Write Statistics Trigger' of plugin 'com.adaptavist.confluence.adaptavist-plugin-bubbles'. Disabling. [INFO] [talledLocalContainer] java.lang.UnsupportedOperationException [INFO] [talledLocalContainer] at com.atlassian.plugin.osgi.factory.descriptor.ComponentModuleDescriptor.getModule(ComponentModuleDescriptor.java:17) [INFO] [talledLocalContainer] at com.atlassian.plugin.osgi.factory.descriptor.ComponentModuleDescriptor.getModule(ComponentModuleDescriptor.java:12) [INFO] [talledLocalContainer] at com.atlassian.confluence.plugin.descriptor.TriggerModuleDescriptor.getJobDetail(TriggerModuleDescriptor.java:127) [INFO] [talledLocalContainer] at com.atlassian.confluence.plugin.descriptor.TriggerModuleDescriptor.enabled(TriggerModuleDescriptor.java:87) [INFO] [talledLocalContainer] at com.atlassian.plugin.manager.DefaultPluginManager.notifyModuleEnabled(DefaultPluginManager.java:1159) [INFO] [talledLocalContainer] at com.atlassian.confluence.plugin.ConfluencePluginManager.notifyModuleEnabled(ConfluencePluginManager.java:116) [INFO] [talledLocalContainer] at com.atlassian.plugin.manager.DefaultPluginManager.enablePluginModules(DefaultPluginManager.java:987) [INFO] [talledLocalContainer] at com.atlassian.plugin.manager.DefaultPluginManager.addPlugins(DefaultPluginManager.java:558) [INFO] [talledLocalContainer] at com.atlassian.plugin.manager.DefaultPluginManager.init(DefaultPluginManager.java:153) [INFO] [talledLocalContainer] at com.atlassian.confluence.plugin.ConfluencePluginManager.init(ConfluencePluginManager.java:164) [INFO] [talledLocalContainer] at com.atlassian.confluence.plugin.ConfluencePluginManager.processConfluenceReadyEvent(ConfluencePluginManager.java:273) [INFO] [talledLocalContainer] at com.atlassian.confluence.plugin.ConfluencePluginManager.onApplicationEvent(ConfluencePluginManager.java:178) Here is the section from my atlassian-plugin.xml that follows your recommendation. <component key="writestatsJobDetail" name="Write Statistics Job Delegate" class="com.adaptavist.confluence.bubbles.userstats.WriteStatsJobDetail" /> <trigger key="writestats-trigger" name="Write Statistics Trigger"> <job key="writestatsJobDetail" /> <schedule cron-expression="0 0/5 * * * ?" /> </trigger> Take 1 It almost looks like UPM is looking for a job Module when processing the trigger module. But since trigger >> job is no longer a job (it's a component) this may fail in confluence < 3.3 Take 2 Confluence 3.1 uses atlassian-plugins-osgi-2.4.0 which implements ComponentModuleDescriptor#getModule() { throw new UnsupportedOperationException(); } Confluence 3.4.6 uses atlassian-plugins-osgi-2.6.4 which has an implementation of getModule() like ComponentModuleDescriptor#getModule() { return (Object) new BeanPrefixModuleFactory().createModule(getKey(), this); } I think the version of atlassian-plugins-osgi that comes with confluence 3.1 and 3.2 is not capable of referencing spring modules when initialising the trigger module. One note: The sources of confluence 3.4.6 don't show that the provided patch has been applied (yet) in this version. However the job is triggered as specified by the trigger.

            Matt Ryall added a comment -

            Uli, this means the superclass constructor, JobDetail(String, Class), isn't available in that version of Confluence. You should be able to work around this by using the setters instead in your constructor.

            I've updated the workaround document to do this instead:

            public SampleJobDetail(SampleDependency sampleDependency) {
                super();
                setName(SampleJobDetail.class.getSimpleName());
                setJobClass(SampleJob.class);
                this.sampleDependency = sampleDependency;
            }
            

            This should work with all versions of Confluence.

            Matt Ryall added a comment - Uli, this means the superclass constructor, JobDetail(String, Class) , isn't available in that version of Confluence. You should be able to work around this by using the setters instead in your constructor. I've updated the workaround document to do this instead: public SampleJobDetail(SampleDependency sampleDependency) { super (); setName(SampleJobDetail. class. getSimpleName()); setJobClass(SampleJob.class); this .sampleDependency = sampleDependency; } This should work with all versions of Confluence.

            Don Willis added a comment -

            Don Willis added a comment - I've written up Matt's workaround in more detail at http://confluence.atlassian.com/display/CONFDEV/Workaround+pattern+for+autowiring+jobs

            Matt Ryall added a comment -

            As a workaround, you should be able to use a "component" module to create a Quartz JobDetail class (which will be autowired) and specify the name of this component as the job in your trigger module. When the job module is looked up by the trigger module to register it with the scheduler, it doesn't look at the module type – any module which returns a JobDetail object should work.

            Creating a JobDetail instead of a Job is a bit more work, but shouldn't be too hard if you look at the code snippet included above. The job class is instantiated every time a job is triggered, so the way to pass Confluence components to your Job implementation is via the JobDataMap, available on the JobExecutionContext.getMergedDataMap() inside your job's execute() method.

            Matt Ryall added a comment - As a workaround, you should be able to use a "component" module to create a Quartz JobDetail class (which will be autowired) and specify the name of this component as the job in your trigger module. When the job module is looked up by the trigger module to register it with the scheduler, it doesn't look at the module type – any module which returns a JobDetail object should work. Creating a JobDetail instead of a Job is a bit more work, but shouldn't be too hard if you look at the code snippet included above. The job class is instantiated every time a job is triggered, so the way to pass Confluence components to your Job implementation is via the JobDataMap, available on the JobExecutionContext.getMergedDataMap() inside your job's execute() method.

              matt@atlassian.com Matt Ryall
              matt@atlassian.com Matt Ryall
              Votes:
              5 Vote for this issue
              Watchers:
              6 Start watching this issue

                Created:
                Updated:
                Resolved: