How to create a background job in AEM as a Cloud? - java

I want to create a daily background job to be executed by AEM.
I read an aem document and apache sling official site, and I thought I need two classes.
a service class that register the job to JobManager.
a consumer class that do the job.
So I tried these code, but my job was not executed.
service class
import org.apache.sling.event.jobs.JobManager;
import org.apache.sling.event.jobs.JobBuilder.ScheduleBuilder;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Component
public class MyJobService {
private static final Logger logger = LoggerFactory.getLogger(MyJobService.class);
#Reference
private JobManager jobManager;
public static final String JOB_TOPIC = "my/sample/jobtopic";
public void startScheduledJob() {
ScheduleBuilder scheduleBuilder = jobManager.createJob(JOB_TOPIC).schedule();
scheduleBuilder.hourly(9, 0); // execute daily at AM9:00
if (scheduleBuilder.add() == null) {
logger.error("myjobservice error");
}
}
}
job consumer class
import org.apache.sling.event.jobs.Job;
import org.apache.sling.event.jobs.consumer.JobConsumer;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Component(
immediate = true,
service = JobConsumer.class,
property = {
JobConsumer.PROPERTY_TOPICS + "=my/sample/jobtopic"
}
)
public class MyJobConsumer implements JobConsumer {
private static final Logger logger = LoggerFactory.getLogger(MyJobConsumer.class);
#Override
public JobResult process(Job job) {
String topic = job.getTopic();
logger.info("this message is from myjobconsumer. topic is " + topic);
return JobResult.OK;
}
}
Do I need another class or some configurations? Does My code have something wrong?

If you annotate a method with #Activate it will be called when the component starts.
#Activate
public void startScheduledJob()
I guess you want your job to run on startup.
Another option would be to let MyJobService be a servlet and call it from outside.

Related

How to pass a network instance to a consumer in a spring java application

