I am trying to figure out how to easily use spring state machine including persistence with JPA.
This is the problem I am dealing with:
Incompatible data types - factory and persistence
At a certain point in the program I would like to use the state machine which is connected to a user. There are repositories for that purpose (project spring-statemachine-data-jpa).
At first there is a check if a state machine already exists for a player, using the repository. If not, creating a new state machine and persist it.
The problem is that I have different types of state machines. The factory creates a StateMachine<UserState, UserEvent>, the repository returns a JpaRepositoryStateMachine. These are not compatible to each other and for me it is not clear how to persist / create / restore the state machines.
Can you please clarify that for me?
#Autowired
private StateMachineRepository<JpaRepositoryStateMachine> repository;
public someMethod(User user) {
Optional<JpaRepositoryStateMachine> stateMachine = repository.findById(user.getId()); // JPA state machine
if(stateMachine.isEmpty()) {
StateMachine<UserState, UserEvent> createdStateMachine = factory.getStateMachine(user.getId()); // spring state machine
repository.save(createdStateMachine); // compile error
}
// here: ready-to-use statemachine - how?
}
Thanks for your help!
Try to use SpringStateMachineService to get a state machine instance instead of explicitly retrieving it from repository or factory. You can use default implementation provided by Spring:
#Bean
public StateMachineService<State, Event> stateMachineService(
final StateMachineFactory<State, Event> stateMachineFactory,
final StateMachinePersist<WorkflowState, WorkflowEvent, String> stateMachinePersist) {
return new DefaultStateMachineService<>(stateMachineFactory, stateMachinePersist);
}
So, your code will look like:
#Autowired
private StateMachineService<State, Event> stateMachineService;
public someMethod(User user) {
StateMachine<State, Event> stateMachine = stateMachineService.acquireStateMachine(user.getId(), false);
// here: ready-to-use statemachine - call stateMachine.start() for example
}
If you go inside the acquireStateMachine method you can see that it queries state machine from repository by id and creates a new one if nothing found.
You can use JpaPersistingStateMachineInterceptor to implicitly save and update state machine instance on every change.
#Bean
public JpaPersistingStateMachineInterceptor<State, Event, String>
jpaPersistingStateMachineInterceptor() {
return new JpaPersistingStateMachineInterceptor(stateMachineRepository);
}
See Persisting State Machine
Related
I'm developing a SpringBoot web application for managing gaming servers.
I want to have a cronjob that queries the servers, checks whether they have crashed and collects relevant data, such as the number of players online etc. This data needs to be stored and shared among services that require it. Since this data will change often and will become invalid after the whole application stops, I don't want to persist these stats in the database, but in the application memory.
Current implementation
Currently, my implementation is pretty naive - having a collection as a member field of the corresponding Spring service and storing the server statuses there. However I feel this is a really bad solution, as the services should be stateless and also I don't take concurrency into account.
Example code:
#Service
public class ServersServiceImpl implements ServersService {
private final Map<Long, ServerStats> stats = new HashMap<>(); // Map server ID -> stats
...
public void startServer(Long id) {
// ... call service to actually start server process
serverStats.setRunning(true);
stats.put(id, serverStats);
}
...
}
Alternative: Using #Repository classes
I could move the collection with the data to classes with #Repository annotation, which would be semantically more correct. There, I would implement a thread-safe logic of storing the data in java collection. Then I would inject this repository into relevant services.
#Repository
public class ServerStatsRepository {
private final Map<Long, ServerStats> stats = new ConcurrentHashMap<>();
...
public ServerStats getServerStats(Long id) {
return stats.get(id);
}
public ServerStats updateServerStats(Long id, ServerStats serverStats) {
return stats.put(id, serverStats);
}
...
}
Using Redis also came to mind, but I don't want to add too much complexity to the app.
Is my proposed solution a valid approach? Would there be any better option of handling this problem?
I have spring state machine like in the image below:
I want the state machine to be started at the start of the app. And after that it should go in the Re-State where on some time (Scheduled) it go in State GetOrders with SubStates (GetA, GetB and GetC). After that if there is some error it should go in the error, otherwise if everything is okay it should go in the Re-State where it should wait for the Scheduler.
This is my config
#Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception {
config.withConfiguration()
.autoStartup(true)
.machineId("orderMachine");
}
And this is the method for the scheduler:
#Scheduled(cron = "0 0 1 1 * *")
public void startStateMachine() {
StateMachine<States, Events> sm = factory.getStateMachine();
sm.start();
sm.sendEvent(Events.ReState);
}
Everything is working okay but I have noticed that every time this method executes, the stateMachine that is starting have different UUID with the previous one but the Id is same. So I think I am making multiple instances of the state machine. Is it possible to reuse the same state machine or not finishing the process. Because in my case most of the time the state of the machine should be in Re-State. It can be considered as waiting state.
Try to use SpringStateMachineService to get a state machine instance instead of explicitly retrieving it from StateMachineFactory. You can use default implementation provided by Spring:
#Bean
public StateMachineService<State, Event> stateMachineService(
final StateMachineFactory<State, Event> stateMachineFactory) {
return new DefaultStateMachineService<>(stateMachineFactory);
}
So, your code will look like:
#Scheduled(cron = "0 0 1 1 * *")
public void startStateMachine() {
// here we need to get stateMachineId from some storage
stateMachineService.acquireStateMachine(stateMachineId, true)
.sendEvent(Events.ReState);
}
In the code above you need to provide particular state machine ids. Usually, I store them in the database, query them and instantiate state machine for each one.
Persisting state machine context is out of scope of the question. See Persisting State Machine
Well your state machine should be most likely public and static. So that you would make sure by that there is only one instance of your machine.
It's RESTful web app. I am using Hibernate Envers to store historical data. Along with revision number and timestamp, I also need to store other details (for example: IP address and authenticated user). Envers provides multiple ways to have a custom revision entity which is awesome. I am facing problem in setting the custom data on the revision entity.
#RevisionEntity( MyCustomRevisionListener.class )
public class MyCustomRevisionEntity extends DefaultRevisionEntity {
private String userName;
private String ip;
//Accessors
}
public class MyCustomRevisionListener implements RevisionListener {
public void newRevision( Object revisionEntity ) {
MyCustomRevisionEntity customRevisionEntity = ( MyCustomRevisionEntity ) revisionEntity;
//Here I need userName and Ip address passed as arguments somehow, so that I can set them on the revision entity.
}
}
Since newRevision() method does not allow any additional arguments, I can not pass my custom data (like username and ip) to it. How can I do that?
Envers also provides another approach as:
An alternative method to using the org.hibernate.envers.RevisionListener is to instead call the getCurrentRevision( Class revisionEntityClass, boolean persist ) method of the org.hibernate.envers.AuditReader interface to obtain the current revision, and fill it with desired information.
So using the above approach, I'll have to do something like this:
Change my current dao method like:
public void persist(SomeEntity entity) {
...
entityManager.persist(entity);
...
}
to
public void persist(SomeEntity entity, String userName, String ip) {
...
//Do the intended work
entityManager.persist(entity);
//Do the additional work
AuditReader reader = AuditReaderFactory.get(entityManager)
MyCustomRevisionEntity revision = reader.getCurrentRevision(MyCustomRevisionEntity, false);
revision.setUserName(userName);
revision.setIp(ip);
}
I don't feel very comfortable with this approach as keeping audit data seems a cross cutting concern to me. And I obtain the userName and Ip and other data through HTTP request object. So all that data will need to flow down right from entry point of application (controller) to the lowest layer (dao layer).
Is there any other way in which I can achieve this? I am using Spring.
I am imagining something like Spring keeping information about the 'stack' to which a particular method invocation belongs. So that when newRevision() in invoked, I know which particular invocation at the entry point lead to this invocation. And also, I can somehow obtain the arguments passed to first method of the call stack.
One good way to do this would be to leverage a ThreadLocal variable.
As an example, Spring Security has a filter that initializes a thread local variable stored in SecurityContextHolder and then you can access this data from that specific thread simply by doing something like:
SecurityContext ctx = SecurityContextHolder.getSecurityContext();
Authorization authorization = ctx.getAuthorization();
So imagine an additional interceptor that your web framework calls that either adds additional information to the spring security context, perhaps in a custom user details object if using spring security or create your own holder & context object to hold the information the listener needs.
Then it becomes a simple:
public class MyRevisionEntityListener implements RevisionListener {
#Override
public void newRevision(Object revisionEntity) {
// If you use spring security, you could use SpringSecurityContextHolder.
final UserContext userContext = UserContextHolder.getUserContext();
MyRevisionEntity mre = MyRevisionEntity.class.cast( revisionEntity );
mre.setIpAddress( userContext.getIpAddress() );
mre.setUserName( userContext.getUserName() );
}
}
This feels like the cleanest approach to me.
It is worth noting that the other API getCurrentRevision(Session,boolean) was deprecated as of Hibernate 5.2 and is scheduled for removal in 6.0. While an alternative means may be introduced, the intended way to perform this type of logic is using a RevisionListener.
Using Cassandra, I want to create keyspace and tables dynamically using Spring Boot application. I am using Java based configuration.
I have an entity annotated with #Table whose schema I want to be created before application starts up since it has fixed fields that are known beforehand.
However depending on the logged in user, I also want to create additional tables for those user dynamically and be able to insert entries to those tables.
Can somebody guide me to some resources that I can make use of or point me in right direction in how to go about solving these issues. Thanks a lot for help!
The easiest thing to do would be to add the Spring Boot Starter Data Cassandra dependency to your Spring Boot application, like so...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
In addition, this will add the Spring Data Cassandra dependency to your application.
With Spring Data Cassandra, you can configure your application's Keyspace(s) using the CassandraClusterFactoryBean (or more precisely, the subclass... CassandraCqlClusterFactoryBean) by calling the setKeyspaceCreations(:Set) method.
The KeyspaceActionSpecification class is pretty self-explanatory. You can even create one with the KeyspaceActionSpecificationFactoryBean, add it to a Set and then pass that to the setKeyspaceCreations(..) method on the CassandraClusterFactoryBean.
For generating the application's Tables, you essentially just need to annotate your application domain object(s) (entities) using the SD Cassandra #Table annotation, and make sure your domain objects/entities can be found on the application's CLASSPATH.
Specifically, you can have your application #Configuration class extend the SD Cassandra AbstractClusterConfiguration class. There, you will find the getEntityBasePackages():String[] method that you can override to provide the package locations containing your application domain object/entity classes, which SD Cassandra will then use to scan for #Table domain object/entities.
With your application #Table domain object/entities properly identified, you set the SD Cassandra SchemaAction to CREATE using the CassandraSessionFactoryBean method, setSchemaAction(:SchemaAction). This will create Tables in your Keyspace for all domain object/entities found during the scan, providing you identified the proper Keyspace on your CassandraSessionFactoryBean appropriately.
Obviously, if your application creates/uses multiple Keyspaces, you will need to create a separate CassandraSessionFactoryBean for each Keyspace, with the entityBasePackages configuration property set appropriately for the entities that belong to a particular Keyspace, so that the associated Tables are created in that Keyspace.
Now...
For the "additional" Tables per user, that is quite a bit more complicated and tricky.
You might be able to leverage Spring Profiles here, however, profiles are generally only applied on startup. If a different user logs into an already running application, you need a way to supply additional #Configuration classes to the Spring ApplicationContext at runtime.
Your Spring Boot application could inject a reference to a AnnotationConfigApplicationContext, and then use it on a login event to programmatically register additional #Configuration classes based on the user who logged into the application. You need to follow your register(Class...) call(s) with an ApplicationContext.refresh().
You also need to appropriately handle the situation where the Tables already exist.
This is not currently supported in SD Cassandra, but see DATACASS-219 for further details.
Technically, it would be far simpler to create all the possible Tables needed by the application for all users at runtime and use Cassandra's security settings to restrict individual user access by role and assigned permissions.
Another option might be just to create temporary Keyspaces and/or Tables as needed when a user logs in into the application, drop them when the user logs out.
Clearly, there are a lot of different choices here, and it boils down more to architectural decisions, tradeoffs and considerations then it does technical feasibility, so be careful.
Hope this helps.
Cheers!
Following spring configuration class creates keyspace and tables if they dont exist.
#Configuration
public class CassandraConfig extends AbstractCassandraConfiguration {
private static final String KEYSPACE = "my_keyspace";
private static final String USERNAME = "cassandra";
private static final String PASSWORD = "cassandra";
private static final String NODES = "127.0.0.1"; // comma seperated nodes
#Bean
#Override
public CassandraCqlClusterFactoryBean cluster() {
CassandraCqlClusterFactoryBean bean = new CassandraCqlClusterFactoryBean();
bean.setKeyspaceCreations(getKeyspaceCreations());
bean.setContactPoints(NODES);
bean.setUsername(USERNAME);
bean.setPassword(PASSWORD);
return bean;
}
#Override
public SchemaAction getSchemaAction() {
return SchemaAction.CREATE_IF_NOT_EXISTS;
}
#Override
protected String getKeyspaceName() {
return KEYSPACE;
}
#Override
public String[] getEntityBasePackages() {
return new String[]{"com.panda"};
}
protected List<CreateKeyspaceSpecification> getKeyspaceCreations() {
List<CreateKeyspaceSpecification> createKeyspaceSpecifications = new ArrayList<>();
createKeyspaceSpecifications.add(getKeySpaceSpecification());
return createKeyspaceSpecifications;
}
// Below method creates "my_keyspace" if it doesnt exist.
private CreateKeyspaceSpecification getKeySpaceSpecification() {
CreateKeyspaceSpecification pandaCoopKeyspace = new CreateKeyspaceSpecification();
DataCenterReplication dcr = new DataCenterReplication("dc1", 3L);
pandaCoopKeyspace.name(KEYSPACE);
pandaCoopKeyspace.ifNotExists(true).createKeyspace().withNetworkReplication(dcr);
return pandaCoopKeyspace;
}
}
Using #Enes Altınkaya answer:
#Value("${cassandra.keyspace}")
private String keySpace;
#Override
protected List<CreateKeyspaceSpecification> getKeyspaceCreations() {
return Arrays.asList(
CreateKeyspaceSpecification.createKeyspace()
.name(keySpace)
.ifNotExists()
.withNetworkReplication(new DataCenterReplication("dc1", 3L)));
}
To define your varaibles use an application.properties or application.yml file:
cassandra:
keyspace: yout_keyspace_name
Using config files instead of hardcoded strings you can publish your code on for example GitHub without publishing your passwords and entrypoints (.gitignore files) which may be a security risk.
The following cassandra configuration will create a keyspace when it does not exist and also run the start-up script specified
#Configuration
#PropertySource(value = {"classpath:cassandra.properties"})
#EnableCassandraRepositories
public class CassandraConfig extends AbstractCassandraConfiguration {
#Value("${cassandra.keyspace}")
private String cassandraKeyspace;
#Override
protected List<CreateKeyspaceSpecification> getKeyspaceCreations() {
return Collections.singletonList(CreateKeyspaceSpecification.createKeyspace(cassandraKeyspace)
.ifNotExists()
.with(KeyspaceOption.DURABLE_WRITES, true)
.withSimpleReplication());
}
#Override
protected List<String> getStartupScripts() {
return Collections.singletonList("CREATE TABLE IF NOT EXISTS "+cassandraKeyspace+".test(id UUID PRIMARY KEY, greeting text, occurrence timestamp) WITH default_time_to_live = 600;");
}
}
For table's creation you can use this in the application.properties file
spring.data.cassandra.schema-action=CREATE_IF_NOT_EXISTS
This answer is inspired by Viswanath's answer.
My cassandra.yml looks as follows:
spring:
data:
cassandra:
cluster-name: Test Cluster
keyspace-name: keyspace
port: 9042
contact-points:
- 127.0.0.1
#Configuration
#PropertySource(value = { "classpath:cassandra.yml" })
#ConfigurationProperties("spring.data.cassandra")
#EnableCassandraRepositories(basePackages = "info.vishrantgupta.repository")
public class CassandraConfig extends AbstractCassandraConfiguration {
#Value("${keyspacename}")
protected String keyspaceName;
#Override
protected String getKeyspaceName() {
return this.keyspaceName;
}
#Override
protected List getKeyspaceCreations() {
return Collections.singletonList(CreateKeyspaceSpecification
.createKeyspace(keyspaceName).ifNotExists()
.with(KeyspaceOption.DURABLE_WRITES, true)
.withSimpleReplication());
}
#Override
protected List getStartupScripts() {
return Collections.singletonList("CREATE KEYSPACE IF NOT EXISTS "
+ keyspaceName + " WITH replication = {"
+ " 'class': 'SimpleStrategy', "
+ " 'replication_factor': '3' " + "};");
}
}
You might have to customize #ConfigurationProperties("spring.data.cassandra"), if your configuration starts with cassandra in cassandra.yml file then use #ConfigurationProperties("cassandra")
So say I lookup an object from the repository. If I save this object immediately after lookup, Spring Data is smart enough not to update the database. If I change a property within this object and then save, spring data does an update. How does it know it needs to do an update or not?
This is not provided by Spring Data, its a feature of your persistence framework (hibernate, openjpa, eclipselink,...).
Persistence providers enhance the domain objects with some "stuff" for optimization. Normally, this is done by so called runtime enhancement, so your class gets loaded inside of the application and enhanced there(runtime weaving).
Openjpa also allows build-time-enhancement, which means, the "openjpa-domain-extension-stuff" becomes added to your entities at compile time. (there is a maven goal in the openjpa plugin too)
https://openjpa.apache.org/builds/2.2.2/apache-openjpa/docs/ref_guide_pc_enhance.html
If you run mvn openjpa:enhance your simple domain will look now like the following:
(I used jad to decompile the class, as it is to long to show all stuff inside, I copied the most relevant parts)
import org.apache.openjpa.enhance.*;
import org.apache.openjpa.util.IntId;
import org.apache.openjpa.util.InternalException;
public class Entity implements PersistenceCapable
{
public Integer getId()
{
return pcGetid(this);
}
public void setId(Integer id)
{
pcSetid(this, id);
}
....
....
private static final void pcSetid(Entity entity, Integer integer)
{
if(entity.pcStateManager == null)
{
entity.id = integer;
return;
} else
{
entity.pcStateManager.settingObjectField(entity, pcInheritedFieldCount + 3, entity.id, integer, 0);
return;
}
}
....
protected void pcClearFields()
{
id = null;
}
public PersistenceCapable pcNewInstance(StateManager statemanager, Object obj, boolean flag)
{
Entity entity = new Entity();
if(flag)
entity.pcClearFields();
entity.pcStateManager = statemanager;
entity.pcCopyKeyFieldsFromObjectId(obj);
return entity;
}
}
By manipulating your entity, the pcStateManager gets invoked. If you run a persist operation, the persistence framework checks the statemanager if there are changes within your entity and sends the update to the database if necessary.
Spring doesn't actually work directly on instances of your class. What it does is create a proxy that wraps the actual instance and delegates to it. This proxy holds the state of persistence of the underlying instance. In other words, it knows if the instance is in the same state as it is in the database as it is in memory.
If you invoke (certain) methods, it will consider itself dirty. The EntityManager will have to push those changes. If you don't, then it also knows that no changes need to be pushed.