Setting 'relaxedQueryChars' for embedded Tomcat - java

How can I set relaxedQueryChars for Spring Boot embedded Tomcat?
The connector attribute described here, but Spring Boot documentation has no such parameter listed.
How to set Tomcat's Connector attributes in general?

I am not sure if you can do this with properties file. I believe this should work
#Component
public class MyTomcatWebServerCustomizer
implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
connector.setAttribute("relaxedQueryChars", "yourvaluehere");
}
});
}
}

If you are using Spring Boot 2.x then you need to use WebSeerverFactoryCustomizer as given below.
#Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory>
containerCustomizer(){
return new EmbeddedTomcatCustomizer();
}
private static class EmbeddedTomcatCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> {
connector.setAttribute("relaxedPathChars", "<>[\\]^`{|}");
connector.setAttribute("relaxedQueryChars", "<>[\\]^`{|}");
});
}
}

I did this as a working solution for me:
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
return new MyCustomizer();
}
private static class MyCustomizer implements EmbeddedServletContainerCustomizer {
#Override
public void customize(ConfigurableEmbeddedServletContainer factory) {
if(factory instanceof TomcatEmbeddedServletContainerFactory) {
customizeTomcat((TomcatEmbeddedServletContainerFactory) factory);
}
}
void customizeTomcat(TomcatEmbeddedServletContainerFactory factory) {
factory.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> {
connector.setAttribute("relaxedPathChars", "<>[\\]^`{|}");
connector.setAttribute("relaxedQueryChars", "<>[\\]^`{|}");
});
}
}

The simplest method is to configure the server (add a line to application.properties).
You can add something like this:
server.tomcat.relaxed-path-chars=<,>,etc
Spring Documentation Comma-separated list of additional unencoded characters that should be allowed in URI paths. Only "< > [ \ ] ^ ` { | }" are allowed.*

Related

How can I override configuration in spring using conditional annotations (for example)

I have the following class available in a dependency jar:
#Configuration
#EnableSpringDataWebSupport
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setBasePath(CoreHttpPathStore.REST_PATH);
config.setReturnBodyOnCreate(true);
config.setReturnBodyOnUpdate(true);
config.hasResourceMappingForDomainType(GrantedAuthority.class);
}
#Override
public void configureJacksonObjectMapper(ObjectMapper mapper) {
super.configureJacksonObjectMapper(mapper);
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
}
I want this to be the default configuration, but in some case, I must add some configuration, for this, I create in my application this new class:
#Configuration
#EnableSpringDataWebSupport
public class MyAppRepositoryRestConfig extends RepositoryRestConfig {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
super.configureRepositoryRestConfiguration(config);
config.exposeIdsFor(
User.class,
Client.class,
Role.class,
Organization.class,
);
}
// #Override
// public void configureJacksonObjectMapper(ObjectMapper mapper) {
// super.configureJacksonObjectMapper(mapper);
// }
}
The problem is that the method configureRepositoryRestConfiguration in the jar is called twice, which makes me believe this is not what I should do.
How can I boot my configuration conditionally ?
You can try to do this, create your config as a bean:
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return new RepositoryRestConfigurer() {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Class1.class, Class2.class);
}
};
}

Can I change the default definition displayed by Swagger?