I have a spring java applicaction with a service that initializes a network.
package com.victor.simple.events.services;
import org.hyperledger.fabric.gateway.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
#Service
public class HyperledgerNetworkService {
#Value("${fabric.wallet.path}")
private String walletPath;
#Value("${fabric.wallet.privateKey}")
private String privateKeyPath;
#Value("${fabric.wallet.x509Cert}")
private String x509CertPath;
#Value("${fabric.wallet.certificate.type}")
private String certType;
#Value("${fabric.org.msp}")
private String mspId;
#Value("${fabric.org.networkProfile}")
private String networkProfilePath;
private static final String ADMIN_LABEL = "admin";
#Bean
public Gateway initialize() throws IOException,CertificateException, InvalidKeyException {
System.out.println("Initializing Gateway");
//Load an existing wallet holding identities used to access the network.
Path walletDirectory = Paths.get(walletPath);
try (FileInputStream certificate = new FileInputStream(walletDirectory.toAbsolutePath()+x509CertPath);
FileInputStream privateKey = new FileInputStream(walletDirectory.toAbsolutePath()+privateKeyPath)) {
CertificateFactory fac = CertificateFactory.getInstance(certType);
X509Certificate cert = (X509Certificate) fac.generateCertificate(certificate);
PrivateKey pk = Identities.readPrivateKey(new InputStreamReader(privateKey));
Identity identity = Identities.newX509Identity(mspId,cert,pk);
Wallet wallet = Wallets.newInMemoryWallet();
if(wallet.get(ADMIN_LABEL) == null)
wallet.put(ADMIN_LABEL, identity);
//Path to a common connection profile describing the network.
Path networkConfigFile = Paths.get(networkProfilePath);
System.out.println(networkConfigFile);
//Configure the gateway connection used to access the network.
Gateway.Builder builder = Gateway.createBuilder()
.identity(wallet,"admin")
//.discovery(true)
.networkConfig(networkConfigFile);
return builder.connect();
}
}
}
Then i have a controller that handles the contract events on that network:
#RequiredArgsConstructor
#RestController
public class EventController {
#Autowired
private Consumer<ContractEvent> eventConsumer;
private final Gateway networkService;
#Value("${fabric.channel.contract}")
private String contractName;
#Value("${fabric.channel.name}")
private String channelName;
#PostConstruct
private void registerEvent() throws ContractException{
System.out.println("PostConstruct registerEvent() in EventController execution start");
//Instantiating networks and chaincodes
Network network = networkService.getNetwork(channelName);
Contract contract = network.getContract(contractName);
//Capture contract events
contract.addContractListener(eventConsumer);
}
}
You can see that when an event occurs this is managed by the event consumer:
#Component
public class EventConsumer implements Consumer<ContractEvent> {
#Override
public void accept(ContractEvent contractEvent) {
System.out.println("New event received in the EventConsumer");
}
}
What i want to do is to be able to pass to the EventConsumer the instance of the network so from there i can operate against that network after an event is identified.
Something like
Thanks
Please try to create a bean for network.
#Configuration
public class FabricConfig {
#Value("${fabric.channel.name}")
private String channelName;
#Bean
public Gateway getGateway() {
return ...;
}
#Bean
public Network getNetwork(Gateway gateway) {
return gateway.getNetwork(channelName);
}
}
This bean can be used in EventConsumer class.
#Component
#AllArgsConstructor
public class EventConsumer implements Consumer<ContractEvent> {
private Network network;
Note that #Autowired annotation is not required, if you have
constructor. Spring will try to inject beans via constructor even if it is not annotated as #Autowired.
Unfortunatelly I was unable to verify that it works. Not having experience with Hyperledger Fabric Blockchain.

How to create a cron job in Play Framework

I'm creating a simple cron job that run a task in a specific time (for example 3AM) with Play Framework 2.6.x.
But now I'm stucking in a simple schedule task:
I created an Actor:
package actors;
import akka.actor.UntypedActor;
import org.slf4j.LoggerFactory;
public class DoSomethingActor extends UntypedActor {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(DoSomethingActor.class);
#Override
public void onReceive(final Object message) throws Throwable {
log.info("Write your crone task or simply call your method here that perform your task " + message);
}
}
Then I created a Schedule class that call Actor each time I've set:
package tasks;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Cancellable;
import scala.concurrent.duration.Duration;
#Singleton
public class DoSomethingScheduler {
#Inject
public DoSomethingScheduler(final ActorSystem system,
#Named("do-something-actor") final ActorRef doSomethingActor) {
final Cancellable scheduler;
final int timeDelayFromAppStart = 0;
final int timeGapInSeconds = 1; //Here you provide the time delay for every run
system.scheduler().schedule(
Duration.create(timeDelayFromAppStart, TimeUnit.MILLISECONDS), //Initial delay when system start
Duration.create(timeGapInSeconds, TimeUnit.SECONDS), //Frequency delay for next run
doSomethingActor,
"message for onreceive method of doSomethingActor",
system.dispatcher(),
null);
}
}
Finally, I bind these class in a Module class:
package modules;
import actors.DoSomethingActor;
import com.google.inject.AbstractModule;
import play.libs.akka.AkkaGuiceSupport;
import tasks.DoSomethingScheduler;
public class SchedulerModule extends AbstractModule implements AkkaGuiceSupport{
#Override
protected void configure() {
this.bindActor(DoSomethingActor.class, "do-something-actor");
this.bind(DoSomethingScheduler.class).asEagerSingleton();
}
}
After these things, I run the application but it doesn't work as I expected. I expected it shows a logging every 1 SECOND but there is nothing happen.
Could you please help me to fix it?
Thank you so much!
The solution is in dev mode, I have to send a HTTP request to the application to load the module. In production mode, they will be loaded immediately.

How to retrieve the Application Context in Spring Boot 2

I have this ApplicationContextProvider class defined along with the MyApplication.java (entry point where the application is run):
package com.company.my.app;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public class ApplicationContextProvider implements ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public ApplicationContext getContext() {
return applicationContext;
}
}
Have the package restapi with two classes in it (Greeting is just a class to hold data):
package com.company.my.app.restapi;
import com.company.my.app.ApplicationContextProvider;
import io.micrometer.core.instrument.Counter;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class GreetingController {
private static final Logger LOG = LoggerFactory.getLogger(GreetingController.class);
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
ApplicationContextProvider acp = new ApplicationContextProvider();
ApplicationContext context = acp.getContext();
if (context == null) LOG.info("app context is NULL");
Counter bean = context.getBean(Counter.class);
bean.increment();
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
Finally the MyApplication class is:
package com.company.my.app;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.binder.MeterBinder;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Counter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#SpringBootApplication
public class MyApplication {
#Bean
public MeterBinder exampleMeterBinder() {
return (meterRegistry) -> Counter.builder("my.counter")
.description("my simple counter")
.register(meterRegistry);
}
#Configuration
public class CounterConfig {
#Bean
public Counter simpleCounter(MeterRegistry registry) {
return registry.counter("my.counter");
}
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
When I run the app and call http://localhost:8081/greeting in my browser, it crashes printing app context is NULL. How do I get the application context? I need it to retrieve the simple counter bean.
tl;dr: You don't need the context; there's a better way.
ApplicationContextAware is an artifact from much older versions of Spring, before many of the now-standard features were available. In modern Spring, if you need the ApplicationContext, just inject it like any other bean. However, you almost certainly shouldn't interact with it directly, especially for getBean, which should be replaced with injecting whatever you were getting.
In general, when you need a Spring bean, you should declare it as a constructor parameter. (If you have multiple constructors, you need to annotate one with #Autowired, but if there's only a single constructor, Spring is smart enough to know to use it.) If you're using Lombok, you can use #Value to automatically write the constructor, and Groovy and Kotlin have similar features.
In the specific case of Micrometer, which you're showing here, it is not conventional to declare individual metrics as beans because they are fine-grained tools intended to apply to specific code paths. (Some services might have 10 separate metrics to track various possible scenarios.) Instead, you inject the MeterRegistry and select the counters or other metrics that you need as part of your constructor. Here, your controller class should look like this. (I've eliminated the duplicate AtomicLong, but you could add it back in as you showed if there's a specific reason you need it.)
#RestController
public class GreetingController {
private static final Logger LOG = LoggerFactory.getLogger(GreetingController.class);
private static final String template = "Hello, %s!";
private final Counter counter;
public GreetingController(MeterRegistry meterRegistry) {
counter = meterRegistry.counter("my.counter");
}
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
counter.increment();
long count = (long) counter.count();
return new Greeting(count, String.format(template, name));
}
}

AEM 6.3 - Creating Event handler using OSGi R6 annotations

I have created an Event handler by following https://github.com/nateyolles/aem-osgi-annotation-demo/blob/master/core/src/main/java/com/nateyolles/aem/osgiannotationdemo/core/listeners/SampleOsgiResourceListener.java and it works fine. However, I get the warning "The field SlingConstants.TOPIC_RESOURCE_ADDED is deprecated". I did some searching and found this thread :https://forums.adobe.com/thread/2325819
Here are the challenges that I am facing:
1) I want to create a separate configuration interface for my event handler. I tried this and it isn't working
package com.aem.sites.interfaces;
import org.apache.sling.api.SlingConstants;
import org.osgi.service.event.EventConstants;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
#ObjectClassDefinition(name = "Temperature Listener Configuration")
public #interface TemperatureListenerConfiguration {
#AttributeDefinition(
name = EventConstants.EVENT_FILTER,
description = "Configurable paths for temperature event listener",
type = AttributeType.STRING
)
String getPaths() default "/content/aemsite/en/jcr:content/root/responsivegrid/banner";
#AttributeDefinition(
name = EventConstants.EVENT_TOPIC,
description = "Event types",
type = AttributeType.STRING
)
String[] getEventTypes() default {SlingConstants.TOPIC_RESOURCE_ADDED,SlingConstants.TOPIC_RESOURCE_CHANGED, SlingConstants.TOPIC_RESOURCE_REMOVED};
}
package com.aem.sites.listeners;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.aem.sites.interfaces.TemperatureListenerConfiguration;
#Component(immediate=true,
service=EventHandler.class,
configurationPid = "com.aem.sites.listeners.EventHandler")
#Designate(ocd=TemperatureListenerConfiguration.class)
public class TemperaturePropertyListener implements EventHandler{
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
public void handleEvent(Event event) {
logger.info("*********************Event handler*****************************");
}
#Activate
#Modified
public void activate(TemperatureListenerConfiguration config) {
//config.getPaths();
logger.info("**************************TemperaturePropertyListener******************activate**********************");
}
}
I also want the solution for SlingConstants deprecated issue. Not sure if ResourceChangeListener is the answer to my problem and if yes then how everything is going to work together in the code.
Thanks in advance
===============================
Latest Code
package com.aem.sites.listeners;
import java.util.List;
import org.apache.sling.api.resource.observation.ResourceChange;
import org.apache.sling.api.resource.observation.ResourceChangeListener;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.aem.sites.interfaces.TemperatureListenerConfiguration;
#Component(immediate=true,
service=ResourceChangeListener.class,
configurationPid = "com.aem.sites.listeners.TemperaturePropertyListener")
#Designate(ocd=TemperatureListenerConfiguration.class)
public class TemperaturePropertyListener implements ResourceChangeListener{
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
public void onChange(List<ResourceChange> changes) {
for (final ResourceChange change : changes) {
logger.info("**************************TemperaturePropertyListener******************change type**********************"+change.getType());
}
}
#Activate
#Modified
public void activate(TemperatureListenerConfiguration config) {
//config.getPaths();
logger.info("**************************TemperaturePropertyListener******************activate**********************");
}
}
The Interface
package com.aem.sites.interfaces;
import org.apache.sling.api.resource.observation.ResourceChangeListener;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
#ObjectClassDefinition(name = "Temperature Listener Configuration")
public #interface TemperatureListenerConfiguration {
#AttributeDefinition(
name = ResourceChangeListener.PATHS,
description = "Configurable paths for temperature event listener",
type = AttributeType.STRING
)
String[] getPaths() default {"/content/aemsite/en/jcr:content/root/responsivegrid/banner"};
#AttributeDefinition(
name = ResourceChangeListener.CHANGES,
description = "Event types",
type = AttributeType.STRING
)
String[] getEventTypes() default {"ADDED","REMOVED","CHANGED","PROVIDER_ADDED", "PROVIDER_REMOVED"};
}
Looking at the Javadoc for org.apache.sling.api.SlingConstants in sling 9 documentation here: http://sling.apache.org/apidocs/sling9/org/apache/sling/api/SlingConstants.html
it tells you specifically that TOPIC_RESOURCE_ADDED is deprecated:
Deprecated. Register a ResourceChangeListener instead
Read the documentation for ResourceChangeListener, additionally, you can take a look at a sample SCR service impl from ACS Samples:
It should not be hard to convert that to R6 declarative service.
Also, here are two examples from the sling project ResourceBackedPojoChangeMonitor and OsgiObservationBridge
Try to mimic those classes with the properties in the same class.

