"404 Not Found" when viewing swagger api-docs when using swagger-springmvc (now springfox) - rest

I am trying to configure swagger in my spring project, but hitting Hitting "http://localhost:8080/api-docs" says "404 Not Found".
Maven Dependency
<dependency>
<groupId>com.mangofactory</groupId>
<artifactId>swagger-springmvc</artifactId>
<version>0.8.2</version>
</dependency>
SwaggerConfig.java File
package com.ucap.swagger;
import com.mangofactory.swagger.configuration.JacksonScalaSupport;
import com.mangofactory.swagger.configuration.SpringSwaggerConfig;
import com.mangofactory.swagger.configuration.SpringSwaggerModelConfig;
import com.mangofactory.swagger.configuration.SwaggerGlobalSettings;
import com.mangofactory.swagger.core.DefaultSwaggerPathProvider;
import com.mangofactory.swagger.core.SwaggerApiResourceListing;
import com.mangofactory.swagger.core.SwaggerPathProvider;
import com.mangofactory.swagger.scanners.ApiListingReferenceScanner;
import com.wordnik.swagger.model.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
#Configuration
#ComponentScan(basePackages = "com.mangofactory.swagger")
public class SwaggerConfig {
public static final List<String> DEFAULT_INCLUDE_PATTERNS = Arrays.asList("/news/.*");
public static final String SWAGGER_GROUP = "mobile-api";
#Value("${app.docs}")
private String docsLocation;
#Autowired
private SpringSwaggerConfig springSwaggerConfig;
#Autowired
private SpringSwaggerModelConfig springSwaggerModelConfig;
/**
* Adds the jackson scala module to the MappingJackson2HttpMessageConverter registered with spring
* Swagger core models are scala so we need to be able to convert to JSON
* Also registers some custom serializers needed to transform swagger models to swagger-ui required json format
*/
#Bean
public JacksonScalaSupport jacksonScalaSupport() {
JacksonScalaSupport jacksonScalaSupport = new JacksonScalaSupport();
//Set to false to disable
jacksonScalaSupport.setRegisterScalaModule(true);
return jacksonScalaSupport;
}
/**
* Global swagger settings
*/
#Bean
public SwaggerGlobalSettings swaggerGlobalSettings() {
SwaggerGlobalSettings swaggerGlobalSettings = new SwaggerGlobalSettings();
swaggerGlobalSettings.setGlobalResponseMessages(springSwaggerConfig.defaultResponseMessages());
swaggerGlobalSettings.setIgnorableParameterTypes(springSwaggerConfig.defaultIgnorableParameterTypes());
swaggerGlobalSettings.setParameterDataTypes(springSwaggerModelConfig.defaultParameterDataTypes());
return swaggerGlobalSettings;
}
/**
* API Info as it appears on the swagger-ui page
*/
private ApiInfo apiInfo() {
ApiInfo apiInfo = new ApiInfo(
"News API",
"Mobile applications and beyond!",
"https://helloreverb.com/terms/",
"matt#raibledesigns.com",
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0.html"
);
return apiInfo;
}
/**
* Configure a SwaggerApiResourceListing for each swagger instance within your app. e.g. 1. private 2. external apis
* Required to be a spring bean as spring will call the postConstruct method to bootstrap swagger scanning.
*
* #return
*/
#Bean
public SwaggerApiResourceListing swaggerApiResourceListing() {
//The group name is important and should match the group set on ApiListingReferenceScanner
//Note that swaggerCache() is by DefaultSwaggerController to serve the swagger json
SwaggerApiResourceListing swaggerApiResourceListing = new SwaggerApiResourceListing(springSwaggerConfig.swaggerCache(), SWAGGER_GROUP);
//Set the required swagger settings
swaggerApiResourceListing.setSwaggerGlobalSettings(swaggerGlobalSettings());
//Use a custom path provider or springSwaggerConfig.defaultSwaggerPathProvider()
swaggerApiResourceListing.setSwaggerPathProvider(apiPathProvider());
//Supply the API Info as it should appear on swagger-ui web page
swaggerApiResourceListing.setApiInfo(apiInfo());
//Global authorization - see the swagger documentation
swaggerApiResourceListing.setAuthorizationTypes(authorizationTypes());
//Every SwaggerApiResourceListing needs an ApiListingReferenceScanner to scan the spring request mappings
swaggerApiResourceListing.setApiListingReferenceScanner(apiListingReferenceScanner());
return swaggerApiResourceListing;
}
#Bean
/**
* The ApiListingReferenceScanner does most of the work.
* Scans the appropriate spring RequestMappingHandlerMappings
* Applies the correct absolute paths to the generated swagger resources
*/
public ApiListingReferenceScanner apiListingReferenceScanner() {
ApiListingReferenceScanner apiListingReferenceScanner = new ApiListingReferenceScanner();
//Picks up all of the registered spring RequestMappingHandlerMappings for scanning
apiListingReferenceScanner.setRequestMappingHandlerMapping(springSwaggerConfig.swaggerRequestMappingHandlerMappings());
//Excludes any controllers with the supplied annotations
apiListingReferenceScanner.setExcludeAnnotations(springSwaggerConfig.defaultExcludeAnnotations());
//
apiListingReferenceScanner.setResourceGroupingStrategy(springSwaggerConfig.defaultResourceGroupingStrategy());
//Path provider used to generate the appropriate uri's
apiListingReferenceScanner.setSwaggerPathProvider(apiPathProvider());
//Must match the swagger group set on the SwaggerApiResourceListing
apiListingReferenceScanner.setSwaggerGroup(SWAGGER_GROUP);
//Only include paths that match the supplied regular expressions
apiListingReferenceScanner.setIncludePatterns(DEFAULT_INCLUDE_PATTERNS);
return apiListingReferenceScanner;
}
/**
* Example of a custom path provider
*/
#Bean
public ApiPathProvider apiPathProvider() {
ApiPathProvider apiPathProvider = new ApiPathProvider(docsLocation);
apiPathProvider.setDefaultSwaggerPathProvider(springSwaggerConfig.defaultSwaggerPathProvider());
return apiPathProvider;
}
private List<AuthorizationType> authorizationTypes() {
ArrayList<AuthorizationType> authorizationTypes = new ArrayList<>();
List<AuthorizationScope> authorizationScopeList = newArrayList();
authorizationScopeList.add(new AuthorizationScope("global", "access all"));
List<GrantType> grantTypes = newArrayList();
LoginEndpoint loginEndpoint = new LoginEndpoint(apiPathProvider().getAppBasePath() + "/user/authenticate");
grantTypes.add(new ImplicitGrant(loginEndpoint, "access_token"));
return authorizationTypes;
}
#Bean
public SwaggerPathProvider relativeSwaggerPathProvider() {
return new ApiRelativeSwaggerPathProvider();
}
private class ApiRelativeSwaggerPathProvider extends DefaultSwaggerPathProvider {
#Override
public String getAppBasePath() {
return "/";
}
#Override
public String getSwaggerDocumentationBasePath() {
return "/api-docs";
}
}
}
ApiPathProvider.java file
package com.ucap.swagger;
import com.mangofactory.swagger.core.SwaggerPathProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.ServletContext;
public class ApiPathProvider implements SwaggerPathProvider {
private SwaggerPathProvider defaultSwaggerPathProvider;
#Autowired
private ServletContext servletContext;
private String docsLocation;
public ApiPathProvider(String docsLocation) {
this.docsLocation = docsLocation;
}
#Override
public String getApiResourcePrefix() {
return defaultSwaggerPathProvider.getApiResourcePrefix();
}
public String getAppBasePath() {
return UriComponentsBuilder
.fromHttpUrl(docsLocation)
.path(servletContext.getContextPath())
.build()
.toString();
}
#Override
public String getSwaggerDocumentationBasePath() {
return UriComponentsBuilder
.fromHttpUrl(getAppBasePath())
.pathSegment("api-docs/")
.build()
.toString();
}
#Override
public String getRequestMappingEndpoint(String requestMappingPattern) {
return defaultSwaggerPathProvider.getRequestMappingEndpoint(requestMappingPattern);
}
public void setDefaultSwaggerPathProvider(SwaggerPathProvider defaultSwaggerPathProvider) {
this.defaultSwaggerPathProvider = defaultSwaggerPathProvider;
}
}
Also, there is a property file in resources folder which contains:
app.docs=http://localhost:8080
When I am hitting "http://localhost:8080/api-docs" it neither gives any result nor any error, On google Rest the response code is 404.
Please help me on this.

