I would be glad for any kind of opinion on this setup for a Vehicle Routing Problem.
First of all, these are my first steps with this tool, so please forgive me if I'm totally out of scope :-)
I've made an algorithm without optaplanner, to test a basic rules setup.
It worked for a single Vehicle, but optaplanner looked very similar to my original idea to assign points to each satisfied rule, then select the vehicle with the higest score.
It's also configurable, and for sure it's better than what I made before.
Let's begin
Looking at the docs, and some videos, seems that VRP problems are solved when all the pickup points have been already defined in a dataset feeded into the Solver.
What about finding a Vehicle for each request in real-time, refusing it in case no vehicle can satisfy the constraints? Consider that there are calls to external (paid) Maps services, that are making the process slower, and have a cost. It's better to avoid redundancy in these calls.
Pickup requests can be done for any future date, but not the current day.
Planning Entity - the pickup request
#PlanningEntity
public class FindBestVehicleRequest
{
#PlanningId
private Long id;
// Shadow variable candidate
private Double requiredSpace;
// Shadow variable candidate
private int requiredAutonomy;
private String pickupAddress;
// Shadow variable candidate
private LatLng pickupPosition;
private LocalDateTime pickupDateTime;
#PlanningVariable(valueRangeProviderRefs = "vehicle")
private Vehicle vehicle;
...
}
Calculations involved in each request
I've read into the docs about shadow variables, I'm still far from understanding how to define them, but I suppose that are useful in my case: as stated before, for each request I need to call the Maps Service(Google, OpenStreetMaps, ...) in order to calculate the distance to reach the pickup address from vhere the vehicle is located.
Obtain the Vehicle origin position,
some pseudo code of the logic:
if (vehicle.hasOrdersBefore(pickupDateTime) {
LatLng origin = vehicle.lastOrderBefore(pickupDateTime).getPosition();
String destination = pickupAddress;
Integer distance = mapsServer.getDistance(origin, destination);
return distance;
}
There are more calculations like this one involved, but there's no need to list all of them, they're similar.
I'm studying all the available algorithm types to find the one that's more indicated for this problem.
ConstraintProvider implementation
public class BestVehicleConstraintProvider implements ConstraintProvider {
#Override public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
return new Constraint[]{
vehicleHasAutonomy(constraintFactory)
};
}
// One HARD constraint
private Constraint vehicleHasAutonomy(ConstraintFactory constraintFactory) {
return constraintFactory.from(FindBestVehicleRequest.class)
.groupBy(FindBestVehicleRequest::getVehicle, sum(FindBestVehicleRequest::getRequiredAutonomy))
.filter((vehicle, requiredAutonomy) -> requiredAutonomy > vehicle.getVehicleTypeProperties().getMaxKmAutonomy())
.penalize("vehicleHasAutonomy", HardSoftScore.ONE_HARD,
((vehicle, requiredSpace) -> vehicle.getVehicleTypeProperties().getMaxKmAutonomy() - requiredSpace));
}
}
And the final part,
the Solution class
#PlanningSolution
public class FindBestVehicleSolution
{
#PlanningEntityCollectionProperty
private List<FindBestVehicleRequest> processes;
#ProblemFactCollectionProperty
#ValueRangeProvider(id = "vehicle")
private List<Vehicle> vehicles; // <----- I'm fetching a list of active Vehicles in
// the requested pickupDate, and passing it here
#ProblemFactProperty
private String pickupAddress;
// private LatLng pickupAddressPosition; // shadow variable ? how to call the map server
// and populate this field ?
#PlanningScore
private HardSoftScore score;
...
}
Ok, so I think that all the code is here. I'm looking for suggestions on proper ways to:
- call the maps server to get diestances in an efficient way
- avoid repeating the same calculations
- (IMPORTANT!) if a Vehicle satisfy certain rules, like if it has no assigned orders in the selected day, end the evaluation process directly (vehicle found!)
Yes I'm asking too much maybe, but the documentations is a bit hard to adapt to this situation, I think that with time I will get better, but I'd like to make some simulations with Optaplanner Workbench soon :-)
Thanks for anyone that will give any kind of suggestion!
Related
I am learning about how to use OptaPlanner via solving linear programming. I know there are quite a lot of LP solvers, but I think it's a good start point for me to understand the modeling.
But When I started to work on it, I run into a problem that it looks like the entity planning variable can only be countable.
The exception is like below:
The selector is not countable, check the ValueRanges involved.
Verify that a ValueRangeProvider does not return ValueRange when it can return CountableValueRange or Collection.
Can I know:
What's the "legitimate" way to use the "DoubleValueRange"?
Is there a "best practise" for LP in OptaPlanner?
Thank you.
My solution:
#PlanningSolution
public class Solution {
#PlanningEntityProperty
private Entity entity;
#PlanningScore
private HardSoftScore score = HardSoftScore.ZERO;
public Solution(Entity entity) {
this.entity = entity;
}
#ValueRangeProvider(id = "x_1")
public ValueRange<Double> getX1Range() {
return ValueRangeFactory.createDoubleValueRange(0.0, 100.0);
}
#ValueRangeProvider(id = "x_2")
public ValueRange<Double> getX2Range() {
return ValueRangeFactory.createDoubleValueRange(0.0, 100.0);
}
...<getters setters>...
My Entity:
#PlanningEntity
public class Entity {
private long id;
#PlanningVariable(valueRangeProviderRefs = "x_1")
private Double x1;
#PlanningVariable(valueRangeProviderRefs = "x_2")
private Double x2;
...<getters setters>...
My constraints:
...
private Constraint c1(ConstraintFactory constraintFactory) {
return constraintFactory.from(Entity.class)
.filter(e -> !(3.0 * e.getX1() + 5.0 * e.getX2() < 5.0 ))
.penalize("C1", HardSoftScore.ONE_HARD);
}
private Constraint c2(ConstraintFactory constraintFactory) {
return constraintFactory.from(Entity.class)
.filter(e -> !(2.0 * e.getX1() - 6.0 * e.getX2() > 0.0 ))
.penalize("C2", HardSoftScore.ONE_HARD);
}
private Constraint min(ConstraintFactory constraintFactory) {
return constraintFactory.from(Entity.class)
.penalize("MIN", HardSoftScore.ONE_SOFT, e -> (int)(-e.getX1() + 6.0 * e.getX2()));
}
...
Looking at the return types from the ValueRangeFactory's createXXXValueRange() method set, I notice that all, apart from one, are returning a CountableValueRange. The one exception to that rule is createDoubleValueRange(), which is returning a "mere" ValueRange (ValueRange).
You error points in the same direction : the ValueRange isn't considered being a valid countable ValueRange. It makes sense to me : OptaPlanner wants to select from a set of discrete values to do its moves. So I don't think there's any "legitimate" way to use it for OptaPlanner's solver Move's.
For a reason I can't identify, I myself wouldn't take OptaPlanner as my first choice to solve LP problems, as the set of feasible solutions can be calculated. But using OptaPlanner anyhow (like in your case), I would then work with multiples (of 10, 100, 1000, ...) of discrete values (in the result calculation dividing them by 10, 100, 1000, ...). AND/BUT at the same time, I would be extremely cautious to avoid score corruption, the danger of which would never be very far away.
Out of the box, OptaPlanner currently (8.3 or lower) doesn't have any move selectors to deal with DoubleValueRanges. See the Investment Portfolio example on how to plug in your own to deal with bounds based value ranges such as DoubleValueRanges.
That being said. If your value ranges are truly continuous (so not discrete), you're indeed probably better off with an LP instead. Note that in most planning problems, value ranges are discreet because you can't send 0.75 of a vehicle/person to location A and the other 0.25 of it/him/her to location B.
See also sudo's fine answer.
Here are the relevant pieces of the code I inherited. The object "process" is the old process that is passed to the method. The object "newProcess" is what I am replacing it with, using different fields of the user's choosing.
try
{
final EntityManager em = getEntityManager();
em.getTransaction().begin();
JpaProcessDAO pDao = new JpaProcessDAO(em);
Process newProcess = pDao.findById(processId);
newProcess.setName(process.getName());
newProcess.setDataBaseVersion(process.getDataBaseVersion());
newProcess.setNotes(process.getNotes());
newProcess.setReadyForUse(process.getReadyForUse();
newProcess.setSteps(process.getSteps());
em.merge(newProcess); <---- WHERE PROBLEM OCCURS
em.persist(newProcess);
em.getTrasaction().commit();
}
RESULT: Every field that I change is changed in newProcess EXCEPT "Steps". During the merge step in the code, that list goes back to whatever the steps were in the original object "process".
Now this could be because that "Step" is an object itself, not a primitive like all of the other fields I set in "newProcess":
Mapping in Process.java
#OneToMany(mappedBy="process")
private List<Step>
// getter, setter
In Step.java there is a collection of objects, some of which are lists of nonprimitive objects themselves.
Step.java
public class Step implements Serializable {
#Id
#Column(name = "step_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int stepId;
private String duration;
private String name;
private String notes;
private Integer sort;
#OneToMany(mappedBy="step", cascade=CascadeType.REMOVE)
private List<Constituent> constituents;
#OneToMany(mappedBy="step")
private List<Reference> references;
#ManyToOne
#JoinColumn(name ="process_id")
private Process process;
#OneToMany(mappedBy="step",cascade=CascadeType.REMOVE)
private List<StepEquipment> stepEquipments;
public Step() {
}
// getters/setters
}
Does anybody know what this inherited code I have could possibly do wrong?
ADDITIONS TO CODE ON 11/29:
public T findById(final Integer id) throws CPDPersistenceExceptin {
return findByPrimaryKey(id,templateClass);
}
public T findBYPrimaryKey(Object key, Class<T> clazz) {
T t = getEntityManager().find(clazz,key);
getEntityManager.merge(t);
getEntityManager.refresh(t);
return t; <-------------- newProcess is returned by this statement.
}
newProcess does not have the steps that were in the original process,nor does it have the ProcessCategories that were in process. The Hibernate logs say
that select is going on for process_id, database_version, process_name, process_notes, and process_ready_to_use only in the merge and refresh statements.
You need to synchronize both sides of the association. In your code you're only setting newProcess.setSteps(...), but each Step doesn't set a Process. From here:
However, we still need to have both sides in sync as otherwise, we break the Domain Model relationship consistency, and the entity state transitions are not guaranteed to work unless both sides are properly synchronized.
So in other words, you would need to do something along the lines of:
newProcess.setSteps(process.getSteps());
process.getSteps().forEach(s -> s.setProcess(newProcess));
As in answer from dyslexit told you need to set the Process to each Step.
But in addition you need to have the new Steps persisted and old ones removed. You can do this manually per Step but easier way would be to alter your code a bit.
Mofify the mapping annotation in step like:
#OneToMany(mappedBy = "process", cascade=CascadeType.PERSIST, orphanRemoval=true)
private List<Step> steps;
so tell persist to cascade to Steps also and to remove all Steps that are detached from Process.
Modify the update logic:
// newProcess.setSteps(process.getSteps());
// em.merge(newProcess); <---- WHERE PROBLEM OCCURS
// em.persist(newProcess);
newProcess.getSteps().clear(); // remove old steps
newProcess.getSteps().addAll(process.getSteps()); // add new steps
// You need to set the other side of association also as below
newProcess.getSteps().forEach(s -> s.setProcess(newProcess));
// em.persist(newProcess); // not sure if needed
SO: do not REPLACE the list but instead MODIFY the original list.
ALSO: there might not be a need for any merge/persist operation (and certainly doing both in series is not something that should ever be done). But because you use mystical JpaProcessDAO I can not be sure so check that.
And also see for what those are really used, great explanation here.
I am guessing that entity manager might handle everything just fine - without persist/merge stuff -because I think you already got managed entity when called pDao.findById(processId);, that is why I have commented it out.
Another story is then the mappings you have in your Step class. Those might also need changes to persistence & cascade setting.
As a side note: have also a look at this question how you might have update done easier with ModelMapper.
I am sorry for the vague question. I am not sure what I'm looking for here.
I have a Java class, let's call it Bar. In that class is an instance variable, let's call it foo. foo is a String.
foo cannot just have any value. There is a long list of strings, and foo must be one of them.
Then, for each of those strings in the list I would like the possibility to set some extra conditions as to whether that specific foo can belong in that specific type of Bar (depending on other instance variables in that same Bar).
What approach should I take here? Obviously, I could put the list of strings in a static class somewhere and upon calling setFoo(String s) check whether s is in that list. But that would not allow me to check for extra conditions - or I would need to put all that logic for every value of foo in the same method, which would get ugly quickly.
Is the solution to make several hundred classes for every possible value of foo and insert in each the respective (often trivial) logic to determine what types of Bar it fits? That doesn't sound right either.
What approach should I take here?
Here's a more concrete example, to make it more clear what I am looking for. Say there is a Furniture class, with a variable material, which can be lots of things, anything from mahogany to plywood. But there is another variable, upholstery, and you can make furniture containing cotton of plywood but not oak; satin furniture of oak but not walnut; other types of fabric go well with any material; et cetera.
I wouldn't suggest creating multiple classes/templates for such a big use case. This is very opinion based but I'll take a shot at answering as best as I can.
In such a case where your options can be numerous and you want to keep a maintainable code base, the best solution is to separate the values and the logic. I recommend that you store your foo values in a database. At the same time, keep your client code as clean and small as possible. So that it doesn't need to filter through the data to figure out which data is valid. You want to minimize dependency to data in your code. Think of it this way: tomorrow you might need to add a new material to your material list. Do you want to modify all your code for that? Or do you want to just add it to your database and everything magically works? Obviously the latter is a better option. Here is an example on how to design such a system. Of course, this can vary based on your use case or variables but it is a good guideline. The basic rule of thumb is: your code should have as little dependency to data as possible.
Let's say you want to create a Bar which has to have a certain foo. In this case, I would create a database for BARS which contains all the possible Bars. Example:
ID NAME FOO
1 Door 1,4,10
I will also create a database FOOS which contains the details of each foo. For example:
ID NAME PROPERTY1 PROPERTY2 ...
1 Oak Brown Soft
When you create a Bar:
Bar door = new Bar(Bar.DOOR);
in the constructor you would go to the BARS table and query the foos. Then you would query the FOOS table and load all the material and assign them to the field inside your new object.
This way whenever you create a Bar the material can be changed and loaded from DB without changing any code. You can add as many types of Bar as you can and change material properties as you goo. Your client code however doesn't change much.
You might ask why do we create a database for FOOS and refer to it's ids in the BARS table? This way, you can modify the properties of each foo as much as you want. Also you can share foos between Bars and vice versa but you only need to change the db once. cross referencing becomes a breeze. I hope this example explains the idea clearly.
You say:
Is the solution to make several hundred classes for every possible
value of foo and insert in each the respective (often trivial) logic
to determine what types of Bar it fits? That doesn't sound right
either.
Why not have separate classes for each type of Foo? Unless you need to define new types of Foo without changing the code you can model them as plain Java classes. You can go with enums as well but it does not really give you any advantage since you still need to update the enum when adding a new type of Foo.
In any case here is type safe approach that guarantees compile time checking of your rules:
public static interface Material{}
public static interface Upholstery{}
public static class Oak implements Material{}
public static class Plywood implements Material{}
public static class Cotton implements Upholstery{}
public static class Satin implements Upholstery{}
public static class Furniture<M extends Material, U extends Upholstery>{
private M matrerial = null;
private U upholstery = null;
public Furniture(M matrerial, U upholstery){
this.matrerial = matrerial;
this.upholstery = upholstery;
}
public M getMatrerial() {
return matrerial;
}
public U getUpholstery() {
return upholstery;
}
}
public static Furniture<Plywood, Cotton> cottonFurnitureWithPlywood(Plywood plywood, Cotton cotton){
return new Furniture<>(plywood, cotton);
}
public static Furniture<Oak, Satin> satinFurnitureWithOak(Oak oak, Satin satin){
return new Furniture<>(oak, satin);
}
It depends on what you really want to achieve. Creating objects and passing them around will not magically solve your domain-specific problems.
If you cannot think of any real behavior to add to your objects (except the validation), then it might make more sense to just store your data and read them into memory whenever you want. Even treat rules as data.
Here is an example:
public class Furniture {
String name;
Material material;
Upholstery upholstery;
//getters, setters, other behavior
public Furniture(String name, Material m, Upholstery u) {
//Read rule files from memory or disk and do all the checks
//Do not instantiate if validation does not pass
this.name = name;
material = m;
upholstery = u;
}
}
To specify rules, you will then create three plain text files (e.g. using csv format). File 1 will contain valid values for material, file 2 will contain valid values for upholstery, and file 3 will have a matrix format like the following:
upholstery\material plywood mahogany oak
cotton 1 0 1
satin 0 1 0
to check if a material goes with an upholstery or not, just check the corresponding row and column.
Alternatively, if you have lots of data, you can opt for a database system along with an ORM. Rule tables then can be join tables and come with extra nice features a DBMS may provide (like easy checking for duplicate values). The validation table could look something like:
MaterialID UpholsteryID Compatability_Score
plywood cotton 1
oak satin 0
The advantage of using this approach is that you quickly get a working application and you can decide what to do as you add new behavior to your application. And even if it gets way more complex in the future (new rules, new data types, etc) you can use something like the repository pattern to keep your data and business logic decoupled.
Notes about Enums:
Although the solution suggested by #Igwe Kalu solves the specific case described in the question, it is not scalable. What if you want to find what material goes with a given upholstery (the reverse case)? You will need to create another enum which does not add anything meaningful to the program, or add complex logic to your application.
This is a more detailed description of the idea I threw out there in the comment:
Keep Furniture a POJO, i.e., just hold the data, no behavior or rules implemented in it.
Implement the rules in separate classes, something along the lines of:
interface FurnitureRule {
void validate(Furniture furniture) throws FurnitureRuleException;
}
class ValidMaterialRule implements FurnitureRule {
// this you can load in whatever way suitable in your architecture -
// from enums, DB, an XML file, a JSON file, or inject via Spring, etc.
private Set<String> validMaterialNames;
#Overload
void validate(Furniture furniture) throws FurnitureRuleException {
if (!validMaterialNames.contains(furniture.getMaterial()))
throws new FurnitureRuleException("Invalid material " + furniture.getMaterial());
}
}
class UpholsteryRule implements FurnitureRule {
// Again however suitable to implement/config this
private Map<String, Set<String>> validMaterialsPerUpholstery;
#Overload
void validate(Furniture furniture) throws FurnitureRuleException {
Set<String> validMaterialNames = validMaterialsPerUpholstery.get(furniture.getUpholstery();
if (validMaterialNames != null && !validMaterialNames.contains(furniture.getMaterial()))
throws new FurnitureRuleException("Invalid material " + furniture.getMaterial() + " for upholstery " + furniture.getUpholstery());
}
}
// and more complex rules if you need to
Then have some service along the lines of FurnitureManager. It's the "gatekeeper" for all Furniture creation/updates:
class FurnitureManager {
// configure these via e.g. Spring.
private List<FurnitureRule> rules;
public void updateFurniture(Furniture furniture) throws FurnitureRuleException {
rules.forEach(rule -> rule.validate(furniture))
// proceed to persist `furniture` in the database or whatever else you do with a valid piece of furniture.
}
}
material should be of type Enum.
public enum Material {
MAHOGANY,
TEAK,
OAK,
...
}
Furthermore you can have a validator for Furniture that contains the logic which types of Furniture make sense, and then call that validator in every method that can change the material or upholstery variable (typically only your setters).
public class Furniture {
private Material material;
private Upholstery upholstery; //Could also be String depending on your needs of course
public void setMaterial(Material material) {
if (FurnitureValidator.isValidCombination(material, this.upholstery)) {
this.material = material;
}
}
...
private static class FurnitureValidator {
private static boolean isValidCombination(Material material, Upholstery upholstery) {
switch(material) {
case MAHOGANY: return upholstery != Upholstery.COTTON;
break;
//and so on
}
}
}
}
We often are oblivious of the power inherent in enum types. The Java™ Tutorials clearly states "you should use enum types any time you need to represent a fixed set of constants."
How do you simply make the best of enum in resolving the challenge you presented? - Here goes:
public enum Material {
MAHOGANY( "satin", "velvet" ),
PLYWOOD( "leather" ),
// possibly many other materials and their matching fabrics...
OAK( "some other fabric - 0" ),
WALNUT( "some other fabric - 0", "some other fabric - 1" );
private final String[] listOfSuitingFabrics;
Material( String... fabrics ) {
this.listOfSuitingFabrics = fabrics;
}
String[] getListOfSuitingFabrics() {
return Arrays.copyOf( listOfSuitingFabrics );
}
public String toString() {
return name().substring( 0, 1 ) + name().substring( 1 );
}
}
Let's test it:
public class TestMaterial {
for ( Material material : Material.values() ) {
System.out.println( material.toString() + " go well with " + material.getListOfSuitingFabrics() );
}
}
Probably the approach I'd use (because it involves the least amount of code and it's reasonably fast) is to "flatten" the hierarchical logic into a one-dimensional Set of allowed value combinations. Then when setting one of the fields, validate that the proposed new combination is valid. I'd probably just use a Set of concatenated Strings for simplicity. For the example you give above, something like this:
class Furniture {
private String wood;
private String upholstery;
/**
* Set of all acceptable values, with each combination as a String.
* Example value: "plywood:cotton"
*/
private static final Set<String> allowed = new HashSet<>();
/**
* Load allowed values in initializer.
*
* TODO: load allowed values from DB or config file
* instead of hard-wiring.
*/
static {
allowed.add("plywood:cotton");
...
}
public void setWood(String wood) {
if (!allowed.contains(wood + ":" + this.upholstery)) {
throw new IllegalArgumentException("bad combination of materials!");
}
this.wood = wood;
}
public void setUpholstery(String upholstery) {
if (!allowed.contains(this.wood + ":" + upholstery)) {
throw new IllegalArgumentException("bad combination of materials!");
}
this.upholstery = upholstery;
}
public void setMaterials(String wood, String upholstery) {
if (!allowed.contains(wood + ":" + upholstery)) {
throw new IllegalArgumentException("bad combination of materials!");
}
this.wood = wood;
this.upholstery = upholstery;
}
// getters
...
}
The disadvantage of this approach compared to other answers is that there is no compile-time type checking. For example, if you try to set the wood to plywoo instead of plywood you won’t know about your error until runtime. In practice this disadvantage is negligible since presumably the options will be chosen by a user through a UI (or through some other means), so you won’t know what they are until runtime anyway. Plus the big advantage is that the code will never have to be changed so long as you’re willing to maintain a list of allowed combinations externally. As someone with 30 years of development experience, take my word for it that this approach is far more maintainable.
With the above code, you'll need to use setMaterials before using setWood or setUpholstery, since the other field will still be null and therefore not an allowed combination. You can initialize the class's fields with default materials to avoid this if you want.
I have this relationship in one table like this:
#ManyToOne
#JoinColumn(name="TEAM_HOME", nullable=false)
private Team team;
#Column(name="TEAM_AWAY", insertable=false, updatable=false)
private int teamAway;
And this relationship in other:
#XmlTransient
#OneToMany(mappedBy="team",fetch=FetchType.EAGER)
private Set<Result> result;
I want to reference one column from table Team multiple times in table Result, bet I don't know how. I have tried different ways but without success.
You will need two Relations from Team to Result:
#OneToMany(mappedBy="teamHome",...)
private Set<Result> resultsHome;
#OneToMany(mappedBy="teamAway",...)
private Set<Result> resultsAway;
but nothing stops you from adding methods like
public Set<Result> getResults() {
Set<Result> results = new HashSet<>();
results.addAll(resultsHome);
results.addAll(resultsAway);
return results;
}
public void addResult(Result result) {
if (result.teamHome == this) {
resultsHome.add(result);
} else {
resultsAway.add(result);
}
}
I guess this is not what you were hoping for. But putting all Results in the same collection would not be correct as it is something different. After all, you would probably want something like the inverse result for your "away"-matches in that collection.
You should really ask yourself if you really want those 2 Relations in your Team class. Wouldn't a DAO.getMatchesForTeam(...) serve the same purpose? When you really want to make a model of sports events, you might end up with plenty of relations in Team and it will clutter up your code. The #XMLTransient annotation already indicates that the results aren't that important to the team for them to be transported to the client.
Oh and btw: There is something between team and result: the match. The result should only hold the result. That way you can make a Result.inverse() method that will give you the result from the other team's perspective. But there is nothing like a Match.inverse() because the home team will always be the home team.
I'm trying to merge these three objects into a single complex object:
public class Person {
private String name;
private List<Event> events;
// getters and setters
}
public class Event {
private String name;
private List<Gift> gifts;
// getters and setters
}
public class Gift {
private String name;
private String recipient;// the name of the person
private String eventName;
// getters and setters
}
My goal is to save the Person object in MongoDB using Morphia and this how I want my document laid out. I've created a document builder, of sorts, that combines lists of each object. Each Person gets a list of all Events, but can only receive specific Gifts. While my document builder does create a document that Morphia can persist, only the Gifts of that last recipient (sort order) are inserted into the Events for all Persons. Though for the correct Events.
public void merge() {
for (Person person : listOfPersons) {
for (Event event : listOfEvents) {
// somePersonsGifts: a sublist of gifts based on Event and Person.
List<Gift> somePersonsGifts = new ArrayList<Gift>();
for (Gift gift : listOfGifts) {
if (person.getName().equals(gift.getRecipient()) && gift.getEventName().equals(event.getName())) {
somePersonsGifts.add(gift);
}
}
event.setGifts(somePersonsGifts);
}
person.setEvents(listOfEvents)
}
}
If I modify the code slightly to process one person at a time by removing the outer loop and having the method take an argument for specific index of the Persons list:
public void merge(int p) {
Person person = listOfPersons.get(p);
//...and so on
I get one complete Person object with the correct gifts. If try to feed the this modified version into a loop, the problem comes back. I've tried using regular for-loops and synchronized collections. I've tried using Google Guava's ImmutableArrayList and still no luck. I know the problem is that I'm changing the lists while accessing them but I can't find anyway around it. I wrote a DAO that uses the MongoDB driver directly and it works properly, but it's a lot more code and quite ugly. I really want this approach to work, the answer is in front of me but I just can't see it. Any help would be greatly appreciated.
Here is your problem:
List<Gift> somePersonsGifts = new ArrayList<Gift>();
....
event.setGifts(somePersonsGifts);
You add the gifts only for one person; if you want to aggregate all the gifts into the event, re-use the existing list.
I don't know anything about MongoDB or Morphia but I suspect the problem is your use of the setters event.setGifts(somePersonsGifts) and person.setEvents(events). Your code does not seem to merge the existing gift and event lists with the ones you are calculating further in the loop, which is how you would want it to behave (if I understand the question correctly).
You should retrieve the allready existing gift list (and event list too) instead of overwriting them with empty new ones.
I don't know if the method merge() is inside the list but I assume that since you are using the list events here
person.setEvents(events);
Maybe you meant
person.setEvents(listOfEvents)
Notice that you are adding all the events to each person. If all the persons went to all the events, it is unnecessary to have the events inside the person.