How to get ResourceResolver in a background thread? - java

I'm working on a solution in Adobe Experience Manager (AEM) that receives an HTTP request containing a URL to a file, which I want to download and store in the JCR.
So, I have a servlet that receives the request. It spawns a thread so that I can do the download in the background, and then redirects to a confirmation page. This allows me to send the user on their way without waiting while I try to download the file.
I can download the file just fine, but I'm having trouble getting a usable ResourceResolver to store the file in the JCR from my thread.
At first, I simply referenced the request's ResourceResolver in the background thread:
Servlet:
public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws ServletException, IOException {
...
signingProvider.getDocFromService(params, request.getResourceResolver());
response.sendRedirect(confirmationPage);
}
And in the provider class:
public void getDocFromService(Map<String, String> params, ResourceResolver resolver) {
new Thread( new Runnable() {
public void run() {
Session session = null;
if (resolver != null) {
session = resolver.adaptTo(Session.class);
Node root = session.getRootNode();
...
}
}
}
}
but that didn't work. After reading up on resolvers vs threads, I thought I would be better off creating a new Resolver instance, so I tried to inject a ResourceResolverFactory:
Servlet:
signingProvider.getDocFromService(params);
Provider:
public void getDocFromService(Map<String, String> params) {
new Thread( new Runnable() {
#Reference
private ResourceResolverFactory resolverFactory;
// security hole, fix later
ResourceResolver resolver = resolverFactory.getAdministrativeResourceResolver(null);
Session session = null;
if (resolver != null) {
session = resolver.adaptTo(Session.class);
Node root = session.getRootNode();
...
}
}
}
but the ResourceResolverFactory is null, so I crash when asking it for a resolver. Apparently, no factory is getting injected into the #Reference
I would really rather not do the work on the main thread; after I download the file I'm going to turn around and read it from the JCR and copy it elsewhere. Both of these operations could be slow or fail. I have a copy of the file at the original URL, so the end-user needn't care if my download/uploads had trouble. I just want to send them a confirmation so they can get on with business.
Any advice on how to get a ResourceResolver in a separate thread?

For things like post\background processing you can use Sling Jobs. Please refer to the documentation to find out some details.

Note: #daniil-stelmakh brings a good point in his answer, sling jobs are much better suited for your purpose, to add to his answer, here is a sling tutorial that demonstrates sling jobs: https://sling.apache.org/documentation/tutorials-how-tos/how-to-manage-events-in-sling.html
To answer your question directly:
The issue, really is the placement of #Reference annotation.
That annotation is handled by Maven SCR Plugin and it should be placed on a private member of a '#Component' annotated class.
Basically move your ResourceResolverFactory declaration to become a private member of your class, not the Thread.
#Component(
label = "sample service",
description = "sample service"
)
#Service
public class ServiceImpl {
#Reference
private ResourceResolverFactory resolverFactory;
public void getDocFromService(Map<String, String> params) {
new Thread( new Runnable() {
// security hole, fix later
ResourceResolver resolver = resolverFactory.getAdministrativeResourceResolver(null);
Session session = null;
if (resolver != null) {
session = resolver.adaptTo(Session.class);
Node root = session.getRootNode();
...
}
}
}
}

Related

What is the CDI equivalent of EJB's SessionSynchronization#afterCompletion method?

I've read CDI 2.0 specification (JSR 365) and found out the existence of the #Observes(during=AFTER_SUCCESS) annotation, but it actually requires a custom event to be defined in order to work.
This is what i've got:
//simple """transactional""" file system manager using command pattern
#Transactional(value = Transactional.TxType.REQUIRED)
#TransactionScoped
#Stateful
public class TransactionalFileSystemManager implements SessionSynchronization {
private final Deque<Command> commands = new ArrayDeque<>();
public void createFile(InputStream content, Path path, String name) throws IOException {
CreateFile command = CreateFile.execute(content, path, name);
commands.addLast(command);
}
public void deleteFile(Path path) throws IOException {
DeleteFile command = DeleteFile.execute(path);
commands.addLast(command);
}
private void commit() throws IOException{
for(Command c : commands){
c.confirm();
}
}
private void rollback() throws IOException{
Iterator<Command> it = commands.descendingIterator();
while (it.hasNext()) {
Command c = it.next();
c.undo();
}
}
#Override
public void afterBegin() throws EJBException{
}
#Override
public void beforeCompletion() throws EJBException{
}
#Override
public void afterCompletion(boolean commitSucceeded) throws EJBException{
if(commitSucceeded){
try {
commit();
} catch (IOException e) {
throw new EJBException(e);
}
}
else {
try {
rollback();
} catch (IOException e) {
throw new EJBException(e);
}
}
}
}
However, I want to adopt a CDI-only solution so I need to remove anything EJB related (including the SessionSynchronization interface). How can i achieve the same result using CDI?
First the facts: the authoritative source for this topic is the Java Transaction API (JTA) specification. Search for it online, I got this.
Then the bad news: In order to truly participate in a JTA transaction, you either have to implement a connector according to the Java Connector Architecture (JCA) specification or a XAResource according to JTA. Never done any of them, I am afraid both are going to be hard. Nevertheless, if you search, you may find an existing implementation of a File System Connector.
Your code above will never accomplish true 2-phase commit because, if your code fails, the transaction is already committed, so the application state is inconsistent. Or, there is a small time window when the real transaction is committed but the file system change have not beed executed, again the state is inconsistent.
Some workarounds I can think of, none of which solves the consistency problem:
Persist the File System commands in a database. This ensures that they are enqueued transactionally. A scheduled job wakes up and actually tries to execute the queued FS commands.
Register a Synchronization with the current Transaction, fire an appropriate event from there. Your TransactionalFileSystemManager observes this event, no during attribute needed I guess.

AEM Reverse Replication with two publisher

I Have two Publisher and one Author Server.
I use reverse Replication for Usergenerated Content, for modified Content.
So I have also a Forward Replication (Selfmade, because AEM havent).
The Problem is, that the Replication between the Publisher going in an endless loop. So they replicate like playing ping pong.
What can I do about the ping pong play of the publishers?
I'm Using CQ 6.1, Java 1.7
I found a Solution.
Set Replication Options --> watch printscreen
Adapt WorkflowSession in Session class to replicate
Close Sessionafter replication
Never close Workflow Session
Clean outbox on publishers in crx (/var/replication/outbox) [every time, if something fails]
Create Launcher for Reverse replication Create & Modified (with condition: cq:distribute!=)
Create Launcher for Forward replication Create & Modified (code example)
Because I have more than one Forward Replicator, I made an Abstract
Class. If you don't use it, put all in one class
Attention: with ReplicationOption setSynchronous(true), the
replication was fine to replicate from publisher to publisher. But
because I have an administration page on author, I have to unncomment
this attribute. Because the changes on Auhtor were not replicated to
the publishe
#Component(immediate = true)
#Service(value = WorkflowProcess.class)
public class ReplicateUsergeneratedContentToPublishWorkflow extends AbstractAuthorToPublishWorkflow implements WorkflowProcess{
// OSGI properties
#Property(value = "This workflow replicate usergenerated content from author to publisher")
static final String DESCRIPTION = Constants.SERVICE_DESCRIPTION;
#Property(value = "Titel")
static final String VENDOR = Constants.SERVICE_VENDOR;
#Property(value = "Replicate the usergenerated content from one publisher via author to the ohter publisher")
static final String LABEL = "process.label";
private static final Logger LOGGER = LoggerFactory.getLogger(ReplicateUsergeneratedContentToPublishWorkflow.class);
#Reference
private ResourceResolverFactory resolverFactory;
#Reference
protected Replicator replicator;
#Reference
private SlingRepository repository;
#Reference
SlingSettingsService slingSettingsService;
#Override
public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metaDataMap) throws WorkflowException {
Session session = null;
SimpleCredentials administrator = new SimpleCredentials("username", "password".toCharArray());
try {
java.util.Set<String> runModes = slingSettingsService.getRunModes();
session = repository.login(administrator);
//the replication need to check the payload
String payload = workItem.getWorkflowData().getPayload().toString();
Node node = null;
if (session.itemExists(payload)) {
node = (Node) session.getItem(payload);
}
activateNode(node, workflowSession, replicator);
//save all changes
session.save();
} catch (PathNotFoundException e) {
LOGGER.error("path not found", e);
workflowSession.terminateWorkflow(null);
} catch (ReplicationException e) {
LOGGER.error("error replicating content node", e);
workflowSession.terminateWorkflow(null);
} catch (RepositoryException e) {
LOGGER.error("error reading path to content node", e);
workflowSession.terminateWorkflow(null);
}finally{
if(session != null){
session.logout();
}
}
}
}
public abstract class AbstractAuthorToPublishWorkflow implements WorkflowProcess {
protected void activateNode(Node node, WorkflowSession workflowSession, Replicator replicator) throws RepositoryException, ReplicationException {
ReplicationOptions replicationOptions = new ReplicationOptions();
replicationOptions.setSuppressStatusUpdate(true);
replicationOptions.setSuppressVersions(true);
//replicationOptions.setSynchronous(true);
//the property cq:distribute is settet if the node should be replicated from publisher to author (set it in your own code)
if (node != null) {
node.setProperty("cq:distribute", (Value) null);
//important use WorkflowSession and adapt it to Session class, replication is going to an endless loop, if you doing it without WorkflowSession
replicator.replicate(workflowSession.adaptTo(Session.class), ReplicationActionType.ACTIVATE, node.getPath(), replicationOptions);
}
}
}
Special for User And Group forward Replication, don't interfere the deactivate action from useradmin on author
//Important that you don't interfer the Deactivate Action from useradmin
//do nothing if the action is deactivate!
if( !userNode.getProperty("cq:lastReplicationAction").getString().equals("Deactivate")) {
activateNode(userNode, workflowSession, replicator);
//save all changes
session.save();
}
And for the the codepart were I modifie a node in author, I add this
//quickfix
//FrameworkUtil.getBundle(NodeManageDAO.class).getBundleContext()
BundleContext bundleContext = FrameworkUtil.getBundle(PhotoNodeManagerDAO.class).getBundleContext();
ServiceReference serviceReference = bundleContext.getServiceReference(SlingSettingsService.class.getName( ));
SlingSettingsService slingSettingsService = (SlingSettingsService)bundleContext.getService(serviceReference);
Set<String> runmode= slingSettingsService.getRunModes();
//just in author mode
if(runmode.contains("author")) {
//attention replication from author is not working without nullable / delete the cq:distribute property
node.setProperty("cq:distribute", (Value)null);
}
If you have a updated your workflow model, than you have to restart the worklflow and clean the failures and the cadaverous from old replication configs. Clean on author and on each publisher seperated, go to crx under /etc/workflow/launcher/config.
For the reverse replicator on publisher, set also the condition: cq:distribute!=
and on each part in the code where you change the nodes, add the following three properties
node.setProperty("cq:distribute", ValueFactoryImpl.getInstance().createValue("true"));
node.setProperty("cq:lastModifiedBy", ValueFactoryImpl.getInstance().createValue(session.getUserID()));
node.setProperty("cq:lastModified", ValueFactoryImpl.getInstance().createValue(Calendar.getInstance()));
session.save();
Sample of the Launchers [authorserver]/etc/workflow.html --> launchers

Updating Dropwizard config at runtime

Is it possible to have my app update the config settings at runtime? I can easily expose the settings I want in my UI but is there a way to allow the user to update settings and make them permanent ie save them to the config.yaml file? The only way I can see it to update the file by hand then restart the server which seems a bit limiting.
Yes. It is possible to reload the service classes at runtime.
Dropwizard by itself does not have the way to reload the app, but jersey has.
Jersey uses a container object internally to maintain the running application. Dropwizard uses the ServletContainer class of Jersey to run the application.
How to reload the app without restarting it -
Get a handle to the container used internally by jersey
You can do this by registering a AbstractContainerLifeCycleListener in Dropwizard Environment before starting the app. and implement its onStartup method as below -
In your main method where you start the app -
//getting the container instance
environment.jersey().register(new AbstractContainerLifecycleListener() {
#Override
public void onStartup(Container container) {
//initializing container - which will be used to reload the app
_container = container;
}
});
Add a method to your app to reload the app. It will take in the list of string which are the names of the service classes you want to reload. This method will call the reload method of the container with the new custom DropWizardConfiguration instance.
In your Application class
public static synchronized void reloadApp(List<String> reloadClasses) {
DropwizardResourceConfig dropwizardResourceConfig = new DropwizardResourceConfig();
for (String className : reloadClasses) {
try {
Class<?> serviceClass = Class.forName(className);
dropwizardResourceConfig.registerClasses(serviceClass);
System.out.printf(" + loaded class %s.\n", className);
} catch (ClassNotFoundException ex) {
System.out.printf(" ! class %s not found.\n", className);
}
}
_container.reload(dropwizardResourceConfig);
}
For more details see the example documentation of jersey - jersey example for reload
Consider going through the code and documentation of following files in Dropwizard/Jersey for a better understanding -
Container.java
ContainerLifeCycleListener.java
ServletContainer.java
AbstractContainerLifeCycleListener.java
DropWizardResourceConfig.java
ResourceConfig.java
No.
Yaml file is parsed at startup and given to the application as Configuration object once and for all. I believe you can change the file after that but it wouldn't affect your application until you restart it.
Possible follow up question: Can one restart the service programmatically?
AFAIK, no. I've researched and read the code somewhat for that but couldn't find a way to do that yet. If there is, I'd love to hear that :).
I made a task that reloads the main yaml file (it would be useful if something in the file changes). However, it is not reloading the environment. After researching this, Dropwizard uses a lot of final variables and it's quite hard to reload these on the go, without restarting the app.
class ReloadYAMLTask extends Task {
private String yamlFileName;
ReloadYAMLTask(String yamlFileName) {
super("reloadYaml");
this.yamlFileName = yamlFileName;
}
#Override
public void execute(ImmutableMultimap<String, String> parameters, PrintWriter output) throws Exception {
if (yamlFileName != null) {
ConfigurationFactoryFactory configurationFactoryFactory = new DefaultConfigurationFactoryFactory<ReportingServiceConfiguration>();
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
ObjectMapper objectMapper = Jackson.newObjectMapper();
final ConfigurationFactory<ServiceConfiguration> configurationFactory = configurationFactoryFactory.create(ServiceConfiguration.class, validator, objectMapper, "dw");
File confFile = new File(yamlFileName);
configurationFactory.build(new File(confFile.toURI()));
}
}
}
You can change the configuration in the YAML and read it while your application is running. This will not however restart the server or change any server configurations. You will be able to read any changed custom configurations and use them. For example, you can change the logging level at runtime or reload other custom settings.
My solution -
Define a custom server command. You should use this command to start your application instead of the "server" command.
ArgsServerCommand.java
public class ArgsServerCommand<WC extends WebConfiguration> extends EnvironmentCommand<WC> {
private static final Logger LOGGER = LoggerFactory.getLogger(ArgsServerCommand.class);
private final Class<WC> configurationClass;
private Namespace _namespace;
public static String COMMAND_NAME = "args-server";
public ArgsServerCommand(Application<WC> application) {
super(application, "args-server", "Runs the Dropwizard application as an HTTP server specific to my settings");
this.configurationClass = application.getConfigurationClass();
}
/*
* Since we don't subclass ServerCommand, we need a concrete reference to the configuration
* class.
*/
#Override
protected Class<WC> getConfigurationClass() {
return configurationClass;
}
public Namespace getNamespace() {
return _namespace;
}
#Override
protected void run(Environment environment, Namespace namespace, WC configuration) throws Exception {
_namespace = namespace;
final Server server = configuration.getServerFactory().build(environment);
try {
server.addLifeCycleListener(new LifeCycleListener());
cleanupAsynchronously();
server.start();
} catch (Exception e) {
LOGGER.error("Unable to start server, shutting down", e);
server.stop();
cleanup();
throw e;
}
}
private class LifeCycleListener extends AbstractLifeCycle.AbstractLifeCycleListener {
#Override
public void lifeCycleStopped(LifeCycle event) {
cleanup();
}
}
}
Method to reload in your Application -
_ymlFilePath = null; //class variable
public static boolean reloadConfiguration() throws IOException, ConfigurationException {
boolean reloaded = false;
if (_ymlFilePath == null) {
List<Command> commands = _configurationBootstrap.getCommands();
for (Command command : commands) {
String commandName = command.getName();
if (commandName.equals(ArgsServerCommand.COMMAND_NAME)) {
Namespace namespace = ((ArgsServerCommand) command).getNamespace();
if (namespace != null) {
_ymlFilePath = namespace.getString("file");
}
}
}
}
ConfigurationFactoryFactory configurationFactoryFactory = _configurationBootstrap.getConfigurationFactoryFactory();
ValidatorFactory validatorFactory = _configurationBootstrap.getValidatorFactory();
Validator validator = validatorFactory.getValidator();
ObjectMapper objectMapper = _configurationBootstrap.getObjectMapper();
ConfigurationSourceProvider provider = _configurationBootstrap.getConfigurationSourceProvider();
final ConfigurationFactory<CustomWebConfiguration> configurationFactory = configurationFactoryFactory.create(CustomWebConfiguration.class, validator, objectMapper, "dw");
if (_ymlFilePath != null) {
// Refresh logging level.
CustomWebConfiguration webConfiguration = configurationFactory.build(provider, _ymlFilePath);
LoggingFactory loggingFactory = webConfiguration.getLoggingFactory();
loggingFactory.configure(_configurationBootstrap.getMetricRegistry(), _configurationBootstrap.getApplication().getName());
// Get my defined custom settings
CustomSettings customSettings = webConfiguration.getCustomSettings();
reloaded = true;
}
return reloaded;
}
Although this feature isn't supported out of the box by dropwizard, you're able to accomplish this fairly easy with the tools they give you.
Before I get started, note that this isn't a complete solution for the question asked as it doesn't persist the updated config values to the config.yml. However, this would be easy enough to implement yourself simply by writing to the config file from the application. If anyone would like to write this implementation feel free to open a PR on the example project I've linked below.
Code
Start off with a minimal config:
config.yml
myConfigValue: "hello"
And it's corresponding configuration file:
ExampleConfiguration.java
public class ExampleConfiguration extends Configuration {
private String myConfigValue;
public String getMyConfigValue() {
return myConfigValue;
}
public void setMyConfigValue(String value) {
myConfigValue = value;
}
}
Then create a task which updates the config:
UpdateConfigTask.java
public class UpdateConfigTask extends Task {
ExampleConfiguration config;
public UpdateConfigTask(ExampleConfiguration config) {
super("updateconfig");
this.config = config;
}
#Override
public void execute(Map<String, List<String>> parameters, PrintWriter output) {
config.setMyConfigValue("goodbye");
}
}
Also for demonstration purposes, create a resource which allows you to get the config value:
ConfigResource.java
#Path("/config")
public class ConfigResource {
private final ExampleConfiguration config;
public ConfigResource(ExampleConfiguration config) {
this.config = config;
}
#GET
public Response handleGet() {
return Response.ok().entity(config.getMyConfigValue()).build();
}
}
Finally wire everything up in your application:
ExampleApplication.java (exerpt)
environment.jersey().register(new ConfigResource(configuration));
environment.admin().addTask(new UpdateConfigTask(configuration));
Usage
Start up the application then run:
$ curl 'http://localhost:8080/config'
hello
$ curl -X POST 'http://localhost:8081/tasks/updateconfig'
$ curl 'http://localhost:8080/config'
goodbye
How it works
This works simply by passing the same reference to the constructor of ConfigResource.java and UpdateConfigTask.java. If you aren't familiar with the concept see here:
Is Java "pass-by-reference" or "pass-by-value"?
The linked classes above are to a project I've created which demonstrates this as a complete solution. Here's a link to the project:
scottg489/dropwizard-runtime-config-example
Footnote: I haven't verified this works with the built in configuration. However, the dropwizard Configuration class which you need to extend for your own configuration does have various "setters" for internal configuration, but it may not be safe to update those outside of run().
Disclaimer: The project I've linked here was created by me.

Manually change task on running process-instance

in order of developing a custom BPM Application there is one feature we used with another BPM engine provider and like to use it with camunda too.
The targeted functionality is about setting/reset running process instances to a specified task other than the current active one. From our perspective necessary when e.g.:
authoring process-instances due to process-version migration
resolving incidents
resolving accidentially wrong usage by an user
Finally I didn't really found a simple function to do this but worked out some custom code which worked with some limitations. There are some weaknesses and uncertainities within this code so that I have the following question:
Did I miss an alternative way to achieve this or is the following approach correct or is it even fully unsupported at the moment ?
The current weaknesses imho:
first and most important: no historic task instance is stored. This
causes that it's not traceable who or even when the task was triggered/activate/started.
I found the following post on camunda google group (post) which says that it's
correct at this point because it’s a task out of the process definition scope but
by using a task definition from the underlying process definition I should
be "in scope" ?!
the code is based on internal implementation and not on official interface
at this point a lot of "bootstrap"/initialization have to be done manually
but as user (not developer of camunda) I am not fully aware of what is required and
what is optional
some parts like parsing expressions from task definition failed (see code commented out)
but that may be caused by wrong usage
Here's the code (experimental snippet of our camunda service facade) :
#Inject
protected HistoryService histService;
#Inject
protected TaskService taskService;
#Inject
protected ManagementService managementService;
#Inject
protected RuntimeService runtimeService;
#Inject
protected IdentityService identityService;
#Inject
protected RepositoryService repositoryService;
#Inject
protected FormService formService;
#Inject
protected ProcessEngine processEngine;
public void startTask(String processInstanceId, String taskKey) {
Collection<TaskDefinition> taskDefs = getAvailableTasks(
processInstanceId);
TaskEntity newTask = null;
TaskDefinition taskDef = null;
for (TaskDefinition taskDefinition : taskDefs) {
if (taskDefinition.getKey().equals(taskKey)) {
taskDef = taskDefinition;
break;
}
}
boolean taskDefExists = taskDef != null;
List<Task> runningTasksByKey = getTasksByKey(taskKey, processInstanceId);
boolean taskIsAlreadyRunning = runningTasksByKey != null
&& runningTasksByKey.size() > 0;
if (taskDefExists && !taskIsAlreadyRunning) {
newTask = (TaskEntity) taskService.newTask();
ProcessInstance procInst = getProcessInstance(processInstanceId);
ExecutionEntity procInstEntity = (ExecutionEntity) procInst;
String taskName = (String) taskDef.getNameExpression().
getExpressionText();
// String taskAssigne = (String) taskDef.getAssigneeExpression().
// getValue(
// procInstEntity);
// newTask.setAssignee(taskAssigne);
newTask.setTaskDefinitionKey(taskDef.getKey());
newTask.setProcessInstance(procInstEntity);
newTask.setTaskDefinition(taskDef);
newTask.setName(taskName);
newTask.setProcessInstanceId(processInstanceId);
newTask.setProcessDefinitionId(procInstEntity.
getProcessDefinitionId());
taskService.saveTask(newTask);
TaskServiceImpl taskServiceImpl = (TaskServiceImpl) BpmPlatform.
getProcessEngineService().getDefaultProcessEngine().
getTaskService();
CommandExecutor commandExecutor = taskServiceImpl.
getCommandExecutor();
ExecutionEntity executionEntity = commandExecutor.execute(
new SaveTaskActivityInstanceCmd(newTask,
procInstEntity));
// commandExecutor.execute(new `SaveTaskHistoricActivityInstanceCmd(executionEntity, newTask));`
}
}
public Collection<TaskDefinition> getAvailableTasks(String processInstanceId) {
Map<String, TaskDefinition> taskDefs = null;
Collection<TaskDefinition> taskDefObjects = null;
if (processInstanceId != null) {
ProcessInstanceQuery procInstQuery = runtimeService.
createProcessInstanceQuery().processInstanceId(
processInstanceId);
ProcessDefinitionEntity procDefEntity = getProcessDefinitionEager(
processInstanceId);
taskDefs = procDefEntity.getTaskDefinitions();
}
taskDefObjects = (Collection<TaskDefinition>) (taskDefs != null ? taskDefs.
values() : new ArrayList<TaskDefinition>());
return taskDefObjects;
}
public ProcessDefinitionEntity getProcessDefinitionEager(
String processInstanceId) {
ProcessInstanceQuery procInstQuery = runtimeService.
createProcessInstanceQuery().processInstanceId(
processInstanceId);
ProcessInstance procInst = procInstQuery.singleResult();
String procDefId = procInst.getProcessDefinitionId();
return (ProcessDefinitionEntity) repositoryService.getProcessDefinition(
procDefId);
}
public List<Task> getTasksByKey(String taskKey, String processInstanceId) {
List<Task> tasks = taskService.createTaskQuery().processInstanceId(
processInstanceId).taskDefinitionKey(taskKey).list();
return tasks;
}
public class SaveTaskActivityInstanceCmd implements Command<ExecutionEntity>,
Serializable {
private TaskEntity newTask;
private ExecutionEntity procInstEntity;
public SaveTaskActivityInstanceCmd(TaskEntity newTaskInit,
ExecutionEntity procInstEntityInit) {
this.newTask = newTaskInit;
this.procInstEntity = procInstEntityInit;
}
public ExecutionEntity execute(CommandContext commandContext) {
ActivityImpl actImpl = new ActivityImpl(newTask.
getTaskDefinitionKey(),
procInstEntity.getProcessDefinition());
actImpl.setActivityBehavior(new UserTaskActivityBehavior(
new CdiExpressionManager(), newTask.getTaskDefinition()));
ExecutionEntity execEntity = new ExecutionEntity();
execEntity.setActivity(actImpl);
execEntity.setActivityInstanceId(newTask.getTaskDefinitionKey()
+ ":" + newTask.getId());
execEntity.setEventName(newTask.getEventName());
execEntity.setProcessDefinitionId(newTask.getProcessDefinitionId());
execEntity.setActive(true);
execEntity.setProcessInstance(procInstEntity);
commandContext.getExecutionManager().insert(execEntity);
return execEntity;
}
}
I appreciate any hint or advice :-)
Beginning with Camunda 7.3, you can use process instance modification to start any activity in a process and cancel any active activity instance.
Example:
runtimeService.createProcessInstanceModification(processInstanceId)
.startBeforeActivity("someActivityId")
.cancelActivityInstance("someActivityInstanceId")
.execute();
See http://docs.camunda.org/7.3/guides/user-guide/#process-engine-process-instance-modification for documentation.
I wouldn't mess with the process instance on that level, as you already noticed, you are bypassing camundas services. When faced with a similar problem, we went with the following:
cancel the process instance of the old process version
start a new instance of the extended process and forward it programmatically to the desired state ...
Another option: model an entry point (message start event) inside the new process version. Then, instead of programmatically forwarding the instance to the desired state, just start the new instance via the event and pass all process variables of the old instance ...