The version that you're using (0.8.2) is a very old version of springfox. You should try and move to the latest released version, which is 2.0.
To answer your questions specifically.
Move to the most recent pre-2.0 version. Change the version of swagger-spingmvc in your pom to 1.0.2
The swagger configuration is vastly simplified in later releases. Change your SwaggerConfig to look like this.
#Configuration
//Hard to tell without seeing all your configuration, but optionally,
//add EnableWebMvc annotation in case its not working
#EnableWebMvc
#EnableSwagger
//Assuming your controllers are in this package
#ComponentScan(basePackages = "com.ucap.swagger")
public class SwaggerConfig {
public static final List<String> DEFAULT_INCLUDE_PATTERNS
= Arrays.asList("/news/.*");
//Unless you specifically want this group, I would recommend ignoring this
public static final String SWAGGER_GROUP = "mobile-api";
#Autowired
private SpringSwaggerConfig springSwaggerConfig;
#Bean
public SwaggerSpringMvcPlugin customImplementation(){
return new SwaggerSpringMvcPlugin(this.springSwaggerConfig)
.includePatterns(DEFAULT_INCLUDE_PATTERNS);
}
}
You don't need your path provider any more.
You no longer need the property file

Related

Spring Cloud Contract Stub Runner : how to configure Wiremock server?