Can I change the default definition from 'default' to my own one. I would like the page to load and instead of it loading the 'default' it would load mine which is just called 'swagger' in this case:
I am using Spring fox and Spring boot. This is my Swagger Config class:
#Configuration
#EnableSwagger2WebMvc
#Import(SpringDataRestConfiguration.class)
public class SwaggerDocumentationConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.openet.usage.trigger"))
.paths(PathSelectors.any())
.build();
}
private static Predicate<String> matchPathRegex(final String... pathRegexs) {
return new Predicate<String>() {
#Override
public boolean apply(String input) {
for (String pathRegex : pathRegexs) {
if (input.matches(pathRegex)) {
return true;
}
}
return false;
}
};
}
#Bean
WebMvcConfigurer configurer () {
return new WebMvcConfigurerAdapter() {
#Override
public void addResourceHandlers (ResourceHandlerRegistry registry) {
registry.addResourceHandler("/config/swagger.json").
addResourceLocations("classpath:/config");
registry
.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
};
}
}
It is possible to change this behavior, but it looks more like a hack.
The SwaggerResourcesProvider is responsible for providing info for the dropdown list. First, implement this interface. Second, add the Primary annotation to your class to become the main implementation that should be used instead of the default InMemorySwaggerResourcesProvider class. But it still makes sense to reuse definitions provided by InMemorySwaggerResourcesProvider and that is why it should be injected.
The last part is to implement the overridden get method and change to the list you want to display. This example should display only one definition named swagger.
// other annotations
#Primary
public class SwaggerDocumentationConfig implements SwaggerResourcesProvider {
private final InMemorySwaggerResourcesProvider resourcesProvider;
#Inject
public MySwaggerConfig(InMemorySwaggerResourcesProvider resourcesProvider) {
this.resourcesProvider = resourcesProvider;
}
#Override
public List<SwaggerResource> get() {
return resourcesProvider.get().stream()
.filter(r -> "swagger".equals(r.getName()))
.collect(Collectors.toList());
}
// the rest of the configuration
}
I just did a redirect in my controller:
#RequestMapping(value = "/", method = RequestMethod.GET)
public void redirectRootToSwaggerDocs(HttpServletResponse response) throws IOException {
response.sendRedirect("/my-api/swagger-ui.html?urls.primaryName=swagger");
}
The easiest way I found is just to make the groupName rank highly alphabetically. Such as "1 swagger", "a swagger" or "-> swagger".
...
return new Docket(DocumentationType.OAS_30)
.groupName("-> swagger");
...
...
return new Docket(DocumentationType.OAS_30)
.groupName("<what u want>")
...
just set a default group name.

How to add custom MeterRegisty for Spring Boot 2

I am currently exporting Actuator metrics for my Spring Boot Webflux project to DataDog with 10 seconds interval. I would like to add another exporter for one of our internal system that is not in the list of supported backends. Looking at the implementation from DataDogMeterRegistry I came up with the following.
public interface ExternalConfig extends StepRegistryConfig {
ExternalConfig DEFAULT = k -> null;
#Override
default String prefix() {
return "vwexternal";
}
}
#Slf4j
public class ExternalMeterRegistry extends StepMeterRegistry {
public ExternalMeterRegistry() {
this(ExternalConfig.DEFAULT, Clock.SYSTEM);
}
public ExternalMeterRegistry(StepRegistryConfig config, Clock clock) {
super(config, clock);
}
#Override
protected void publish() {
log.info("HERE");
}
#Override
protected TimeUnit getBaseTimeUnit() {
return TimeUnit.MILLISECONDS;
}
}
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
Metrics.addRegistry(new ExternalMeterRegistry());
}
}
However this is not working since no logs are printed.
My question is how can I add and implement another MeterRegistry for Spring Boot Micrometer?
You need to start the publishing. Compare with the LoggingMeterRegistry
In your constructor something like:
start(new NamedThreadFactory("vw-metrics-publisher"))

Use #PathParam(javax.websocket.server.PathParam) in WebSocketConfigurer for Spring Boot application

