Standalone Java Implementation for extracting values in URI Template (RFC 6570)? - java

Is there a Java standalone implementation to extract values ​​of parameters in an URI as defined by an URI-Template (RFC 6570)?
The best implementation I've found is a ruby implementation ( https://github.com/sporkmonger/addressable )
Via http://code.google.com/p/uri-templates/wiki/Implementations I found a Java implementation: Handy-URI-Templates
It supports the resolution of an URI-Template with parameter values to a final URI. Unfortunately, it can not do the reverse: extraction of parameter values ​​in the URI according URI-Template.
Implentations of the JAX-RS (or Restlet) have this feature internally.
But none seems to have isolated this feature module which could used independently.
Does anyone have another idea?
Here a example to Use spring-Web :
import org.springframework.web.util.UriTemplate;
public class UriParserSpringImpl implements UriParser {
private final UriTemplate uriTemplate;
private final String uriTemplateStr;
public UriParserSpringImpl(final String template) {
this.uriTemplateStr = template;
this.uriTemplate = new UriTemplate(template);
}
#Override
public Map<String, String> parse(final String uri) {
final boolean match = this.uriTemplate.matches(uri);
if (!match) {
return null;
}
return uriUtils.decodeParams(this.uriTemplate.match(uri));
}
#Override
public Set<String> getVariables() {
return Collections.unmodifiableSet(new LinkedHashSet<String>(this.uriTemplate.getVariableNames()));
}
}
Another for Jersey (JAX-RS implementation) :
import com.sun.jersey.api.uri.UriTemplate;
public class UriParserJerseyImpl implements UriParser {
private final UriTemplate uriTemplate;
private final Map<String, String> valuesMaps;
public UriParserJerseyImpl(final String template) {
this.uriTemplate = new UriTemplate(template);
final Map<String, String> valuesMaps = new HashMap<String, String>();
for (final String prop : this.uriTemplate.getTemplateVariables()) {
valuesMaps.put(prop, null);
}
this.valuesMaps = Collections.unmodifiableMap(valuesMaps);
}
#Override
public Map<String, String> parse(final String uri) {
final Map<String, String> values = new HashMap<String, String>(this.valuesMaps);
final boolean match = this.uriTemplate.match(uri, values);
if (!match) {
return null;
}
return values;
}
#Override
public Set<String> getVariables() {
return this.valuesMaps.keySet();
}
}
With interface :
public interface UriParser {
public Set<String> getVariables();
public Map<String, String> parse(final String uri);
}

The damnhandy uri template library has an open issue for exactly this feature. I've already gotten the PR for the feature merged and it should be out in version 2.2! Head over there and let the maintainers know you're interested.
Also if you can't wait, you can see how I did it here and use that for yourself.

java.net.URI
Can't set the parameters after it's instantiated, but it has a nice set of getters and you can contruct a new one to alter it.

Related

Declare Map fields with type system

In Java say I have a class that represents http headers:
public class Headers {
String 'x-requested-by' = "foo";
String 'content-type' = "application/json"
}
because of the field names with non-standard variable names (hyphens), typically a more dynamic map is used like so:
Map<String, String> map = new HashMap<String, String>();
but my quesetion is - is there a way to declare which fields will exist in there statically, instead of only at runtime?
You can use an enum map, which will only accept keys of a specified enum type, while the enum itself will statically limit options.
enum Headers {
X_REQUESTED_BY("x-requested-by"), CONTENT_TYPE("content-type");
private String headerName;
private Headers(String n) {
this.headerName = n;
}
public String getHeaderName() {
return headerName;
}
}
And use the enum map to store values:
Map<Headers, String> headerValues = new EnumMap<>(Headers.class);
Your API can then be extended with such methods as addHeader(Headers h), which makes it possible to statically limit options while keeping it type-safe.
No. Only one thing you can do is init a Map with default values after initialization.
public class Header {
public static final String X_REQUESTED_BY = "x-requested-by";
public static final String CONTENT_TYPE = "content-type";
private final Map<String, String> map = new HashMap<String, String>();
{
map.put(X_REQUESTED_BY, "foo");
map.put(CONTENT_TYPE, "application/json");
}
}

Mapping fields as key-value pair