How to get an existing websocket instance

I'm working on an application that uses Websockets (Java EE 7) to send messages to all the connected clients asynchronously. The server (Websocket endpoint) should send these messages whenever a new article (an engagement modal in my app) is created.
Everytime a connection is established to the websocket endpoint, I'm adding the corresponding session to a list, which I could be able to access outside.
But the problem I had is, when I'm accessing this created websocket endpoint to which all the clients connected from outside (any other business class), I've get the existing instance (like a singleton).
So, can you please suggest me a way I can get an existing instance of the websocket endpoint, as I can't create it as new MyWebsocketEndPoint() coz it'll be created by the websocket internal mechanism whenever the request from a client is received.
For a ref:
private static WebSocketEndPoint INSTANCE = null;
public static WebSocketEndPoint getInstance() {
if(INSTANCE == null) {
// Instead of creating a new instance, I need an existing one
INSTANCE = new WebSocketEndPoint ();
}
return INSTANCE;
}
Thanks in advance.
The container creates a separate instance of the endpoint for every client connection, so you can't do what you're trying to do. But I think what you're trying to do is send a message to all the active client connections when an event occurs, which is fairly straightforward.
The javax.websocket.Session class has the getBasicRemote method to retrieve a RemoteEndpoint.Basic instance that represents the endpoint associated with that session.
You can retrieve all the open sessions by calling Session.getOpenSessions(), then iterate through them. The loop will send each client connection a message. Here's a simple example:
#ServerEndpoint("/myendpoint")
public class MyEndpoint {
#OnMessage
public void onMessage(Session session, String message) {
try {
for (Session s : session.getOpenSessions()) {
if (s.isOpen()) {
s.getBasicRemote().sendText(message);
}
} catch (IOException ex) { ... }
}
}
But in your case, you probably want to use CDI events to trigger the update to all the clients. In that case, you'd create a CDI event that a method in your Websocket endpoint class observes:
#ServerEndpoint("/myendpoint")
public class MyEndpoint {
// EJB that fires an event when a new article appears
#EJB
ArticleBean articleBean;
// a collection containing all the sessions
private static final Set<Session> sessions =
Collections.synchronizedSet(new HashSet<Session>());
#OnOpen
public void onOpen(final Session session) {
// add the new session to the set
sessions.add(session);
...
}
#OnClose
public void onClose(final Session session) {
// remove the session from the set
sessions.remove(session);
}
public void broadcastArticle(#Observes #NewArticleEvent ArticleEvent articleEvent) {
synchronized(sessions) {
for (Session s : sessions) {
if (s.isOpen()) {
try {
// send the article summary to all the connected clients
s.getBasicRemote().sendText("New article up:" + articleEvent.getArticle().getSummary());
} catch (IOException ex) { ... }
}
}
}
}
}
The EJB in the above example would do something like:
...
#Inject
Event<ArticleEvent> newArticleEvent;
public void publishArticle(Article article) {
...
newArticleEvent.fire(new ArticleEvent(article));
...
}
See the Java EE 7 Tutorial chapters on WebSockets and CDI Events.
Edit: Modified the #Observer method to use an event as a parameter.
Edit 2: wrapped the loop in broadcastArticle in synchronized, per #gcvt.
Edit 3: Updated links to Java EE 7 Tutorial. Nice job, Oracle. Sheesh.
Actually, WebSocket API provides a way how you can control endpoint instantiation. See https://tyrus.java.net/apidocs/1.2.1/javax/websocket/server/ServerEndpointConfig.Configurator.html
simple sample (taken from Tyrus - WebSocket RI test):
public static class MyServerConfigurator extends ServerEndpointConfig.Configurator {
public static final MyEndpointAnnotated testEndpoint1 = new MyEndpointAnnotated();
public static final MyEndpointProgrammatic testEndpoint2 = new MyEndpointProgrammatic();
#Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
if (endpointClass.equals(MyEndpointAnnotated.class)) {
return (T) testEndpoint1;
} else if (endpointClass.equals(MyEndpointProgrammatic.class)) {
return (T) testEndpoint2;
}
throw new InstantiationException();
}
}
You need to register this to an endpoint:
#ServerEndpoint(value = "/echoAnnotated", configurator = MyServerConfigurator.class)
public static class MyEndpointAnnotated {
#OnMessage
public String onMessage(String message) {
assertEquals(MyServerConfigurator.testEndpoint1, this);
return message;
}
}
or you can use it with programmatic endpoints as well:
public static class MyApplication implements ServerApplicationConfig {
#Override
public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> endpointClasses) {
return new HashSet<ServerEndpointConfig>
(Arrays.asList(ServerEndpointConfig.Builder
.create(MyEndpointProgrammatic.class, "/echoProgrammatic")
.configurator(new MyServerConfigurator())
.build()));
}
#Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) {
return new HashSet<Class<?>>(Arrays.asList(MyEndpointAnnotated.class));
}
Of course it is up to you if you will have one configurator used for all endpoints (ugly ifs as in presented snippet) or if you'll create separate configurator for each endpoint.
Please do not copy presented code as it is - this is only part of Tyrus tests and it does violate some of the basic OOM paradigms.
See https://github.com/tyrus-project/tyrus/blob/1.2.1/tests/e2e/src/test/java/org/glassfish/tyrus/test/e2e/GetEndpointInstanceTest.java for complete test.

Categories

Resources