package com.example.stubrunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.contract.stubrunner.server.EnableStubRunnerServer;
import org.springframework.cloud.contract.wiremock.WireMockConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
#EnableStubRunnerServer
public class StubRunnerApplication {
public static void main(String[] args) {
SpringApplication.run(StubRunnerApplication.class, args);
}
#Bean
public WireMockConfigurationCustomizer optionsCustomizer() {
WireMockConfigurationCustomizer customizer = new WireMockConfigurationCustomizer() {
#Override
public void customize(com.github.tomakehurst.wiremock.core.WireMockConfiguration config) {
config.jettyHeaderBufferSize(16384);
}
};
return customizer;
}
}
Above customizer bean does not seem to have any effect. This feature has not much documentation. With security token headers Wiremock's (jettty) default value is just too little.
I used start.spring.io with (current) defaults: spring boot 2.5.5. and spring cloud Hoxton.SR3.
java -jar wiremock-standalone-2.26.3.jar --jetty-header-buffer-size 16384
works just fine.
EDIT :
package com.example.wiremockrunnerlatest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.contract.stubrunner.server.EnableStubRunnerServer;
import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner;
#SpringBootApplication
#EnableStubRunnerServer
#AutoConfigureStubRunner(httpServerStubConfigurer = HeaderSizeConfigurer.class)
public class WiremockRunnerLatestApplication {
public static void main(String[] args) {
SpringApplication.run(WiremockRunnerLatestApplication.class, args);
}
}
... and then :
public class HeaderSizeConfigurer extends WireMockHttpServerStubConfigurer {
#Override
public WireMockConfiguration configure(WireMockConfiguration httpStubConfiguration, HttpServerStubConfiguration httpServerStubConfiguration) {
return httpStubConfiguration.jettyHeaderBufferSize(16384);
}
}
Have you tried using #AutoConfigureStubRunner annotation?
Just add below annotation in your tests:
#AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.CLASSPATH,
ids = "com.org:servicename:+:stubs")
Here stubsmode is classpath which means that stubs would be available in classpath.
to do that add:
testCompile("com.org:servicename:+:stubs") { transitive = false }
in your build if your using gradle or add equivalient from maven.
This will download the application automatically from remote and configures the wiremock server for stubs to be available.
complete configuration to run a spring boot test is like:
#RunWith(SpringRunner.class)
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.MOCK,
classes = HiltiIntegrationApplication.class)
#AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.CLASSPATH,
ids = "com.ict:organization-management:+:stubs")
#DirtiesContext
Hope this helps!

AEM Querybuilder Getting If Referenced

