How to add "api" prefix to all controllers under com.myproject.api? - java

I was trying to find it but I found many different scenarios but not this one.
What I want to do is to add "/api/" prefix to all routes in controllers under com.myproject.api .
I want "/api/*" for all controllers under package com.myapp.api and no prefix for all controllers under com.myapp.web
Is it possible with Spring / Spring Boot ?

With Spring Boot, this worked for me :
#Configuration
#EnableWebMvc
public class WebMvcConfiguration implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api",
HandlerTypePredicate.forBasePackage("com.your.package"));
}
}

If you are using springboot, you can add the following:
server.servlet.context-path=/api
to application.properties file.

I achieved the result I think you are looking for in the following way, so long as you are using MVC.
First make a configuration class that implements WebMvcRegistrations
#Configuration
public class WebMvcConfig implements WebMvcRegistrations {
#Value("${Prop.Value.String}") //"api"
private String apiPrefix;
#Value("${Prop.Package.Names}") //["com.myapp.api","Others if you like"]
private String[] prefixedPackages;
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new PrefixedApiRequestHandler(apiPrefix,prefixedPackages);
}
}
Then create a class that extends RequestMappingHandlerMapping
and overrides getMappingForMethod
#Log4j2
public class PrefixedApiRequestHandler extends RequestMappingHandlerMapping {
private final String prefix;
private final String[] prefixedPackages;
public PrefixedApiRequestHandler(final String prefix, final String... packages) {
super();
this.prefix = prefix;
this.prefixedPackages = packages.clone();
}
#Override
protected RequestMappingInfo getMappingForMethod(final Method method, final Class<?> handlerType) {
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
if (info == null) {
return null;
}
for (final String packageRef : prefixedPackages) {
if (handlerType.getPackageName().contains(packageRef)) {
info = createPrefixedApi().combine(info);
log.trace("Updated Prefixed Mapping " + info);
return info;
}
}
log.trace("Skipped Non-Prefixed Mapping " + info);
return info;
}
private RequestMappingInfo createPrefixedApi() {
String[] patterns = new String[prefix.length()];
for (int i = 0; i < patterns.length; i++) {
// Build the URL prefix
patterns[i] = prefix;
}
return new RequestMappingInfo(
new PatternsRequestCondition(patterns,
getUrlPathHelper(),
getPathMatcher(),
useSuffixPatternMatch(),
useTrailingSlashMatch(),
getFileExtensions()),
new RequestMethodsRequestCondition(),
new ParamsRequestCondition(),
new HeadersRequestCondition(),
new ConsumesRequestCondition(),
new ProducesRequestCondition(),
null);
}
}
You should then see /api/(ControllerMapping) for all mappings, in the specified packages only. Note: I have #RequestMapping("/") at the top of my controller.

Already answered here and here.
Add an application.properties file under src/main/resources, with the following option:
server.contextPath=/api
Check the official reference for common properties.

You should add #RequestMapping("/api") to top of every desired #Controller or #RestController class.
When both the class and method have that annotation, Spring Boot appends them while building the url. In below example the method will be bound to /api/user route.
#RequestMapping("/api")
#RestController
public class MyController {
#RequestMapping("/user")
public List<User> getUsers(){
return ...
}
}

Related

Spring Boot cache not caching method call based on dynamic controller parameter

I am attempting to use Spring Boot Cache with a Caffeine cacheManager.
I have injected a service class into a controller like this:
#RestController
#RequestMapping("property")
public class PropertyController {
private final PropertyService propertyService;
#Autowired
public PropertyController(PropertyService propertyService) {
this.propertyService = propertyService;
}
#PostMapping("get")
public Property getPropertyByName(#RequestParam("name") String name) {
return propertyService.get(name);
}
}
and the PropertyService looks like this:
#CacheConfig(cacheNames = "property")
#Service
public class PropertyServiceImpl implements PropertyService {
private final PropertyRepository propertyRepository;
#Autowired
public PropertyServiceImpl(PropertyRepository propertyRepository) {
this.propertyRepository = propertyRepository;
}
#Override
public Property get(#NonNull String name, #Nullable String entity, #Nullable Long entityId) {
System.out.println("inside: " + name);
return propertyRepository.findByNameAndEntityAndEntityId(name, entity, entityId);
}
#Cacheable
#Override
public Property get(#NonNull String name) {
return get(name, null, null);
}
}
Now, when I call the RestController get endpoint and supply a value for the name, every request ends up doing inside the method that should be getting cached.
However, if I call the controller get endpoint but pass a hardcoded String into the service class method, like this:
#PostMapping("get")
public Property getPropertyByName(#RequestParam("name") String name) {
return propertyService.get("hardcoded");
}
Then the method is only invoked the first time, but not on subsequent calls.
What's going on here? Why is it not caching the method call when I supply a value dynamically?
Here is some configuration:
#Configuration
public class CacheConfiguration {
#Bean
public CacheManager cacheManager() {
val caffeineCacheManager = new CaffeineCacheManager("property", "another");
caffeineCacheManager.setCaffeine(caffeineCacheBuilder());
return caffeineCacheManager;
}
public Caffeine<Object, Object> caffeineCacheBuilder() {
return Caffeine.newBuilder()
.initialCapacity(200)
.maximumSize(500)
.weakKeys()
.recordStats();
}
}
2 solutions (they work for me):
remove .weakKeys()
propertyService.get(name.intern()) - wouldn't really do that, possibly a big cost
Sorry, but I don't have enough knowledge to explain this. Probably something to do with internal key representation by Caffeine.

