I am using Spring Boot (v2.1.3 RELEASE) and SpringDoc. I already went through https://springdoc.org/springdoc-properties.html and https://springdoc.org/, but it looks like SpringDoc is automatically sorting the parameters alphabetically. How can we prevent this?
#Operation(summary = "Find Students")
#Parameter(in=ParameterIn.QUERY, name="page", description="Results page you want to retrieve (0..N)", schema=#Schema(defaultValue = 0))
#Parameter(in=ParameterIn.QUERY, name="size", description="Number of records per page.", schema=#Schema(defaultValue =50))
#Parameter(in=ParameterIn.QUERY, name="search_type", description=AppConstants.SEARCH_TYPE, schema=#Schema(allowableValues= {"Starts", "Contains"},defaultValue = "Starts"))
#ApiCountryHeader
#GetMapping(value = "/students")
public ResponseEntity<List<Students>> findStudentss(
#Parameter(description = "") #RequestParam(required = false) String studentCd,
#Parameter(description = "") #RequestParam(required = false) String studentName,
#Parameter(hidden=true) String search_type){
....
....
...
return new ResponseEntity<>(studentts, HttpStatus.OK);
}
Fields are not sorted alphabetically, but the order of declaration is preserved.
You can change the fields order, using the different available customizers:
OpenApiCustomiser: To customize the OpenAPI object
OperationCustomizer: To customize an operation based on the HandlerMethod
For example:
#RestController
public class HelloController {
#GetMapping(value = "/persons")
public String getPerson(String b, String a) {
return null;
}
#Bean
OperationCustomizer operationCustomizer() {
return (Operation operation, HandlerMethod handlerMethod) -> {
if ("getPerson".equals(handlerMethod.getMethod().getName())) {
List<Parameter> parameterList = operation.getParameters();
if (!CollectionUtils.isEmpty(parameterList))
Collections.reverse(parameterList);
}
return operation;
};
}
}
Related
Suppose a JSON column called blob in a MySQL 5.7 database table called "Thing" with the following content:
[
{
"id": 1,
"value": "blue"
},
{
"id": 2,
"value": "red"
}
]
Is it possible to select all records from Thing where the blob contains an object within the array where the id is some dynamic value and the value is also some dynamic value.
E.g. "give me all Things where the blob contains an object whose id is 2 and value is 'red'"
Not sure how to form the WHERE clause below:
SET #id = 2;
SET #value1 = 'red';
SET #value2 = 'blue';
-- with equals?
SELECT *
FROM Thing
WHERE JSON_EXTRACT(blob, '$[*].id ... equals #id ... and .value') = #value1;
-- with an IN clause?
SELECT *
FROM Thing
WHERE JSON_EXTRACT(blob, '$[*].id ... equals #id ... and .value') IN (#value1, #value2);
They said it couldn't be done. They told me I was a fool. Lo and behold, I have done it!
Here are some helper functions to teach Hibernate how to perform JSON functions against a MySQL 5.7 backend, and an example use case. It's confusing, for sure, but it works.
Context
This contrived example is of a Person entity which can have many BioDetails, which is a question/answer type (but is more involved than that). The example within below essentially is searching within two JSON payloads, grabbing JSON values from one to build JSON paths within which to search in the other. In the end, you can pass in a complex structure of AND'd or OR'd criteria, which will be applied against a JSON blob and return only the resulting rows which match.
E.g. give my all Person entities where their age is > 30 and their favorite color is blue or orange. Given that those key/value pairs are stored in a JSON blob, you can find those matched using the example code below.
JSON-search classes and example repo
Classes below use Lombok for brevity.
Classes to allow specification of search criteria
SearchCriteriaContainer
#Data
#EqualsAndHashCode
public class SearchCriteriaContainer
{
private List<SearchCriterion> criteria;
private boolean and;
}
SearchCriterion
#Data
#EqualsAndHashCode(callSuper = true)
public class SearchCriterion extends SearchCriteriaContainer
{
private String field;
private List<String> values;
private SearchOperator operator;
private boolean not = false;
}
SearchOperator
#RequiredArgsConstructor
public enum SearchOperator
{
EQUAL("="),
LESS_THAN("<"),
LESS_THAN_OR_EQUAL("<="),
GREATER_THAN(">"),
GREATER_THAN_OR_EQUAL(">="),
LIKE("like"),
IN("in"),
IS_NULL("is null");
private final String value;
#JsonCreator
public static SearchOperator fromValue(#NotBlank String value)
{
return Stream
.of(SearchOperator.values())
.filter(o -> o.getValue().equals(value))
.findFirst()
.orElseThrow(() ->
{
String message = String.format("Could not find %s with value: %s", SearchOperator.class.getName(), value);
return new IllegalArgumentException(message);
});
}
#JsonValue
public String getValue()
{
return this.value;
}
#Override
public String toString()
{
return value;
}
}
Helper class which is used to call JSON functions
#RequiredArgsConstructor
public class CriteriaBuilderHelper
{
private final CriteriaBuilder criteriaBuilder;
public Expression<String> concat(Expression<?>... values)
{
return criteriaBuilder.function("CONCAT", String.class, values);
}
public Expression<String> substringIndex(Expression<?> value, String delimiter, int count)
{
return substringIndex(value, criteriaBuilder.literal(delimiter), criteriaBuilder.literal(count));
}
public Expression<String> substringIndex(Expression<?> value, Expression<String> delimiter, Expression<Integer> count)
{
return criteriaBuilder.function("SUBSTRING_INDEX", String.class, value, delimiter, count);
}
public Expression<String> jsonUnquote(Expression<?> jsonValue)
{
return criteriaBuilder.function("JSON_UNQUOTE", String.class, jsonValue);
}
public Expression<String> jsonExtract(Expression<?> jsonDoc, Expression<?> path)
{
return criteriaBuilder.function("JSON_EXTRACT", String.class, jsonDoc, path);
}
public Expression<String> jsonSearchOne(Expression<?> jsonDoc, Expression<?> value, Expression<?>... paths)
{
return jsonSearch(jsonDoc, "one", value, paths);
}
public Expression<String> jsonSearch(Expression<?> jsonDoc, Expression<?> value, Expression<?>... paths)
{
return jsonSearch(jsonDoc, "all", value, paths);
}
public Expression<String> jsonSearch(Expression<?> jsonDoc, String oneOrAll, Expression<?> value, Expression<?>... paths)
{
if (!"one".equals(oneOrAll) && !"all".equals(oneOrAll))
{
throw new RuntimeException("Parameter 'oneOrAll' must be 'one' or 'all', not: " + oneOrAll);
}
else
{
final var expressions = new ArrayList<>(List.of(
jsonDoc,
criteriaBuilder.literal(oneOrAll),
value,
criteriaBuilder.nullLiteral(String.class)));
if (paths != null)
{
expressions.addAll(Arrays.asList(paths));
}
return criteriaBuilder.function("JSON_SEARCH", String.class, expressions.toArray(Expression[]::new));
}
}
}
Utility to turn SearchCriteria into MySQL JSON function calls
SearchHelper
public class SearchHelper
{
private static final Pattern pathSeparatorPattern = Pattern.compile("\\.");
public static String getKeyPart(String key)
{
return pathSeparatorPattern.split(key)[0];
}
public static String getPathPart(String key)
{
final var parts = pathSeparatorPattern.split(key);
final var path = new StringBuilder();
for (var i = 1; i < parts.length; i++)
{
if (i > 1)
{
path.append(".");
}
path.append(parts[i]);
}
return path.toString();
}
public static Optional<Predicate> getCriteriaPredicate(SearchCriteriaContainer container, CriteriaBuilder cb, Path<String> bioDetailJson, Path<String> personJson)
{
final var predicates = new ArrayList<Predicate>();
if (container != null && container.getCriteria() != null && container.getCriteria().size() > 0)
{
final var h = new CriteriaBuilderHelper(cb);
container.getCriteria().forEach(ac ->
{
final var groupingOnly = ac.getField() == null && ac.getOperator() == null;
// a criterion can be used for grouping other criterion, and might not have a field/operator/value
if (!groupingOnly)
{
final var key = getKeyPart(ac.getField());
final var path = getPathPart(ac.getField());
final var bioDetailQuestionKeyPathEx = h.jsonUnquote(h.jsonSearchOne(bioDetailJson, cb.literal(key), cb.literal("$[*].key")));
final var bioDetailQuestionIdPathEx = h.concat(h.substringIndex(bioDetailQuestionKeyPathEx, ".", 1), cb.literal(".id"));
final var questionIdEx = h.jsonUnquote(h.jsonExtract(bioDetailJson, bioDetailQuestionIdPathEx));
final var answerPathEx = h.substringIndex(h.jsonUnquote(h.jsonSearchOne(personJson, questionIdEx, cb.literal("$[*].questionId"))), ".", 1);
final var answerValuePathEx = h.concat(answerPathEx, cb.literal("." + path));
final var answerValueEx = h.jsonUnquote(h.jsonExtract(personJson, answerValuePathEx));
switch (ac.getOperator())
{
case IN:
{
final var inEx = cb.in(answerValueEx);
if (ac.getValues() == null || ac.getValues().size() == 0)
{
throw new RuntimeException("No values provided for 'IN' criteria for field: " + ac.getField());
}
else
{
ac.getValues().forEach(inEx::value);
}
predicates.add(inEx);
break;
}
case IS_NULL:
{
predicates.add(cb.isNull(answerValueEx));
break;
}
default:
{
if (ac.getValues() == null || ac.getValues().size() == 0)
{
throw new RuntimeException("No values provided for '" + ac.getOperator() + "' criteria for field: " + ac.getField());
}
else
{
ac.getValues().forEach(value ->
{
final var valueEx = cb.literal(value);
switch (ac.getOperator())
{
case EQUAL:
{
predicates.add(cb.equal(answerValueEx, valueEx));
break;
}
case LESS_THAN:
{
predicates.add(cb.lessThan(answerValueEx, valueEx));
break;
}
case LESS_THAN_OR_EQUAL:
{
predicates.add(cb.lessThanOrEqualTo(answerValueEx, valueEx));
break;
}
case GREATER_THAN:
{
predicates.add(cb.greaterThan(answerValueEx, valueEx));
break;
}
case GREATER_THAN_OR_EQUAL:
{
predicates.add(cb.greaterThanOrEqualTo(answerValueEx, valueEx));
break;
}
case LIKE:
{
predicates.add(cb.like(answerValueEx, valueEx));
break;
}
default:
throw new RuntimeException("Unsupported operator during snapshot search: " + ac.getOperator());
}
});
}
}
}
}
// iterate nested criteria
getAnswerCriteriaPredicate(ac, cb, bioDetailJson, personJson).ifPresent(predicates::add);
});
return Optional.of(container.isAnd()
? cb.and(predicates.toArray(Predicate[]::new))
: cb.or(predicates.toArray(Predicate[]::new)));
}
else
{
return Optional.empty();
}
}
}
Example JPA Specification repository / search method
ExampleRepository
#Repository
public interface PersonRepository extends JpaSpecificationExecutor<Person>
{
default Page<Person> search(PersonSearchDirective directive, Pageable pageable)
{
return findAll((person, query, cb) ->
{
final var bioDetail = person.join(Person_.bioDetail);
final var bioDetailJson = bioDetail.get(BioDetailEntity_.bioDetailJson);
final var personJson = person.get(Person_.personJson);
final var predicates = new ArrayList<>();
SearchHelper
.getCriteriaPredicate(directive.getSearchCriteria(), cb, bioDetailJson, personJson)
.ifPresent(predicates::add);
return cb.and(predicates.toArray(Predicate[]::new));
}, pageable);
}
}
I have a document stored in Couchbase.
{
"a": {
"b": {
"key":"Value"
},
"_class":"com.nikhil.model"
},
"c":{
"d":{
"key":"value"
},
// _class is missing here
},
"_class": "com.nikhil.model"
}
Here as you can see I don't have an _class inside the "d" in the doucument because of this I am not able to get this document. An object mapping exception came.
_class is used to map the nested object of couchbase to the model required for mapping but inside the "c" object I don't have this _Class property that is why a mapping exception comes.
Is there any fix for this?
If you are using Spring boot, you need to override the typekey() method in the Couchbase Config file which extends AbstractCouchbaseConfiguration and return MappingCouchbaseConverter.TYPEKEY_SYNCGATEWAY_COMPATIBLE. This will replace your _class with javaClass string in the documents stored in Couchbase Server. I hope this helps.
#Configuration
public class RemoteCouchbaseConfiguration extends AbstractCouchbaseConfiguration {
#Value("${couchbase.host}")
private String host;
#Value("${couchbase.bucket.bucketName}")
private String bucketName;
#Value("${couchbase.bucket.password}")
private String password;
#Override
protected List<String> getBootstrapHosts() {
return Arrays.asList(this.host);
}
#Override
protected String getBucketName() {
return this.bucketName;
}
#Override
protected String getBucketPassword() {
return this.password;
}
#Override
public String typeKey() {
return MappingCouchbaseConverter.TYPEKEY_SYNCGATEWAY_COMPATIBLE;
}
}
Looks like you are using Couchbase with Spring Data, the easiest way is to return a projection:
#Override
public List<UserVO> getUsers(String companyId, List<String> userIds) {
String queryString = "SELECT meta(t).id as id, t.login as login, t.firstName as firstName from " + getBucketName() + " t where t."+getClassFilter()+" "
+ " and t.companyId = '" + companyId + "' and t.isEnabled = true and t.isVisible = true "
+ " and meta(t).id in ["+userIds.stream().map(e->"'"+e+"'").collect( Collectors.joining( "," )) +"]";
N1qlParams params = N1qlParams.build().consistency(ScanConsistency.NOT_BOUNDED).adhoc(true);
ParameterizedN1qlQuery query = N1qlQuery.parameterized(queryString, JsonObject.create(), params);
return userRepository.getCouchbaseOperations().findByN1QLProjection(query, UserVO.class);
}
You could add _class to it using an UPDATE N1QL statement like this:
UPDATE mybucket b
SET b.c.d._class = 'com.foo.bar'
WHERE b.c.d IS NOT MISSING
AND b.c.d._class IS MISSING
That will update any document that has a 'd' object within a 'c' object but doesn't have a '_class' within the c object.
I have three tables, one containing Cards, one containing CardDecks and third one implementing a many-to-many relation between the former two and additionally containg a symbol for every relation entry.
My task is to get three columns from the card-table and the symbol from the relation-table and save it in a data Object specifically designed for handling those inputs, the codition being, that all entries match the given deckId. Or in (hopefully correct) sql-language:
#Query("SELECT R.symbol, C.title, C.type, C.source " +
"FROM card_table C JOIN cards_to_card_deck R ON C.id = R.card_id"+
"WHERE R.card_deck_id = :cardDeckId")
LiveData<List<CardWithSymbol>> getCardsWithSymbolInCardDeckById(long cardDeckId);
But the room implementation class generates:
#Override
public LiveData<List<CardWithSymbol>> getCardsWithSymbolInCardDeckById(long
cardDeckId) {
final String _sql = "SELECT R.symbol, C.title, C.typ, C.source FROM
cards_to_card_deck R INNER JOIN card_table C ON R.card_id = C.id WHERE
R.card_deck_id = ?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
_statement.bindLong(_argIndex, cardDeckId);
return new ComputableLiveData<List<CardWithSymbol>>() {
private Observer _observer;
#Override
protected List<CardWithSymbol> compute() {
if (_observer == null) {
_observer = new Observer("cards_to_card_deck","card_table") {
#Override
public void onInvalidated(#NonNull Set<String> tables) {
invalidate();
}
};
__db.getInvalidationTracker().addWeakObserver(_observer);
}
final Cursor _cursor = __db.query(_statement);
try {
final int _cursorIndexOfSymbol = _cursor.getColumnIndexOrThrow("symbol");
final List<CardWithSymbol> _result = new ArrayList<CardWithSymbol>(_cursor.getCount());
while(_cursor.moveToNext()) {
final CardWithSymbol _item;
final int _tmpSymbol;
_tmpSymbol = _cursor.getInt(_cursorIndexOfSymbol);
_item = new CardWithSymbol(_tmpSymbol,null,null,null);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
}
}
#Override
protected void finalize() {
_statement.release();
}
}.getLiveData();
}
Where
_item = new CardWithSymbol(_tmpSymbol,null,null,null);
should return my fully initialized object.
The CardWithSymbol class is declared as follows:
public class CardWithSymbol {
public int symbol;
public String cardName;
public String cardType;
public String cardSource;
public CardWithSymbol(int symbol, String cardName, String cardType, String cardSource){
this.symbol = symbol;
this.cardName = cardName;
this.cardType = cardType;
this.cardSource = cardSource;
}
And the types of the columns returned by the query are:
int symbol, String title, String type, String source
I already went through some debugging and the rest of the application works just fine. I can even read the symbol from the objects return by the query, but as mentioned above for some reason room ignores the other three parameters and just defaults them to null in the query-implementation.
So after some trial and error and reading through the dao-documentation once again i found my error:
When creating a class for handling subsets of columns in room, it is important to tell room which variable coresponds to which columns via #ColumnInfo(name = "name of the column goes here")-annotation.
So changing my CardWithSymbol class as follows solved the issue for me:
import android.arch.persistence.room.ColumnInfo;
public class CardWithSymbol {
#ColumnInfo(name = "symbol")
public int symbol;
#ColumnInfo(name = "title")
public String cardName;
#ColumnInfo(name = "type")
public String cardType;
#ColumnInfo(name = "source")
public String cardSource;
public CardWithSymbol(int symbol, String cardName, String cardType, String cardSource){
this.symbol = symbol;
this.cardName = cardName;
this.cardType = cardType;
this.cardSource = cardSource;
}
}
i have json string like this downbelow
{"0":{"in":"mmm","loc":"1234"},"1":{"in":"mmm","loc":"1234"}}
Now i need to parse them as like
in | loc
---------
mmm| 1234
mmm| 1234
So far i did
public with sharing class Search
{
public String strTag {get;set;}
public String strlocation {get;set;}
public String result {get;set;}
public PageReference find() {
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
req.setEndpoint('http://test.3spire.net/index.php?in='+strTag+'&loc='+strlocation);
req.setMethod('GET');
//these parts of the POST you may want to customize
req.setCompressed(false);
req.setBody('key1=value1&key2=value2');
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
try {
res = http.send(req);
} catch(System.CalloutException e) {
system.debug('Callout error: '+ e);
result = ''+e;
}
Result results = (Result) JSON.deserialize(res.getBody(),ResultSet.class);
result = res.getBody();
system.debug(res.getBody());
return null;
}
public class ResultSet{
public List<Result> resultSet;
}
public class Result
{
public String ins;
public String loc;
}
}
But its returns
System.TypeException: Invalid conversion from runtime type Search.ResultSet to Search.Result
How can i solved this problem
Thanks in advance
You are calling JSON.deserialize(res.getBody(),ResultSet.class). The second parameter ResultSet is the Apex object type you want the result to be. But then you attempt to cast it to a type of Result instead.
Either do
Result results = JSON.deserialize(res.getBody(), Result.class);
or
ResultSet results = JSON.deserialize(res.getBody(), ResultSet.class);
In your case, based on the JSON it would seem you want the second option. However, your JSON doesn't quite match your ResultSet class either. Your JSON is a map, not a list. Also, there's a field mismatch between "in" and "ins". This JSON is what would match your ResultSet class:
{{"ins":"mmm","loc":"1234"},{"ins":"mmm","loc":"1234"}}
Is there a way in EntityFramework (and the resulting LINQ) to query against a property of an entity that is not hard-coded?
Let's say, something that can be used for a search function.
public IList<Entity> Search (string propertyName, object value) {
// something that'll do the following
return context.Set<Entity>()
.Where(x => x.propertyName == value)
.ToList()
;
}
How about Property Descriptor?
The following code seems to do what you require:
string propertyName = "Length";
List<string> testList = new List<string>();
testList.Add("String1");
testList.Add("String10");
testList.Add("String100");
testList.Add("String1000");
System.ComponentModel.PropertyDescriptorCollection props = System.ComponentModel.TypeDescriptor.GetProperties(typeof(string));
System.ComponentModel.PropertyDescriptor desc = props.Find(propertyName, false);
IEnumerable<object> obj = from env in testList
select desc.GetValue(env);
foreach (object it in obj)
{
Console.WriteLine(it.ToString());
}
You can build equals expression manually like this
private static Expression<Func<TEntity, bool>> BuildEqualExpression<TEntity>(string propertyName, object value)
{
var param = Expression.Parameter(typeof(TEntity), "x");
var body = Expression.MakeBinary(ExpressionType.Equal,
Expression.Property(param, propertyName), Expression.Constant(value));
return Expression.Lambda<Func<TEntity, bool>>(body, new ParameterExpression[] { param });
}
and then use it in your LINQ query
var expression = BuildEqualExpression<TEntity>(propertyName, value);
return context.Set<TEntity>().Where(expression).ToList();