I help oversee (quality assurance) a website on AEM that has around 65,000 pages, 320,000 assets, and about 300 users able post and make changes. One thing that has been extremely helpful is a script a former IT employee wrote for us that used the querybuilder servlet to make queries and pull full lists of pages and assets. I've been able to take this output and build all sorts of automated reports using it.
The one thing I have not been able to figure out is how to find out if an asset or page is referenced by another page. The main thing I care about is just a simple true/false on if it is refereced or not. Ideally I would like it if it could be in the initial query and not have to do a query for each individual asset, but if that is the only way then I guess it in theory could be acceptable.
Just a sample query I could run currently to get some info on assets (I limited this one to 5 results for the sample):
http://localhost:4502/bin/querybuilder.json?p.hits=selective&p.offset=0&p.limit=5&p.properties=jcr%3acontent%2fmetadata%2fdc%3aformat%20jcr%3acontent%2fmetadata%2fdc%3atitle%20jcr%3apath%20&path=%2fcontent%2fdam&type=dam%3aAsset
Is there any way to add to that a field for if it is referenced? Or an array of all the references to it?
We are currently running AEM 6.2 but will soon be upgrading to 6.4.
Thank you!
There is an OOTB servlet that will return the list of pages that refer to a particular page or asset
To check if a page or asset is referenced, use
https://localhost:4502/bin/wcm/references?
_charset_=utf-8
&path=<path of the page>
&predicate=wcmcontent
&exact=false
The output will be a json response containing an array of references of the name 'pages'. If the page is not referenced it will be an empty array.
This servlet uses ReferenceSearch API the other answer mentions. If you need this value as JSON outside of AEM, you can straight away use the OOTB one without having to write your own servlet.
For your requirement, you can leverage the AssetReferenceSearch API which can give the details of Assets used in a page (node of type cq:Page).
You can use the following code to accomplish your task -
package org.redquark.aem.assets.core;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.jcr.Node;
import javax.servlet.Servlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.commons.util.AssetReferenceSearch;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* #author Anirudh Sharma
*
*/
#Component(
service = Servlet.class,
property = {
"sling.servlet.methods=GET",
"sling.servlet.resourceTypes=cq/Page",
"sling.servlet.selectors=assetreferences",
"sling.servlet.extensions=json",
"service.ranking=1000"
}
)
public class FindReferencedAssetsServlet extends SlingSafeMethodsServlet {
// Generated serial version UID
private static final long serialVersionUID = 8446564170082865006L;
private final Logger log = LoggerFactory.getLogger(this.getClass());
private static final String DAM_ROOT = "/content/dam";
#Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) {
response.setContentType("application/json");
Gson gson = new GsonBuilder().setPrettyPrinting().create();
try {
// Get the current node reference from the resource object
Node currentNode = request.getResource().adaptTo(Node.class);
if (currentNode == null) {
// Every adaptTo() can return null, so let's handle the case here
// However, it is very unlikely
log.error("Cannot adapt resource {} to a node", request.getResource().getPath());
response.getOutputStream().print(new Gson().toString());
return;
}
// Using AssetReferenceSearch which will do all the work for us
AssetReferenceSearch assetReferenceSearch = new AssetReferenceSearch(currentNode, DAM_ROOT,
request.getResourceResolver());
Map<String, Asset> result = assetReferenceSearch.search();
List<AssetDetails> assetList = new LinkedList<>();
for (String key : result.keySet()) {
Asset asset = result.get(key);
AssetDetails assetDetails = new AssetDetails(asset.getName(), asset.getPath(), asset.getMimeType());
assetList.add(assetDetails);
}
String jsonOutput = gson.toJson(assetList);
response.getOutputStream().println(jsonOutput);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
The corresponding AssetDetails model class is as follows -
package org.redquark.aem.assets.core;
/**
* #author Anirudh Sharma
*/
public class AssetDetails {
private String name;
private String path;
private String mimeType;
/**
* #param name
* #param path
* #param mimeType
*/
public AssetDetails(String name, String path, String mimeType) {
this.name = name;
this.path = path;
this.mimeType = mimeType;
}
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* #return the path
*/
public String getPath() {
return path;
}
/**
* #param path the path to set
*/
public void setPath(String path) {
this.path = path;
}
/**
* #return the mimeType
*/
public String getMimeType() {
return mimeType;
}
/**
* #param mimeType the mimeType to set
*/
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
}
Now, you can invoke this servlet by the following request -
http://localhost:4502/content/we-retail/language-masters/en/men.assetreferences.json.
This will give output in the following format
[
{
"name": "running-trail-man.jpg",
"path": "/content/dam/we-retail/en/activities/running/running-trail-man.jpg",
"mimeType": "image/jpeg"
},
{
"name": "enduro-trail-jump.jpg",
"path": "/content/dam/we-retail/en/activities/biking/enduro-trail-jump.jpg",
"mimeType": "image/jpeg"
},
{
"name": "indoor-practicing.jpg",
"path": "/content/dam/we-retail/en/activities/climbing/indoor-practicing.jpg",
"mimeType": "image/jpeg"
}
]
You can edit the AssetDetails class as per your requirement.
I hope this helps. Happy Coding!!!

Multi-Tenancy in Reactive Spring boot application using mongodb-reactive

How can we create a multi-tenant application in spring webflux using Mongodb-reactive repository?
I cannot find any complete resources on the web for reactive applications. all the resources available are for non-reactive applications.
UPDATE:
In a non-reactive application, we used to store contextual data in ThreadLocal but this cannot be done with reactive applications as there is thread switching. There is a way to store contextual info in reactor Context inside a WebFilter, But I don't how get hold of that data in ReactiveMongoDatabaseFactory class.
Thanks.
I was able to Implement Multi-Tenancy in Spring Reactive application using mangodb. Main classes responsible for realizing were: Custom MongoDbFactory class, WebFilter class (instead of Servlet Filter) for capturing tenant info and a ThreadLocal class for storing tenant info. Flow is very simple:
Capture Tenant related info from the request in WebFilter and set it in ThreadLocal. Here I am sending Tenant info using header: X-Tenant
Implement Custom MondoDbFactory class and override getMongoDatabase() method to return database based on current tenant available in ThreadLocal class.
Source code is:
CurrentTenantHolder.java
package com.jazasoft.demo;
public class CurrentTenantHolder {
private static final ThreadLocal<String> currentTenant = new InheritableThreadLocal<>();
public static String get() {
return currentTenant.get();
}
public static void set(String tenant) {
currentTenant.set(tenant);
}
public static String remove() {
synchronized (currentTenant) {
String tenant = currentTenant.get();
currentTenant.remove();
return tenant;
}
}
}
TenantContextWebFilter.java
package com.example.demo;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
#Component
public class TenantContextWebFilter implements WebFilter {
public static final String TENANT_HTTP_HEADER = "X-Tenant";
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (request.getHeaders().containsKey(TENANT_HTTP_HEADER)) {
String tenant = request.getHeaders().getFirst(TENANT_HTTP_HEADER);
CurrentTenantHolder.set(tenant);
}
return chain.filter(exchange).doOnSuccessOrError((Void v, Throwable throwable) -> CurrentTenantHolder.remove());
}
}
MultiTenantMongoDbFactory.java
package com.example.demo;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoDatabase;
import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory;
public class MultiTenantMongoDbFactory extends SimpleReactiveMongoDatabaseFactory {
private final String defaultDatabase;
public MultiTenantMongoDbFactory(MongoClient mongoClient, String databaseName) {
super(mongoClient, databaseName);
this.defaultDatabase = databaseName;
}
#Override
public MongoDatabase getMongoDatabase() throws DataAccessException {
final String tlName = CurrentTenantHolder.get();
final String dbToUse = (tlName != null ? tlName : this.defaultDatabase);
return super.getMongoDatabase(dbToUse);
}
}
MongoDbConfig.java
package com.example.demo;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.ReactiveMongoClientFactoryBean;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
#Configuration
public class MongoDbConfig {
#Bean
public ReactiveMongoTemplate reactiveMongoTemplate(MultiTenantMongoDbFactory multiTenantMongoDbFactory) {
return new ReactiveMongoTemplate(multiTenantMongoDbFactory);
}
#Bean
public MultiTenantMongoDbFactory multiTenantMangoDbFactory(MongoClient mongoClient) {
return new MultiTenantMongoDbFactory(mongoClient, "test1");
}
#Bean
public ReactiveMongoClientFactoryBean mongoClient() {
ReactiveMongoClientFactoryBean clientFactory = new ReactiveMongoClientFactoryBean();
clientFactory.setHost("localhost");
return clientFactory;
}
}
UPDATE:
In reactive-stream we cannot store contextual information in ThreadLocal any more as the request is not tied to a single thread, So, This is not the correct solution.
However, Contextual information can be stored reactor Context in WebFilter like this. chain.filter(exchange).subscriberContext(context -> context.put("tenant", tenant));. Problem is how do get hold of this contextual info in ReactiveMongoDatabaseFactory implementation class.
Here is my very rough working solution for Spring WebFlux - they have since updated the ReactiveMongoDatabaseFactory - getMongoDatabase to return a Mono
Create web filter
public class TenantContextFilter implements WebFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(TenantContextFilter.class);
#Override
public Mono<Void> filter(ServerWebExchange swe, WebFilterChain wfc) {
ServerHttpRequest request = swe.getRequest();
HttpHeaders headers = request.getHeaders();
if(headers.getFirst("X-TENANT-ID") == null){
LOGGER.info(String.format("Missing X-TENANT-ID header"));
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
String tenantId = headers.getFirst("X-TENANT-ID");
LOGGER.info(String.format("Processing request with tenant identifier [%s]", tenantId));
return wfc.filter(swe)
.contextWrite(TenantContextHolder.setTenantId(tenantId));
}
}
Create class to get context (credit to somewhere I found this)
public class TenantContextHolder {
public static final String TENANT_ID = TenantContextHolder.class.getName() + ".TENANT_ID";
public static Context setTenantId(String id) {
return Context.of(TENANT_ID, Mono.just(id));
}
public static Mono<String> getTenantId() {
return Mono.deferContextual(contextView -> {
if (contextView.hasKey(TENANT_ID)) {
return contextView.get(TENANT_ID);
}
return Mono.empty();
}
);
}
public static Function<Context, Context> clearContext() {
return (context) -> context.delete(TENANT_ID);
}
}
My spring security setup (all requests allowed for testing)
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain WebFilterChain(ServerHttpSecurity http) {
return http
.formLogin(it -> it.disable())
.cors(it -> it.disable()) //fix this
.httpBasic(it -> it.disable())
.csrf(it -> it.disable())
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
.authorizeExchange(it -> it.anyExchange().permitAll()) //allow anonymous
.addFilterAt(new TenantContextFilter(), SecurityWebFiltersOrder.HTTP_BASIC)
.build();
}
}
Create Tenant Mongo DB Factory
I still have some clean-up work for defaults etc...
public class MultiTenantMongoDBFactory extends SimpleReactiveMongoDatabaseFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(MultiTenantMongoDBFactory.class);
private final String defaultDb;
public MultiTenantMongoDBFactory(MongoClient mongoClient, String databaseName) {
super(mongoClient, databaseName);
this.defaultDb = databaseName;
}
#Override
public Mono<MongoDatabase> getMongoDatabase() throws DataAccessException {
return TenantContextHolder.getTenantId()
.map(id -> {
LOGGER.info(String.format("Database trying to retrieved is [%s]", id));
return super.getMongoDatabase(id);
})
.flatMap(db -> {
return db;
})
.log();
}
}
Configuration Class
#Configuration
#EnableReactiveMongoAuditing
#EnableReactiveMongoRepositories(basePackages = {"com.order.repository"})
class MongoDbConfiguration {
#Bean
public ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory() {
return new MultiTenantMongoDBFactory(MongoClients.create("mongodb://user:password#localhost:27017"), "tenant_catalog");
}
#Bean
public ReactiveMongoTemplate reactiveMongoTemplate() {
ReactiveMongoTemplate template = new ReactiveMongoTemplate(reactiveMongoDatabaseFactory());
template.setWriteResultChecking(WriteResultChecking.EXCEPTION);
return template;
}
}
Entity Class
#Document(collection = "order")
//getters
//setters
Testing
Create two mongo db's with same collection, put different documents in both
In Postman I just did a get request with the "X-TENANT-ID" header and database name as the value (e.g. tenant-12343 or tenant-34383) and good to go!