When reading a JSON file, i would like to map my class as follows:
public class Effect {
private final String type;
private final Map<String, String> parameters;
public Effect(String type, Map<String, String> parameters) {
this.type = type;
this.parameters = parameters;
}
public String getType() {
return this.type;
}
public Map<String, String> getParameters() {
return this.parameters;
}
}
{
"type": {
"key1": "value1",
"key2": "value2",
}
}
So, the mapped JSON object consists of type as the only key and parameters as its value.
I would like to use #JsonCreator on the constructor, but can't figure out, how to map the fields. Do i need to write a custom deserializer or is there an easier way to map the class like i want?
I wrote a custom deserializer, which does what i want, but there might be an easier way, maybe with annotations alone, which i would like to know:
public class EffectDeserializer extends StdDeserializer<Effect> {
private static final long serialVersionUID = 1L;
public EffectDeserializer() {
super(Effect.class);
}
#Override
public Effect deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
JsonNode node = parser.getCodec().readTree(parser);
Iterator<String> fieldNames = node.fieldNames();
if(fieldNames.hasNext()) {
String type = fieldNames.next();
Map<String, String> parameters = new HashMap<>();
for(Iterator<Entry<String, JsonNode>> fields = node.get(type).fields(); fields.hasNext(); ) {
Entry<String, JsonNode> field = fields.next();
parameters.put(field.getKey(), field.getValue().textValue());
}
return new Effect(type, parameters);
}
return null;
}
}
Another way i found would be adding a JsonCreator (constructor in this case), that takes a Map.Entry<String, Map<String, String> and uses that to initialize the values, like this:
#JsonCreator
public Effect(Map.Entry<String, Map<String, String>> entry) {
this.type = entry.getKey();
this.parameters = entry.getValue();
}
If there's no way to get it done with a "normal" constructor, i will probably end up using this, as it uses Jackson's default mapping for Map.Entry, reducing possible error margin.
Add a static factory method that accepts a Map with a dynamic key:
#JsonCreator
public static Effect create(Map<String, Map<String, String>> map) {
String type = map.keySet().iterator().next();
return new Effect(type, map.get(type));
}
EDIT: Just noticed this is basically an uglier version of your own solution using Map.Entry. I would go with that instead.

How to use Aggregation Query with MongoItemReader in spring batch

