Is it bad practice to use StaticMetamodel (JPA) inside ServiceImpl? - java

I am rewriting a huge part of an existing application. We have Group an SuperGroup.
Actualy, the groupe can only store one stringValue.
To respect the new scope of the project we want the Group to store multiple value like date, number and boolean. The type of the Group depend on a value that is store inside the SuperGroup. (It also store the format of the group value, but this is not important)
One of the purpose of the application is to search by group value. And here is my problem.
In a service I need to implement the search that will call a DAO and determine on witch value I have to made the search.
My question is in the title. Is it bad practice to use StaticMetamodel (JPA) inside ServiceImpl?
To illustrate my question, here is the service method.
public Groupe getGroupeBySuperGroupeAndEntrepriseAndValue(SuperGroupe superGroupe, Entreprise entreprise, String value) {
if (entreprise == null || StringUtils.isBlank(value)) {
return null;
}
GroupeDataTypeEnum groupeDataTypeEnum = GroupeDataTypeEnum.getGroupeDataTypeByTypeValue(superGroupe.getDataType());
Object finalValue;
SingularAttribute singularAttribute;
switch (groupeDataTypeEnum) {
case STRING_DATA_TYPE:
finalValue = value;
singularAttribute = Groupe_.stringValue;
break;
case NUMBER_DATA_TYPE:
finalValue = FormatUtils.formatStringToDouble(superGroupe.getDataFormat(), value);
singularAttribute = Groupe_.numberValue;
break;
case DATE_DATA_TYPE:
finalValue = FormatUtils.formatStringToDate(superGroupe.getDataFormat(), value);
singularAttribute = Groupe_.dateValue;
break;
case BOOLEAN_DATA_TYPE:
finalValue = FormatUtils.formatStringToBoolean(superGroupe.getDataFormat(), value);
singularAttribute = Groupe_.booleanValue;
break;
default :
finalValue = null;
break;
}
return groupeDAO.getGroupeBySuperGroupeAndEntrepriseAndValue(superGroupe, entreprise, finalValue, singularAttribute);
}
And in the DAO the search is made on the singular attribut passed in parameter.
My problem is purely conceptual. Is it OK to use SingularAttribute that is generated by org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor inside the service. If we left Hibernate I could lost that StaticMetamodel, so we will have bugs. But we can regenerate them because StaticMetamodel is Java stanndard.
Is there a better way to do what I want here?
I need some help to clear my mind. Thank you by advance, any return is welcome.

Related

Prevent coding multiple if/else statements with two changing expressions