Returning JSON from RESTful Java server code?

I've inherited a web project that a contractor started. I and my coworkers are unfamiliar with the technology used, and have a number of questions. From what we can tell, this appears to be some sort of RESTful Java server code, but my understanding is there are lots of different types of Java RESTful services. Which one is this? Specific questions:
1) Where can we read more (particularly introductory information) about this specific service?
2) The code creates and returns a JSON through some kind of "magic"... I merely return a model class (code below) that has getter and setter methods for its fields, and it's automagically converted into a JSON. I'd like to learn more about how this is done automagically.
3) We already have some code that creates a JSON. We need to return this using this framework. If I already have a JSON, how do I return that? I tried something like this:
String testJSON = "{\"menu\": {\"id\": \"file\", \"value\": \"Hello there\"}}";
return testJSON;
instead of returning a model object with getters/setters, but this returns a literal text string, not a JSON. Is there a way to return an actual JSON that's already a JSON string, and have it be sent as a JSON?
You don't have to be able to answer all of the questions above. Any/all pointers in a helpful direction appreciated!
CODE
First, the view controller that returns the JSON:
package com.aimcloud.server;
import com.aimcloud.util.MySqlConnection;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.aimcloud.models.SubscriptionTierModel;
#Path("subscription_tier")
public class SubscriptionTierController
{
// this method will return a list of subscription_tier table entries that are currently active
#GET
#Produces({ MediaType.APPLICATION_JSON })
public String/*ArrayList<SubscriptionTierModel>*/ getSubscriptionTiers(#QueryParam("includeActiveOnly") Boolean includeActiveOnly)
{
MySqlConnection mysql = MySqlConnection.getConnection();
ArrayList<SubscriptionTierModel> subscriptionTierArray = new ArrayList<SubscriptionTierModel>();
String queryString;
if (includeActiveOnly)
queryString = "SELECT * FROM subscription_tier WHERE active=1";
else
queryString = "SELECT * FROM subscription_tier";
List<Map<String, Object>> resultList = mysql.query(queryString, null);
for (Map<String, Object> subscriptionRow : resultList)
subscriptionTierArray.add( new SubscriptionTierModel(subscriptionRow) );
// String testJSON = "{\"menu\": {\"id\": \"file\", \"value\": \"Hello there\"}}";
// return testJSON;
return subscriptionTierArray;
}
}
Next, the model the code above returns:
package com.aimcloud.models;
// NOTE this does NOT import Globals
import java.sql.Types;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.json.JSONObject;
import com.aimcloud.util.LoggingUtils;
public class SubscriptionTierModel extends ModelPrototype
{
private String name;
private Integer num_studies;
private Integer cost_viewing;
private Integer cost_processing;
private Integer active;
protected void setupFields()
{
this.fields.add("name");
this.fields.add("num_studies");
this.fields.add("cost_viewing");
this.fields.add("cost_processing");
this.fields.add("active");
}
public SubscriptionTierModel()
{
super("subscription");
this.setupFields();
}
public SubscriptionTierModel(Map<String, Object> map)
{
super("subscription");
this.setupFields();
this.initFromMap(map);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setNum_Studies(Integer num_studies) {
this.num_studies = num_studies;
}
public Integer getNum_studies() {
return this.num_studies;
}
public void setCost_viewing(Integer cost_viewing) {
this.cost_viewing = cost_viewing;
}
public Integer getCost_viewing() {
return this.cost_viewing;
}
public void setCost_processing(Integer cost_processing) {
this.cost_processing = cost_processing;
}
public Integer getCost_processing() {
return this.cost_processing;
}
public void setActive(Integer active) {
this.active = active;
}
public Integer getActive() {
return this.active;
}
}
public abstract class ModelPrototype {
protected MySqlConnection mysql;
protected ArrayList<String> fields;
protected String table;
protected Integer id = null;
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
abstract protected void setupFields();
public ModelPrototype() {
mysql = MySqlConnection.getConnection();
this.fields = new ArrayList<String>();
this.fields.add("id");
}
public void initFromDbResult(List<Map<String, Object>> result) {
if (result.size() >= 1)
{
Map<String, Object> userRow = result.get(0);
this.initFromMap(userRow);
if (result.size() > 1)
{
Thread.dumpStack();
}
}
else
{
throw new WebApplicationException(ServerUtils.generateResponse(Response.Status.NOT_FOUND, "resource not found"));
}
}
protected void initFromMap(Map<String, Object> map) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object value = entry.getValue();
// LoggingUtils.log(entry.getKey() + " " + entry.getValue().toString());
if (value != null && this.fields.contains(entry.getKey())) {
this.setField(entry.getKey(), value);
}
}
}
....
1) Where can we read more (particularly introductory information)
about this specific service?
This is a RESTful service that uses basic jax-rs annotations to build the service. I suggest looking at a tutorial like "REST using jersey" or "REST using CXF".
2) The code creates and returns a JSON through some kind of "magic"...
The restful framework used usually takes care of this. #Produces({ MediaType.APPLICATION_JSON }) annotation indicates the framework to do this conversion.This will be defined somewhere in the configuration. Check the spring config files if you are using spring to define the beans. Usually a mapper or a provider will be defined that converts the object to json.
3) We already have some code that creates a JSON. We need to return this using this framework. If I already have a JSON, how do I return that? I tried something like this:
If you already have a json just return that json from the method. Remember to still have the #Produces({ MediaType.APPLICATION_JSON }) annotation on the method.
but this returns a literal text string, not a JSON
A json is a string. That is what you will see in the response, unless you deserialize it back to an object.
I suggest you read up on JAX-RS, the Java specification for RESTful web services. All of the "javax.ws.rs.*" classes/annotations come from JAX-RS
As JAX-RS, is just a specification, there needs to be something that implements the spec. There is probably a third-party, JAX-RS component that is used to run this service. Jersey in one popular implementation. Apache CXF is another.
Now back to JAX-RS. When you read up on this, you will see that the annotations on your class determine the REST characteristics of your service. For example,
#Path("subscription_tier")
defines your class as the resource with URI BASE_PATH/subscription_tier, where BASE_PATH is propbably defined in a configuration file for your web service framework.
As for how the objects are "automagically" converted into a JSON response: that is the role of the web service framework as well. It probably uses some kind of standard object-to-JSON mapping to accomplish this. (I have worked with CXF and XML resources. In that case JAXB was the mapping mechanism). This is a good thing, as the web service developer does not have to worry about this mapping, and can focus on coding just the implementation of service itself.