Some how the requirement changed and I have to use aggregation query insted of basic query in setQuery(). Is this even possible?
Please suggest how can i do that? My Aggregation query is ready but not sure how can I use that in spring batch
public ItemReader<ProfileCollection> searchMongoItemReader() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
MongoItemReader<MyCollection> mongoItemReader = new MongoItemReader<>();
mongoItemReader.setTemplate(myMongoTemplate);
mongoItemReader.setCollection(myMongoCollection);
mongoItemReader.setQuery(" Some Simple Query - Basic");
mongoItemReader.setTargetType(MyCollection.class);
Map<String, Sort.Direction> sort = new HashMap<>();
sort.put("field4", Sort.Direction.ASC);
mongoItemReader.setSort(sort);
return mongoItemReader;
}
extend MongoItemReader and provide your own implementation for method doPageRead(). This way you will have full pagination support and this reading of documents will be part of a step.
public class CustomMongoItemReader<T, O> extends MongoItemReader<T> {
private MongoTemplate template;
private Class<? extends T> inputType;
private Class<O> outputType
private MatchOperation match;
private ProjectionOperation projection;
private String collection;
#Override
protected Iterator<T> doPageRead() {
Pageable page = PageRequest.of(page, pageSize) //page and page size are coming from the class that MongoItemReader extends
Aggregation agg = newAggregation(match, projection, skip(page.getPageNumber() * page.getPageSize()), limit(page.getPageSize()));
return (Iterator<T>) template.aggregate(agg, collection, outputType).iterator();
}
}
And other getter and setters and other methods. Just have a look at sourcecode for MongoItemReader here.
I also removed Query support from it. You can have that also in the same method just copy paste it from MongoItemReader. Same with Sort.
And in the class where you have a reader, you would do something like:
public MongoItemReader<T> reader() {
CustomMongoItemReader reader = new CustomMongoItemReader();
reader.setTemplate(mongoTemplate);
reader.setName("abc");
reader.setTargetType(input.class);
reader.setOutputType(output.class);
reader.setCollection(myMongoCollection);
reader.setMatch(Aggregation.match(new Criteria()....)));
reader.setProjection(Aggregation.project("..","..");
return reader;
}
To be able to use aggregation in a job, making use of all features that spring batch has, you have to create a custom ItemReader.
Extending AbstractPaginatedDateItemReader we can use all the elements from pageable operations.
Here's a simple of that custom class:
public class CustomAggreagationPaginatedItemReader<T> extends AbstractPaginatedDataItemReader<T> implements InitializingBean {
private static final Pattern PLACEHOLDER = Pattern.compile("\\?(\\d+)");
private MongoOperations template;
private Class<? extends T> type;
private Sort sort;
private String collection;
public CustomAggreagationPaginatedItemReader() {
super();
setName(ClassUtils.getShortName(CustomAggreagationPaginatedItemReader.class));
}
public void setTemplate(MongoOperations template) {
this.template = template;
}
public void setTargetType(Class<? extends T> type) {
this.type = type;
}
public void setSort(Map<String, Sort.Direction> sorts) {
this.sort = convertToSort(sorts);
}
public void setCollection(String collection) {
this.collection = collection;
}
#Override
#SuppressWarnings("unchecked")
protected Iterator<T> doPageRead() {
Pageable pageRequest = new PageRequest(page, pageSize, sort);
BasicDBObject cursor = new BasicDBObject();
cursor.append("batchSize", 100);
SkipOperation skipOperation = skip(Long.valueOf(pageRequest.getPageNumber()) * Long.valueOf(pageRequest.getPageSize()));
Aggregation aggregation = newAggregation(
//Include here all your aggreationOperations,
skipOperation,
limit(pageRequest.getPageSize())
).withOptions(newAggregationOptions().cursor(cursor).build());
return (Iterator<T>) template.aggregate(aggregation, collection, type).iterator();
}
#Override
public void afterPropertiesSet() throws Exception {
Assert.state(template != null, "An implementation of MongoOperations is required.");
Assert.state(type != null, "A type to convert the input into is required.");
Assert.state(collection != null, "A collection is required.");
}
private String replacePlaceholders(String input, List<Object> values) {
Matcher matcher = PLACEHOLDER.matcher(input);
String result = input;
while (matcher.find()) {
String group = matcher.group();
int index = Integer.parseInt(matcher.group(1));
result = result.replace(group, getParameterWithIndex(values, index));
}
return result;
}
private String getParameterWithIndex(List<Object> values, int index) {
return JSON.serialize(values.get(index));
}
private Sort convertToSort(Map<String, Sort.Direction> sorts) {
List<Sort.Order> sortValues = new ArrayList<Sort.Order>();
for (Map.Entry<String, Sort.Direction> curSort : sorts.entrySet()) {
sortValues.add(new Sort.Order(curSort.getValue(), curSort.getKey()));
}
return new Sort(sortValues);
}
}
If you look with attention you can see it was create using MongoItemReader from Spring framework, you can see that class at org.springframework.batch.item.data.MongoItemReader, there's the way you have to create a whole new class extending AbstractPaginatedDataItemReader, if you have a look at "doPageRead" method you should be able to see that it only uses the find operation of MongoTemplate, making it impossible to use Aggregate operations in it.
Here is how it should like to use it our CustomReader:
#Bean
public ItemReader<YourDataClass> reader(MongoTemplate mongoTemplate) {
CustomAggreagationPaginatedItemReader<YourDataClass> customAggreagationPaginatedItemReader = new CustomAggreagationPaginatedItemReader<>();
Map<String, Direction> sort = new HashMap<String, Direction>();
sort.put("id", Direction.ASC);
customAggreagationPaginatedItemReader.setTemplate(mongoTemplate);
customAggreagationPaginatedItemReader.setCollection("collectionName");
customAggreagationPaginatedItemReader.setTargetType(YourDataClass.class);
customAggreagationPaginatedItemReader.setSort(sort);
return customAggreagationPaginatedItemReader;
}
As you may notice, you also need a instance of MongoTemplate, here's how it should like too:
#Bean
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory) {
return new MongoTemplate(mongoDbFactory);
}
Where MongoDbFactory is a autowired object by spring framework.
Hope that's enough to help you.