I have created a spring boot application in which I want to use Web Sockets. When I am using it without parameters its working fine. Below is the code without the parameters
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ABC(), "/getABC").setAllowedOrigins("*");
registry.addHandler(new XYZ(), "/getXYZ").setAllowedOrigins("*");
}
}
But now I need to pass a parameter to it using #PathParam. I am not able to use it in this configuration like
registry.addHandler(new XYZ(), "/getXYZ{someId}").setAllowedOrigins("*");
My Handler code:
public class XYZ extends TextWebSocketHandler {
static List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
String someId;
public XYZ() {
}
public XYZ(#PathParam(value = "someId") String someId) {
this.someId= someId;
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// the messages will be broadcasted to all users.
sessions.add(session);
}
}
I think there is some problem with the syntax, try using
public XYZ(#PathParam("someId") String someId)

How create table with spring data cassandara?

I have created my own repository like that:
public interface MyRepository extends TypedIdCassandraRepository<MyEntity, String> {
}
So the question how automatically create cassandra table for that? Currently Spring injects MyRepository which tries to insert entity to non-existent table.
So is there a way to create cassandra tables (if they do not exist) during spring container start up?
P.S. It would be very nice if there is just config boolean property without adding lines of xml and creation something like BeanFactory and etc. :-)
Overide the getSchemaAction property on the AbstractCassandraConfiguration class
#Configuration
#EnableCassandraRepositories(basePackages = "com.example")
public class TestConfig extends AbstractCassandraConfiguration {
#Override
public String getKeyspaceName() {
return "test_config";
}
#Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE_DROP_UNUSED;
}
#Bean
public CassandraOperations cassandraOperations() throws Exception {
return new CassandraTemplate(session().getObject());
}
}
You can use this config in the application.properties
spring.data.cassandra.schema-action=CREATE_IF_NOT_EXISTS
You'll also need to Override the getEntityBasePackages() method in your AbstractCassandraConfiguration implementation. This will allow Spring to find any classes that you've annotated with #Table, and create the tables.
#Override
public String[] getEntityBasePackages() {
return new String[]{"com.example"};
}
You'll need to include spring-data-cassandra dependency in your pom.xml file.
Configure your TestConfig.class as below:
#Configuration
#PropertySource(value = { "classpath:Your .properties file here" })
#EnableCassandraRepositories(basePackages = { "base-package name of your Repositories'" })
public class CassandraConfig {
#Autowired
private Environment environment;
#Bean
public CassandraClusterFactoryBean cluster() {
CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean();
cluster.setContactPoints(env.getProperty("contactpoints from your properties file"));
cluster.setPort(Integer.parseInt(env.getProperty("ports from your properties file")));
return cluster;
}
#Bean
public CassandraConverter converter() {
return new MappingCassandraConverter(mappingContext());
}
#Bean
public CassandraSessionFactoryBean session() throws Exception {
CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
session.setCluster(cluster().getObject());
session.setKeyspaceName(env.getProperty("keyspace from your properties file"));
session.setConverter(converter());
session.setSchemaAction(SchemaAction.CREATE_IF_NOT_EXISTS);
return session;
}
#Bean
public CassandraOperations cassandraTemplate() throws Exception {
return new CassandraTemplate(session().getObject());
}
#Bean
public CassandraMappingContext mappingContext() throws ClassNotFoundException {
CassandraMappingContext mappingContext= new CassandraMappingContext();
mappingContext.setInitialEntitySet(getInitialEntitySet());
return mappingContext;
}
#Override
public String[] getEntityBasePackages() {
return new String[]{"base-package name of all your entity annotated
with #Table"};
}
#Override
protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
return CassandraEntityClassScanner.scan(getEntityBasePackages());
}
}
This last getInitialEntitySet method might be an Optional one. Try without this too.
Make sure your Keyspace, contactpoints and port in .properties file. Like :
cassandra.contactpoints=localhost,127.0.0.1
cassandra.port=9042
cassandra.keyspace='Your Keyspace name here'
Actually, after digging into the source code located in spring-data-cassandra:3.1.9, you can check the implementation:
org.springframework.data.cassandra.config.SessionFactoryFactoryBean#performSchemaAction
with implementation as following:
protected void performSchemaAction() throws Exception {
boolean create = false;
boolean drop = DEFAULT_DROP_TABLES;
boolean dropUnused = DEFAULT_DROP_UNUSED_TABLES;
boolean ifNotExists = DEFAULT_CREATE_IF_NOT_EXISTS;
switch (this.schemaAction) {
case RECREATE_DROP_UNUSED:
dropUnused = true;
case RECREATE:
drop = true;
case CREATE_IF_NOT_EXISTS:
ifNotExists = SchemaAction.CREATE_IF_NOT_EXISTS.equals(this.schemaAction);
case CREATE:
create = true;
case NONE:
default:
// do nothing
}
if (create) {
createTables(drop, dropUnused, ifNotExists);
}
}
which means you have to assign CREATE to schemaAction if the table has never been created. And CREATE_IF_NOT_EXISTS dose not work.
More information please check here: Why `spring-data-jpa` with `spring-data-cassandra` won't create cassandra tables automatically?

Categories