GWT-Objectify : basic

I've been through a few documentations, but am not able to communicate to the datastore yet...can anyone give me a sample project/code of objectify used in GWT web app(I use eclipse)...just a simple 'put' and 'get' action using RPC should do...or, atleast tell me how its done
Easiest way to understand how to make objectify work is to repeat all steps described in this article from David's Chandler blog. Whole blog is a pretty much must read if you interested in GWT, GAE(Java), gwt-presenter, gin\guice,etc. There you will find working example, but anyway here i'll show a slighly advanced example.
In package shared define your entity/model:
import javax.persistence.Embedded;
import javax.persistence.Id;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Unindexed;
#Entity
public class MyEntry implements IsSerializable {
// Objectify auto-generates Long IDs just like JDO / JPA
#Id private Long id;
#Unindexed private String text = "";
#Embedded private Time start;
// empty constructor for serialization
public MyEntry () {
}
public MyEntry (Time start, String text) {
super();
this.text = tText;
this.start = start;
}
/*constructors,getters,setters...*/
}
Time class (also shared package) contains just one field msecs:
#Entity
public class Time implements IsSerializable, Comparable<Time> {
protected int msecs = -1;
//rest of code like in MyEntry
}
Copy class ObjectifyDao from link above to your server.dao package. And then make DAO class specifically for MyEntry -- MyEntryDAO:
package com.myapp.server.dao;
import java.util.logging.Logger;
import com.googlecode.objectify.ObjectifyService;
import com.myapp.shared.MyEntryDao;
public class MyEntryDao extends ObjectifyDao<MyEntry>
{
private static final Logger LOG = Logger.getLogger(MyEntryDao.class.getName());
static
{
ObjectifyService.register(MyEntry.class);
}
public MyEntryDao()
{
super(MyEntry.class);
}
}
Finally we can make requests to database(server package):
public class FinallyDownloadingEntriesServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/plain");
//more code...
resp.setHeader("Content-Disposition", "attachment; filename=\""+"MyFileName"+".txt\";");
try {
MyEntryDao = new MyEntryDao();
/*query to get all MyEntries from datastore sorted by start Time*/
ArrayList<MyEntry> entries = (ArrayList<MyEntry>) dao.ofy().query(MyEntry.class).order("start.msecs").list();
PrintWriter out = resp.getWriter();
int i = 0;
for (MyEntry entry : entries) {
++i;
out.println(i);
out.println(entry.getStart() + entry.getText());
out.println();
}
} finally {
//catching exceptions
}
}