How to manage two sets of properties at runtime?

I'm building a web service that needs to switch between two sets of properties depending on the request URL coming in. I'm not sure which is the best method of handling this.
I've got a Spring Boot app that has an yaml properties file. Inside the properties file the structure looks something like this;
optionA:
foo:
urls:
- a
- b
bar:
something: hello
optionB:
foo:
urls:
- x
- y
bar:
something: bye
Both optionA and optionB have pretty much all the same properties, just different values.
So a request comes in, I check the request and decide if I need optionA or optionB.
I've been trying to get #ConfigurationProperties to handle this but the properties are initialised on startup so it can't be dynamic. Another possibility is that I have two Configuration classes, one for each option but then my code gets full of checks to switch between the two classes and the classes are pretty much identical, not really nice either.
Any best practices or recommendations on how to best manage this would be appreciated, cheers!
If you have not too many options I would go this way: (Just made example with smaller config)
options.yml:
optionA:
name: optionA
optionB:
name: optionB
I created a Option class for extension:
public class Option {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And two Option classes where the #ConfigurationProperties are getting set: (For now these classes are empty but you have the opportunity to be more specific on each different option)
#Component
#ConfigurationProperties(prefix ="optionA", locations = "classpath:options.yml")
public class OptionA extends Option{
}
#Component
#ConfigurationProperties(prefix ="optionB", locations = "classpath:options.yml")
public class OptionB extends Option{
}
For the decision of the different options I created an interface:
public interface OptionService {
Option findOption(boolean businessLogic);
}
And in the implementation I inject both options and the implementation of the business logic: (in an easy way)
#Service
public class OptionServiceImpl implements OptionService {
private OptionA optionA;
private OptionB optionB;
#Override
public Option findOption(boolean businessLogic) {
if(businessLogic){
return getOptionA();
} else {
return getOptionB();
}
}
public OptionA getOptionA() {
return optionA;
}
#Autowired
public void setOptionA(OptionA optionA) {
this.optionA = optionA;
}
public OptionB getOptionB() {
return optionB;
}
#Autowired
public void setOptionB(OptionB optionB) {
this.optionB = optionB;
}
}
And at the end your controller just call the OptionServiceImpl class and deceide which option should be used:
#Controller
public class YourController {
private OptionService optionServiceImpl;
#RequestMapping("/")
public String getIndex(){
Option option = getOptionServiceImpl().findOption(true);
System.out.println(option.getName());
option = getOptionServiceImpl().findOption(false);
System.out.println(option.getName());
return "Hello World";
}
public OptionService getOptionServiceImpl() {
return optionServiceImpl;
}
#Autowired
public void setOptionServiceImpl(OptionService optionServiceImpl) {
this.optionServiceImpl = optionServiceImpl;
}
}
Output of System.out.println:
optionA
optionB
So your business logic to decide which option should be used is not an if - else construct. You are able to create the rules for the decission in the interface and its implementation. I think you are able to create more rules for more controllers.
Change your yml to:
options:
- name: optionA
foo:
urls:
- a
- b
bar:
something: hello
- name: optionB
foo:
urls:
- x
- y
bar:
something: bye
Add Config class:
#Data
#ConfigurationProperties
#Configuration
public class MyConfig {
private List<Option> options;
}
Use it:
#Component
public class UseConfig {
#Autowired
public UseConfig(final MyConfig config) {
System.out.println(config.getOptions());
}
}
Result:
[Option(name=optionA, foo=Foo(urls=[a, b]), bar=Bar(something=hello)), Option(name=optionB, foo=Foo(urls=[x, y]), bar=Bar(something=bye))]
You can define key value pairs in application.properties.
where key is web service name and value is option(list of properties)
Make use of #ConfigurationProperties
#ConfigurationProperties
class Configuration {
Map<String,Option> options;
// getters and setters
}
#Component
class ChooseServiceBasedConfiguration {
#Autowired
Configuration configuration;
public void serviceMethod(String key ){
//get appropriate properties of the web service
configuration.getOptions().get(key);
}
}
based on web service get the values required using the key .

Jackson JSON, filtering properties by path

I need to filter bean properties dynamiclly on serialization.
The #JsonView isn't an option for me.
Assume my Bean (as Json notation):
{
id: '1',
name: 'test',
children: [
{ id: '1.1', childName: 'Name 1.1' },
{ id: '1.2', childName: 'Name 1.2' }
]
}
I want to write the JSON with the following properties:
// configure the ObjectMapper to only serialize this properties:
[ "name", "children.childName" ]
The expected JSON result is:
{
name: 'test',
children: [
{ childName: 'Name 1.1' },
{ childName: 'Name 1.2' }
]
}
Finally I will create an annotation (#JsonFilterProperties) to use with Spring in my RestControllers, something like this:
#JsonFilterProperties({"name", "children.childName"}) // display only this fields
#RequestMapping("/rest/entity")
#ResponseBody
public List<Entity> findAll() {
return serviceEntity.findAll(); // this will return all fields populated!
}
Well, it's tricky but doable. You can do this using Jacksons Filter feature (http://wiki.fasterxml.com/JacksonFeatureJsonFilter) with some minor alterations. To start, we are going to use class name for filter id, this way you won't have to add #JsonFIlter to every entity you use:
public class CustomIntrospector extends JacksonAnnotationIntrospector {
#Override
public Object findFilterId(AnnotatedClass ac) {
return ac.getRawType();
}
}
Next step, make that filter of super class will apply to all of its subclasses:
public class CustomFilterProvider extends SimpleFilterProvider {
#Override
public BeanPropertyFilter findFilter(Object filterId) {
Class id = (Class) filterId;
BeanPropertyFilter f = null;
while (id != Object.class && f == null) {
f = _filtersById.get(id.getName());
id = id.getSuperclass();
}
// Part from superclass
if (f == null) {
f = _defaultFilter;
if (f == null && _cfgFailOnUnknownId) {
throw new IllegalArgumentException("No filter configured with id '" + filterId + "' (type " + filterId.getClass().getName() + ")");
}
}
return f;
}
}
Custom version of ObjectMapper that utilizes our custom classes:
public class JsonObjectMapper extends ObjectMapper {
CustomFilterProvider filters;
public JsonObjectMapper() {
filters = new CustomFilterProvider();
filters.setFailOnUnknownId(false);
this.setFilters(this.filters);
this.setAnnotationIntrospector(new CustomIntrospector());
}
/* You can change methods below as you see fit. */
public JsonObjectMapper addFilterAllExceptFilter(Class clazz, String... property) {
filters.addFilter(clazz.getName(), SimpleBeanPropertyFilter.filterOutAllExcept(property));
return this;
}
public JsonObjectMapper addSerializeAllExceptFilter(Class clazz, String... property) {
filters.addFilter(clazz.getName(), SimpleBeanPropertyFilter.serializeAllExcept(property));
return this;
}
}
Now take a look at MappingJackson2HttpMessageConverter, you will see that it uses one instane of ObjectMapper internaly, ergo you cannot use it if you want different configurations simultaneously (for different requests). You need request scoped ObjectMapper and appropriate message converter that uses it:
public abstract class DynamicMappingJacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {
// Spring will override this method with one that provides request scoped bean
#Override
public abstract ObjectMapper getObjectMapper();
#Override
public void setObjectMapper(ObjectMapper objectMapper) {
// We dont need that anymore
}
/* Additionally, you need to override all methods that use objectMapper attribute and change them to use getObjectMapper() method instead */
}
Add some bean definitions:
<bean id="jsonObjectMapper" class="your.package.name.JsonObjectMapper" scope="request">
<aop:scoped-proxy/>
</bean>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="your.package.name.DynamicMappingJacksonHttpMessageConverter">
<lookup-method name="getObjectMapper" bean="jsonObjectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
And the last part is to implement something that will detect your annotation and perform actual configuration. For that you can create an #Aspect. Something like:
#Aspect
public class JsonResponseConfigurationAspect {
#Autowired
private JsonObjectMapper objectMapper;
#Around("#annotation(jsonFilterProperties)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
/* Here you will have to determine return type and annotation value from jointPoint object. */
/* See http://stackoverflow.com/questions/2559255/spring-aop-how-to-get-the-annotations-of-the-adviced-method for more info */
/* If you want to use things like 'children.childName' you will have to use reflection to determine 'children' type, and so on. */
}
}
Personally, I use this in a different way. I dont use annotations and just do configuration manually:
#Autowired
private JsonObjectMapper objectMapper;
#RequestMapping("/rest/entity")
#ResponseBody
public List<Entity> findAll() {
objectMapper.addFilterAllExceptFilter(Entity.class, "name", "children");
objectMapper.addFilterAllExceptFilter(EntityChildren.class, "childName");
return serviceEntity.findAll();
}
P.S. This approach has one major flaw: you cannot add two different filters for one class.
There's Jackson plugin called squiggly for doing exactly this.
String filter = "name,children[childName]";
ObjectMapper mapper = Squiggly.init(this.objectMapper, filter);
mapper.writeValue(response.getOutputStream(), myBean);
You could integrate it into a MessageConverter or similar, driven by annotations, as you see fit.
If you have a fixed number of possible options, then there is a static solution too: #JsonView
public interface NameAndChildName {}
#JsonView(NameAndChildName.class)
#ResponseBody
public List<Entity> findAll() {
return serviceEntity.findAll();
}
public class Entity {
public String id;
#JsonView(NameAndChildName.class)
public String name;
#JsonView({NameAndChildName.class, SomeOtherView.class})
public List<Child> children;
}
public class Child {
#JsonView(SomeOtherView.class)
public String id;
#JsonView(NameAndChildName.class)
public String childName;
}

Spring MVC multiple requests on singleton

I have a problem with Spring MVC. I am using Stanford NLP and I put initialization of it to singleton class.
#Component
#Scope("singleton")
public class JavaNLP implements NlpInterface
{
private DependencyNLP nlp_object = null;
#PostConstruct
public void init()
{
if (nlp_object == null) {
nlp_object = new DependencyNLP();
nlp_object.init("tokenize, ssplit, pos, lemma, ner, parse, dcoref");
}
}
#Override
public void init(String name, DataContainer container)
{
this.container = container;
nlp_object.annotate(container.getText());
}
#Override
public void execute()
{
...
}
}
Every request is calling init(String name, DataContainer container) and execute. Problem is in 70% requests nlp_object is not initialized.
Inside of a controller:
#Autowired
#Qualifier("javaNLP")
private NlpInterface nlpInterface;
#RequestMapping(value = "/parse", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, headers = {"Content-type=application/json"})
public #ResponseBody
String parseWithGazetteerinJSON(#RequestBody DataContainer container)
{
String name = "Parsing text";
nlpInterface.init(name, container);
nlpInterface.execute();
JSONArray triples = nlpInterface.getTriplesAsJSON();
return triples.toString();
}
EDIT 1
Unfortunetly it is still not working. I think I found where is the problem, just dont know how to fix it.
I put configuration + calling init function into config
<mvc:annotation-driven />
<context:component-scan base-package="com.metadata.tripletws.model" />
<context:component-scan base-package="com.metadata.tripletws.controller" />
<bean id="nlp" class="com.metadata.tripletws.service.Nlp" init-method="init"></bean>
I have created new object called Nlp and it is just sort of envelope for DependencyNLP (external library)
#Component
public class Nlp
{
private DependencyNLP nlp;
public void init()
{
nlp = new DependencyNLP();
nlp.init();
}
public DependencyNLP getInstance()
{
return nlp;
}
public void execute()
{
...
}
...
}
Then I added this code to the controller:
DependencyNLP nlpInstance = nlp.getInstance();
System.out.println(nlpInstance);
nlpInstance.annotate(container.getSection().getText());
nlpInstance.execute(...);
and defined private Autowired variable
#Autowired
private Nlp nlp;
Printed nlpInstance is always same. That makes sense in the end. But here is the problem I beliave. Requests are influencing each other.
Does somebody know how to make it run?
Thanks
Instead of initializing the nlp_object in a #PostConstruct method try injecting it as a resource to your implementation of NlpInterface
Not sure if this will solve your problem, but if it were me I'd give it a go.
Also, you init() method(if you must go that way) should be synhcronized with double if ( singletonObj == null) gates outside and inside the sync block.

Spring StandardTypeLocator in StandardEvaluationContext: registering new import prefixes for use in Thymeleaf templates

We are developing a Spring MVC (v4) web app using the Thymeleaf templating library in the view layer with the Thymeleaf SpringTemplateEngine providing SPEL support.
When we reference types in our templates (e.g. for access to static utility methods or enums) we have to include the fully qualified name as the Spring StandardEvaluationContext StandardTypeLocator only knows about the java.lang package by default. I can see in the Spring API that we need to add our own packages to the type locator using the registerImport(String prefix) method, but I can't work out how to get hold of the default evaluation context that is used in our templates to be able to do this.
I want to de-clutter our Thymeleaf HTML templates by replacing this sort of thing:
T(org.apache.commons.io.FileUtils).byteCountToDisplaySize(1024)
With:
T(FileUtils).byteCountToDisplaySize(1024)
I tried autowiring an EvaluationContext into a controller to see if I could get hold of it, but Spring tells me no qualifying beans are found. Any advice appreciated!
I'm using JAVA based spring config. So in the spring security config class (There must be a #EnableGlobalMethodSecurity(prePostEnabled = true) annotation) , I'm injecting a custom MethodSecurityExpressionHandler bean:
#Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler() {
#Override
public StandardEvaluationContext createEvaluationContextInternal(final Authentication auth, final MethodInvocation mi) {
StandardEvaluationContext evaluationContext = super.createEvaluationContextInternal(auth, mi);
//Register custom package paths, since StandardTypeLocator is only aware of "java.lang"
//So there will be no need to provide a fully qualified path
((StandardTypeLocator) evaluationContext.getTypeLocator()).registerImport("desired.path");
return evaluationContext;
}
};
expressionHandler.setPermissionEvaluator(new ExpressionAccessPermissionEvaluator()); //register some custom PermissionEvaluator if any
return expressionHandler;
}
}
Thanks
I am not sure if this solves your problem, but ThymeleafViewResolver class has addStaticVariable method which adds variables to the context before the view is processed.
I made a little test:
#Autowired
ThymeleafViewResolver thymeleafViewResolver;
#PostConstruct
public void postConstruct() {
thymeleafViewResolver.addStaticVariable("myUtil", new StringUtils());
}
With StringUtils like the below:
public class StringUtils {
public static String print() {
return "Printed";
}
}
And the view:
<div th:text="${T(some.package.StringUtils).print()}">Test</div>
<div th:text="${myUtil.print()}">Test</div>
Both worked fine. The latter will also work if your method is not static.
Hope it helps.
I managed to do this by wrapping the EngineContextFactory Class so I put as follow in my Thymeleaf config class:
#Bean
public SpringTemplateEngine springTemplateEngine(SpringResourceTemplateResolver templateResolver,
IDialect springSecurityDialect)
{
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
templateEngine.setEngineContextFactory(engineContextFactory());
templateEngine.addDialect(springSecurityDialect);
return templateEngine;
}
private IEngineContextFactory engineContextFactory()
{
return new EngineContextFactoryWrapper()
// packages to register
.registerImport("java.util")
.registerImport("java.math")
.registerImport("com.mainsys.fhome.gui.util");
}
public static class EngineContextFactoryWrapper
implements IEngineContextFactory
{
private final IEngineContextFactory delegate;
private final List<String> typeLocatorPrefixes;
public EngineContextFactoryWrapper()
{
super();
delegate = new StandardEngineContextFactory();
typeLocatorPrefixes = new ArrayList<String>();
}
#Override
public IEngineContext createEngineContext(IEngineConfiguration configuration,
TemplateData templateData,
Map<String, Object> templateResolutionAttributes,
IContext context)
{
IEngineContext engineCtx = delegate.createEngineContext(configuration, templateData, templateResolutionAttributes, context);
EvaluationContext evaluationContext;
if (engineCtx.containsVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME))
{
evaluationContext = (EvaluationContext) engineCtx.getVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME);
}
else
{
evaluationContext = new ThymeleafEvaluationContextWrapper(new StandardEvaluationContext());
}
for (String prefix : typeLocatorPrefixes)
{
((StandardTypeLocator) evaluationContext.getTypeLocator()).registerImport(prefix);
}
return engineCtx;
}
public EngineContextFactoryWrapper registerImport(String prefix)
{
typeLocatorPrefixes.add(prefix);
return this;
}
}

Categories