read Java public variables as keys from map

I had a static class that contained several static variables:
public class A{
static {
}
public static final String param1= "paramVal1";
public static final String param2= "paramVal2";
}
I want to change the code, so that the variables will be in a map:
public class A{
static {
}
private static Map<String, String> params = new HashMap<String, String>() ;
public static void initParams() {
params.put("param1", paramVal1);
params.put("param2", paramVal2);
}
However, I already have many classes that call those public parameters, and I don't want to go to every class and change it. Is there any way to use some define function, that would cause java to return the map's value, when the parameter is called? i.e if someone calls A.param1, it would return params.get("param1")
A parameter in not as easy to use (and overwrite) than a method. So I think that short answer to your question is no. That's one of the reasons of getters and setters. But you can allways do the inverse, that is keep the old parameters for compatibility and use a map for newer uses :
public class A{
static {
param1 = "paramVal1";
param2 = "paramVal2";
params = new HashMap<String, String>;
params.put("param1", param1);
params.put("param2", param2);
// eventually other inits for params
}
public static final String param1= "paramVal1";
public static final String param2= "paramVal2";
public static Map<String, String params;
/* or better private static Map<String,String> params
and access via getter */
public static getParam(String name) {
return param.get(name);
}
}
That way, old classes could allways do A.param1, and for newer classes you could start using A.get("param1").
You can do this:
public class A {
private static Map<String, String> params = new HashMap<String, String>();
static {
params.put("param1", "paramVal1");
params.put("param2", "paramVal2");
}
public static final String param1 = params.get("param1");
public static final String param2 = params.get("param2");
}

Java library to read/write Map<String, String> via bean

I'm looking for a library that provides type type-safe read and write access to a Map<String, String> via a proxied Java bean. For example:
interface Person {
String getName();
void setName(String name);
int getAge();
void setAge(int age);
}
Map<String, String> data = new HashMap<String, String>() {{
put("name", "juni");
put("age", "4");
}}
Person p = HypotheticalLibrary.bind(Person.class, data);
p.getName(); // returns "juni"
p.setAge(5); // calls data.put("age", "5") --- notice the implicit type conversion
Is there such a thing?
I don't know of one. However, it's fairly simple to write one using a proxy. You would need to write an InvocationHandler that recognises getters and setters, and gets or puts on the map accordingly. There is one fiddly bit - converting the method name to a key for the map - and one hard bit - working out how to convert the types.
I wrote a quick and dirty implementation in ~60 lines of code. It does a pretty clumsy job on the types; it would take another hundred or so to do a decent job for all basic types.
Assuming you're ok using spring as a dependency, you can use the proxy approach as suggested. BeanUtils class takes care of turning the method name into a property descriptor so you can get the name. No type conversion is required because you're working with the interface itself, so the compiler will ensure that you send the right type in (and thus the right type out).
static interface Person {
void setName(String name);
String getName();
void setAge(int age);
int getAge();
}
public static Person createPerson() {
return createPerson(new HashMap<String, String>());
}
public static Person createPerson(final Map<String, String> props) {
InvocationHandler ih = new InvocationHandler() {
private TypeConverter typeConverter = new SimpleTypeConverter();
#Override
public Object invoke(Object source, Method method, Object[] params)
throws Throwable {
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method);
if (method.getName().startsWith("set")) {
props.put(pd.getName(), typeConverter.convertIfNecessary(params[0], String.class));
return null;
}
else if (method.getName().startsWith("get") ||
method.getName().startsWith("is")) {
Object res = props.get(pd.getName());
return typeConverter.convertIfNecessary(res, method.getReturnType());
}
return null;
}
};
Person p = (Person) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { Person.class },
ih);
return p;
}
public static void main(String args[]) {
final Map<String, String> props = new HashMap<String, String>();
props.put("name", "Matt");
props.put("age", "4");
Person p = createPerson(props);
System.out.println(p.getName());
System.out.println(p.getAge());
}
I don't believe there is one but u could build your own with the help of mvel or ognl or spel. I did build my own sometime back.

Categories

Resources