Quartz default JobFactory implementation do not call setters

According to Quratz documentation2.x Here
If you add setter methods to your job class that correspond to the names of keys in the JobDataMap (such as a setJobSays(String val) method for the data in the example above), then Quartz’s default JobFactory implementation will automatically call those setters when the job is instantiated, thus preventing the need to explicitly get the values out of the map within your execute method.
Now in my case the setters are not automatically called although my all values are primitives. Also in execute method i am able to get values from jobDataMap.
i am using Quartz 2.2.1 and here is my sample code.what should i do ? thanks in advance.
Here is my Job class.
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
#PersistJobDataAfterExecution
#DisallowConcurrentExecution
public class Db2Db implements Job {
public static final String SOURCE_HOST = "sourceHost";
public static final String DESTINATION_HOST = "destinationHost";
protected String sourceHost;
protected String destinationHost;
public Db2Db() {
System.out.println("created object of job");
}
#Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//sourceHost is still null
system.out.println(sourceHost);
}
public void setSourceHost(String sourceHost) {
this.sourceHost = sourceHost;
}
public void setDestinationHost(String destinationHost) {
this.destinationHost= destinationHost;
}
}
and Here is my main method
public class JobDriver {
public static void main(String[] args) throws SchedulerException {
/*Starting quartz server remotely*/
QuartzServer server = QuartzServer.getInstance();
server.start();
JobDetail job = newJob(Db2Db.class)
.usingJobData(SOURCE_HOST, "132.168.0.12")
.usingJobData(DESTINATION_HOST, "localhost")
.withIdentity("DailyRead91", "group1")
.build();
Trigger trigger = newTrigger().withIdentity("DailyTrigger91", "group1").startNow()
.withSchedule(simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();
//Quartz Server Properties
Properties prop = new Properties();
prop.put("org.quartz.scheduler.rmi.proxy", "true");
prop.put("org.quartz.scheduler.rmi.registryHost", "localhost");
prop.put("org.quartz.scheduler.rmi.registryPort", "1099");
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "10");
Scheduler scheduler = new StdSchedulerFactory(prop).getScheduler();
scheduler.scheduleJob(job, trigger);
}
}

Categories