I'm a beginner APEX developer (language based off of Java) and I was wondering if there is an efficient way to write conditional statements where two conditions change while the rest remains static.
For instance, in my code below, Countryy__c will change to say UK, US and Canada (besides France) and for each of these countries the Industry will change from Accounting to Medicine to Legal. Meanwhile, lead type and status will always remain as outbound and open respectively. Moreover each Country and Industry combination has a unique 'Owner ID'.
So in other words, there will be a total of 12 if/else statements with 12 different OwnerIds. Given that the code will be messy to maintain in the future if the number of countries and industries grow, is there a better way of coding this?
public static void changeOwnerToQueue(List<String> DeactivatedUserIds){
List<Lead> leadList = new List<Lead>();
List<lead> updatedQueue = new List<Lead>();
leadList = [SELECT Id, OwnerId, Countryy__c, Industry__c, Lead_Type__c, Status from lead
where OwnerId IN :DeactivatedUserIds];
for(Lead l : leadList){
if(l.Countryy__c == 'France' && l.Industry__c == 'Accounting' && l.Lead_Type__c == 'Outbound' && l.Status == 'Open'){
l.OwnerId = '00G5J000000pX41';
updatedQueue.add(l);
}
}
The most maintainable pattern for this kind of mapping in Apex is to use Custom Metadata. You'd create some Custom Metadata Type (MyOwnerMap__mdt), with fields for Country__c, Industry__c, and Owner__c. You'd create Custom Metadata records to represent all of your mappings. Then, in your code, you'd pull that data to create a Map, using a custom class as a key to represent the unique mapping of Country + Industry -> Owner:
class OwnerMapKey {
public String industry;
public String country;
public OwnerMapKey(String ind, String ctry) {
this.industry = ind;
this.country = ctry;
}
public Boolean equals(Object other) {
if (other instanceof OwnerMapKey) {
OwnerMapKey o = (OwnerMapKey)other;
return this.industry == o.industry && this.country == o.country;
}
return false;
}
public Integer hashCode() {
return (this.industry + this.country).hashCode();
}
}
List<MyOwnerMap__mdt> ownerMapValues = MyOwnerMap__mdt.getAll().values();
Map<OwnerMapKey, Id> ownerMap = new Map<OwnerMapKey, Id>();
for (MyOwnerMap__mdt eachOwnerMap: ownerMapValues) {
ownerMap.put(new OwnerMapKey(eachOwnerMap.Industry__c, eachOwnerMap.Country__c), eachOwnerMap.Owner__c);
}
Then, you can easily access the desired Owner value for any combination of Industry and Country. Note that you'll probably want to have a fallback if that entry is missing from your Custom Metadata.
someRecord.OwnerId = ownerMap.get(new OwnerMapKey(SOME_INDUSTRY, SOME_COUNTRY)) || defaultOwner;
(Disclaimer: above code written directly in Stack Overflow and untested).
The reason this pattern is valuable is that your solution then becomes admin-maintainable: you can change the mapping with no code changes and no deployment, just by altering the Custom Metadata records.

How to avoid multiple Switch case statements

I need to refactor a code that uses multiple case statements based on the queryString value it receives. Everytime a new query is added , we had to add a new case statement. Can we try to simplify it and make it more generic so that we dont have to keep on adding new case statements? Below is the code snippet
switch (queryRequestRecevied) {
case query1:
reply = processQuery1();
break;
case query2:
reply = processQuery2();
break;
case query3:
reply = processQuery3();
break;
case query4:
reply = processQuery4();
break;
case query5:
reply = processQuery5();
break;
case query6:
reply = processQuery6();
break;
default:
reply = processInvalidQuery();
break;
}
The name for the operation you're looking for is Replace Conditional with Polymorphism: Instead of having switch or if statements, use a common interface for your cases and invoke that. I'm presuming here that your query returns some value and using Supplier; if not, use another functional interface or create your own.
Map<String, Supplier<QueryResult>> queryProcessors = new HashMap<>();
...
// in a constructor or similar place
queryProcessors.put(query1, this::processQuery1);
queryProcessors.put(query2, this::processQuery2);
// in your handler method
var result = queryProcessors.getOrDefault(query, this::processInvalidQuery).get();
To decouple even further, you can do this:
// declare some Spring beans implementing this interface
interface QueryProcessor {
String getQuery();
QueryResult processQuery();
}
// in your service class
Map<String, QueryProcessor> queryProcessors;
#Autowired //(not necessary for a single constructor)
MyService(Collection<QueryProcessor> queryProcessors) {
this.queryProcessors = queryProcessors.stream()
.collect(toMap(QueryProcessor::getQuery, identity()));
}
You don't give enough code to see what could be factorized / more generic.
But at least instead of testing your input query you could declare 1 endpoint per query in your #Controller then no need to Switch / case testing.

Long dynamic routes in play framework 2

I am developing an app for showing different aspects of cars. The app has a tree-like structure like this:
Country>Manufacturer>Model>Car_subsystem>Part
I want this structure to be reflected in browser's address bar:
http://localhost:9000/Germany/BMW/X6/Body/doors
Currently, I do it with play framework's dynamic routing as below:
GET /:Country controllers.Application.controllerFunction(Country : String)
GET /:Country/:Manufacturer controllers.Application.controllerFunction(Country : String, Manufacturer : String)
etc.
This works, but I don't like passing around 5 or 6 parameters to all my controller functions just for the paths to be shown beautifully! Is there any other way?
Just use Dynamic parts spanning several / as described in routing doc
route:
GET /Cars/*path controllers.Application.carResolver(path)
Action (simplest approach)
public static Result carResolver(String path) {
Car car = Car.findByPath(path);
return ok(carView.render(car));
}
so each car should have its field path filled with unique string ID, ie: Germany/BMW/X6, Germany/Mercedes/ASL` etc.
Of course it will be much better if you split the path arg by the slash first so you can use each part for displaying different views, 'translate' strings to real object ID etc etc.
public static Result carResolver(String path) {
String[] parts = path.split("/");
int partsLength = parts.length;
String country = (partsLength > 0) ? parts[0] : null;
String manufacturer = (partsLength > 1) ? parts[1] : null;
String carModel = (partsLength > 2) ? parts[2] : null;
// etc
switch (partsLength){
case 0: return countrySelect();
case 1: return allManufacturersIn(country);
case 2: return allModelsOf(manufacturer);
case 3: return singleViewFor(carModel);
// etc
}
return notFound("Didn't find anything for required path...");
}
TIP: "translating" strings to objects will require from you searching in DB by some field, so there's some advices:
Try to make sure that each model has a unique field for searching ie. Country should have unique name as probably you don't want to have Germany 1, Germany 2 etc.
This approach requires probably more time than searching by numeric ID's so try to cache somehow (mem-cache OR at least dedicated DB table) the result of resolving ie.:
Germany/BMW/X6 = Country: 1, Manufacturer: 2, CarModel: 3, action: "singleViewFor"

Possible to have a global object with synchronized access in Google App Engine?

I'm working on an App that has objects that must be available to all instances but also have synchronized access for certain methods within the object.
For instance I have this object:
public class PlanetID implements Serializable {
public PlanetID() {
id = 0;
}
public long generateID() {
id++;
return id;
}
private long id;
}
It's a simple object that creates a long (id) in series. It's necessary that this object generate a unique id every time. At the moment I have a static synchronized method that handles the Datastore access and storage along with the MemCache access and storage. It works for this particular method but I can already see issues with more complex objects that require a user to be able to access non-synchronized variables along with synchronized variables.
Is there some way to make an object global and allow for both synchronized methods and non-synchronized methods along with the storage of the object when those synchronized objects are accessed?
EDIT: I think people focused too much on the example I gave them and not on the bigger question of having a global variable which can be accessed by all instances and having synchronized access to specific methods while allowing asynchronous access to others.
Here's a better example in hopes it makes things a big more clearer.
Ex.
public class Market implements Serializable {
public Market() {
mineral1 = new ArrayList<Listing>();
mineral2 = new ArrayList<Listing>();
mineral3 = new ArrayList<Listing>();
mineral4 = new ArrayList<Listing>();
}
public void addListing(int mineral, String userID, int price, long amount) { //Doesn't require synchronized access
switch (mineral) {
case MINERAL1:
mineral1.add(new Listing(userID, price, amount));
break;
case MINERAL2:
mineral2.add(new Listing(userID, price, amount));
break;
case MINERAL3:
mineral3.add(new Listing(userID, price, amount));
break;
case MINERAL4:
mineral4.add(new Listing(userID, price, amount));
break;
}
}
public void purchased(int mineral, String userID, long amount) { //Requires synchronized access
ArrayList<Listing> mineralList = null;
switch (mineral) {
case MINERAL1:
mineralList = mineral1;
break;
case MINERAL2:
mineralList = mineral2;
break;
case MINERAL3:
mineralList = mineral3;
break;
case MINERAL4:
mineralList = mineral4;
break;
}
Listing remove = null;
for (Listing listing : mineralList)
if (listing.userID == userID)
if (listing.amount > amount) {
listing.amount -= amount;
return;
} else{
remove = listing;
break;
}
mineralList.remove(remove);
Collections.sort(mineralList);
}
public JSONObject toJSON(int mineral) { //Does not require synchronized access
JSONObject jsonObject = new JSONObject();
try {
switch (mineral) {
case MINERAL1:
for (Listing listing : mineral1)
jsonObject.accumulate(Player.MINERAL1, listing.toJSON());
break;
case MINERAL2:
for (Listing listing : mineral2)
jsonObject.accumulate(Player.MINERAL2, listing.toJSON());
break;
case MINERAL3:
for (Listing listing : mineral3)
jsonObject.accumulate(Player.MINERAL3, listing.toJSON());
break;
case MINERAL4:
for (Listing listing : mineral4)
jsonObject.accumulate(Player.MINERAL4, listing.toJSON());
break;
}
} catch (JSONException e) {
}
return jsonObject;
}
public static final int MINERAL1 = 0;
public static final int MINERAL2 = 1;
public static final int MINERAL3 = 2;
public static final int MINERAL4 = 3;
private ArrayList<Listing> mineral1;
private ArrayList<Listing> mineral2;
private ArrayList<Listing> mineral3;
private ArrayList<Listing> mineral4;
private class Listing implements Serializable, Comparable<Listing> {
public Listing(String userID, int price, long amount) {
this.userID = userID;
this.price = price;
this.amount = amount;
}
public JSONObject toJSON() {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("UserID", userID);
jsonObject.put("Price", price);
jsonObject.put("Amount", amount);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return jsonObject;
}
#Override
public int compareTo(Listing listing) {
return (price < listing.price ? -1 : (price == listing.price ? 0 : 1));
}
public String userID;
public int price;
public long amount;
}
}
With GAE, the Java language is NOT going to hide all the datastore abstractions for you.
Stop thinking in terms of global variables and methods. These are Java language constructs. Start thinking in terms of datastore constructs - entities, datastore accesses, and transactions.
On GAE, your code will be simultaneously running on many servers, they will not share global variables, the "shared data" is in the datastore (or memcache)
An entity is an object in the datastore. You can make datastore fetches from anywhere in your code, so they can replace your global variables. You define transactions within your methods to synchronize datastore accesses and ensure a transaction only happens once. You can use transactions in some methods, and don't use transactions when you don't need synchronization.
You shouldn't need your global ArrayLists of minerals. When you handle a purchase, you essentially need a transaction where you fetch a listing from the datastore, or create it if it doesn't exist, update the user, and write it back to the datastore. You probably want to read up on the datastore before continuing.
Other approach beside transaction is to use a single backend instance to keep your global object, and have all access to the object synchronized there. All other instances need to access this backend instance using URLFetch to get the state of the object.
This is a horrible performance bottleneck though if your app want to scale up smoothly, please don't use it, I'm just pointing out alternative approaches. In fact, if possible, please kindly avoid the need to have a synchronized global object on a distributed application in the first place.
Have a look at DatastoreService.allocateIds - it doesn't matter whether or not you're actually using the generated ID to write a datastore entity, you can get unique long numbers out of this in an efficient manner.
Please note however that they're not guaranteed to be in sequence, they're only guaranteed to be unique - the question doesn't state being sequential as a requirement.
public class PlanetID implements Serializable
{
private DatastoreService ds;
public PlanetID()
{
ds = DatastoreServiceFactory.getDatastoreService();
}
public long generateID()
{
return ds.allocateIds("Kind_That_Will_Never_Be_Used", 1L).getStart().getId();
}
}
As noted in comments - you can use transactions to achieve this:
Start a transaction
Create or update the SomeObject entity with index and someText properties.
Commit transaction. If two instances are trying to do this simultaneously then one will get an exception and will need to retry (get the data again, increment, put all in transaction).
Edit:
(removed section on sharded counters as they do not guarantee )
Note that above solution has a write bottleneck at about 1 write/s. If you need a higher performance solution you could look into using backend instances.
Whilst technically possible, you should heed the advice of other answers and use the services supplied by Google, such as datastore and memcache.
However, you could use a single backend which contains your data, then use your favourite RPC method to read and write data into the shared object. You will need to be aware that although it doesn't happen often, backends are not guaranteed not to die randomly - so you could lose all the data in this object.

lucene ignore queries on fields other than default

i have 2 indexes, one for meta data and one for text, i want to be able to remove all field searches in the query and only use the default fields that the user searched, ie "help AND title:carpool" i want only the help part, ideas?
Traverse over tree of BooleanQuery and remove entries related Term("help")
This is a ballpark of what your code should look like:
public static void removeNonDefault(BooleanQuery query, String defaultField) {
List<BooleanClause> clauses = (List<BooleanClause>)query.clauses();
Iterator<BooleanClause> iter = clauses.iterator();
while(iter.hasNext()) {
BooleanClause clause = iter.next();
Query subQuery = clause.getQuery();
if(subQuery instanceof BooleanQuery) {
removeNonDefault((BooleanQuery)subQuery, defaultField);
} else if(subQuery instanceof TermQuery) {
if (!((TermQuery) subQuery).getTerm().field().equals(defaultField)) {
iter.remove();
}
}
}
}
What this does is removes TermQuerys with the non-default field from the BooleanQuery, and recurses down into sub-boolean queries.
Note that this code is not complete. Depending on your situation, there might be more types of queries you should worry about, like phrase queries and constant score range queries.
Make sure to do query.rewrite() before you call this function, to convert any wildcard queries to boolean queries.

Categories

Resources