I've spend a lot of time to validate XML against multiple XSD in Spring. Even when I give all XSD schemas to SchemaFactory it does not work because main schema can't see import schema declared in main XSD file. Even when I give this schemas as files it does not work, because Spring's resource files can't be resolved to absolute path.
<xs:import namespace="http://test.com/types" schemaLocation="types.xsd"/>
1. First we need this dependency which can parse xsd schemas:
implementation("org.apache.ws.xmlschema:xmlschema-core:2.2.4")
2. We create 2 beans. One for storing our XSD's (it will automatically find other files if there this schemaLocation="..."), another for our Validator:
#Bean
fun schema(): XsdSchemaCollection {
return CommonsXsdSchemaCollection(
ClassPathResource("xsd/main.xsd")
).also { it.setInline(true) }
}
#Bean
fun myValidator(schema: XsdSchemaCollection): XmlValidator {
return schema.createValidator()
}
3. And we can use it:
#Autowired
private val myValidator: XmlValidator
fun validate(data: String): Array<SAXParseException> {
return myValidator.validate(StreamSource(data.byteInputStream()))
}
Array<SAXParseException> will contain list of validation exceptions if any of course
Related
I am a bit confused looking at the documentation on how to use a custom transformation strategy for enums. I am doing this in Kotlin, but a Java solution is fine too.
I am using 14.2
I have this
package org.mapstruct.custom.spi
import org.mapstruct.ap.spi.EnumTransformationStrategy
class StripLowerCaseEnumTransformationStrategy : EnumTransformationStrategy {
companion object {
const val NAME = "stripLowerCase"
}
override fun getStrategyName(): String {
return NAME
}
override fun transform(value: String, configuration: String): String {
return value.removePrefix(configuration).toLowerCase()
}
}
But when I try to use it within my EnumMappers, it says it cannot be found. What step am I missing?
#Mapper
interface EnumMapper {
#ValueMappings(
ValueMapping(source = "UNRECOGNIZED", target = MappingConstants.NULL),
ValueMapping(source = "PERSON_STATUS_INVALID", target = MappingConstants.NULL)
)
#EnumMapping(
nameTransformationStrategy = StripLowerCaseEnumTransformationStrategy.NAME,
configuration = "PERSON_STATUS_"
)
fun fromProto(status: PersonStatusProto): PersonStatus
#InheritInverseConfiguration
fun toProto(status: PersonStatus): PersonStatusProto
}
Here is the error: error: There is no registered EnumTransformationStrategy for 'stripLowerCase'. Registered strategies are: prefix, stripPrefix, stripSuffix, suffix.
I also have created the file resources/META-INF/services/org.mapstruct.ap.spi.EnumTransformationStrategy with the value org.mapstruct.custom.spi.StripLowerCaseEnumTransformationStrategy.
Here is also a picture of directory in case it is organized incorrectly:
The reason why the configured EnumTransformationStrategy is not used is because SPIs which are available only in the same compilation unit are not used.
In order for this to work you will need to move the implementation in a different module (not package) and make it available during the compilationl
An example for how this can be done with maven can be seen in this integration test.
Note: I see that you are placing the StripLowerCaseEnumTransformationStrategy under the org.mapstruct package. I would advise not to use packages that you don't own for placing your code, otherwise you might have problems with split packages when running on the Java module path.
I've got a fairly standard spring boot app which is built with gradle from several gradle modules. Here's the directory layout:
- root
- serviceA
- src/main/java
- org.example.serviceA
- ServiceAApplication.java
- serviceB
- serviceC
- common
- src/main/java
- org.example.common
- CommonSecurityConfiguration.java
What I would like to do is to include the CommonSecurityConfiguration class from the shared common module in serviceA. Note that ServiceAApplication and CommonSecurityConfiguration reside in different base packages.
I tried to use #Import(CommonSecurityConfiguration.class) on my ServiceAApplication, but that had no observable effect at all.
The only thing which worked was to annotate ServiceAApplication like so:
#SpringBootApplication(basePackages = { "org.example.serviceA", "org.example.common"})
public class ServiceAApplication { ... }
This approach works, but seems very coarse grained to me - it will import each and every component and configuration it finds in org.example.common.
Is there a better way to do this? Can I include individual classes into the component scan by listing them one by one?
Try to use
#Import(CommonSecurityConfiguration.class) above configuration class. So it would look like this:
#Configuration
#Import(CommonSecurityConfiguration.class)
public class ServiceAConfiguration { ... }
I believe what you are looking for is #CompnentScan("com.example"), this will tell Spring to look at all the files under the specified path recursively. (In this case it would be #ComponentScan("root"))
You find more info here: baeldun.com/spring-component-scanning
Hope this helps.
Since you want to control which components are brought in , we can make an annotation , let's call that annotation PickyComponentImport
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface PickyComponentImport{
}
Then on our SpringBootApplication annotation we can add a new filter which looks for this annotation.
#ComponentScan(basePackages = { "org.example.serviceA",
"org.example.common" }, includeFilters = #Filter(PickyComponentImport.class))
public class ServiceAApplication { ... }
Then we can just add that annotation on any class we want included
#Configuration
#PickyComponentImport
public class CommonSecurityConfiguration {
}
EDIT: I think if you go with this approach you can just componentScan basepackage as root.
I want to use a yml configuration file in my project. I am using jackson-dataformat-yaml for parsing yml files. But I need to parse yml comments as well. I used the similar approach in python using ruamel yaml. How can I do the same in java?
Upd.
What for? Well, I wanted to make it possible to override my configuration options by using command line arguments. So, to generate description message for each option, I wanted to use my comments. Like this:
In my config.yml
# Define a source directory
src: '/foo/bar'
# Define a destination directory
dst: '/foo/baz'
So when you run your program with the --help flag, you'll see the following output:
Your program can be ran with the following options:
--src Define a source directory
--dst Define a destination directory
The main benefit in such a model is that you don't ever need to repeat the same statement twice, because they can be retrieved from the configuration file.
Basically, you have three layers of data:
Your configuration schema. This defines the values that are to be defined in the configuration file.
The configuration file itself, which describes the usual configuration on the current machine.
One-time switches, which override the usual configuration.
The descriptions of what each value does belong to the schema, not to the configuration file itself. Think about it: If someone edits the configuration file on their machine and changes the comments, your help output would suddenly show different descriptions.
My suggestion would be to add the descriptions to the schema. The schema is the Java class you load your YAML into. I am not sure why you are using Jackson, since it uses SnakeYaml as parser and SnakeYaml is perfectly able to deserialize into Java classes, but has more configuration options since it does not generalize over JSON and YAML like Jackson does.
Here's a general idea how to do it with SnakeYaml (beware, untested):
// ConfigParam.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface ConfigParam { String description(); }
// Configuration.java
public class Configuration {
#ConfigParam("Define a source directory")
String src;
#ConfigParam("Define a destination directory")
String dst;
}
// loading code
Yaml yaml = new Yaml(new Constructor(Configuration.class));
Configuration config = yaml.loadAs(input, Configuration.class);
// help generation code
System.out.println("Your program can be ran with the following options:")
for (Field field: Configuration.class.getFields()) {
ConfigParam ann = field.getAnnotation(ConfigParam.class);
if (ann != null) {
System.out.println(String.format("--%s %s", field.getName(), ann.description());
}
}
For mapping actual parameters to the configuration, you can also loop over class fields and map the parameters to the field names after having loaded the configuration (to replace the standard values with the given ones).
Using Spring Boot 1.3.0.RELEASE
I have a couple of yaml files that describe several instances of a program. I now want to parse all those files into a List<Program> (Map, whatever), so I can later on search for the most appropriate instance for a given criteria in all the programs.
I like the approach with #ConfigurationProperties a lot, and it works good enough for a single yaml-file, but I haven't found a way yet to read all files in a directory using that method.
Current approach working for a single file:
programs/program1.yml
name: Program 1
minDays: 4
maxDays: 6
can be read by
#Configuration
#ConfigurationProperties(locations = "classpath:programs/program1.yml", ignoreUnknownFields = false)
public class ProgramProperties {
private Program test; //Program is a POJO with all the fields in the yml.
//getters+setters
I tried changing the locations to an Array listing all of my files locations = {"classpath:programs/program1.yml", "classpath:programs/program2.yml"} as well as using locations = "classpath:programs/*.yml", but that still only loads the first file (array-approach) or nothing at all (wildcard-approach).
So, my question is, what is the best way in Spring Boot to load a bunch of yaml files in a classpath-directory and parse them into a (List of) POJO, so they can be autowired in a Controller? Do I need to use Snakeyaml directly, or is there an integrated mechanism that I just haven't found yet?
EDIT:
A working approach is doing it manually:
private static final Yaml yaml = new Yaml(new Constructor(Program.class));
private static final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
for (Resource resource : resolver.getResources("/programs/*.yml")) {
Object data = yaml.load(resource.getInputStream());
programList.add((Program) data);
}
}
catch (IOException ioe) {
logger.error("failed to load resource", ioe);
}
In Spring, it is possible to load multiple configuration properties files using PropertySource annotation, but not YAML files. See section 26.6.4 in link below:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties
However, from your problem, it seems that you can configure all your programs in single YAML and then get all list of programs in a single list.
Sample YAML (all.yaml)
programs:
- name: A
min: 1
max: 2
- name: B
min: 3
max: 4
Config.java
#Configuration
#ConfigurationProperties(locations={"classpath:all.yaml"})
public class Config{
private List<Program> programs;
public void setPrograms(List<Program> programs) {
this.programs = programs;
}
public List<Program> getPrograms() {
return programs;
}
}
What I am currently doing, as far as I understood your question, is nearly the same.
I am having an application.yml and also profile-specific yml files, e.g. application-{profile}.yml in my src/main/resources.
In the application.yml I have defined the default profile key-values, which are partially overridden by the profile-specific yml files.
If you want to have a type-safe and well defined access of your YML key/values, then you can use the following approach:
#ConfigurationProperties
public class AppSettings {
String name; // has to be the same as the key in your yml file
// setters/getters
}
In your Spring-Boot config, you have to add the following annotations onto your config class:
#ComponentScan
#EnableAutoConfiguration
#EnableConfigurationProperties( value = { AppSettings.class, SomeOtherSettings.class } )
public class SpringContextConfig {
#Autowired
private AppSettings appSettings;
public void test() {
System.out.println(appSettings.getName());
}
}
The #Autowiring is also accessible from other Beans.
The other way around (without an extra separated and type-safe class, is to access the YML-values via #Value("${name}").
To bring it together in a short manner:
Yes, it is possible to use several YAML files for your application via Spring-profiles. You define your current active spring profile via command args, programmatically or via your system env (SPRING_PROFILES_ACTIVE=name1,name2).
Therefore you can have several application.yml files for each profile (see above).
I have a grails project that contains a few domain objects. I am using a java project in this code which can parse a document for me. The controller that calls that Java project is using JAXB to generate XML from the object returned by the Java project.
I want to use this XML document (which is generated after some text parsing, using JAXB) to populate my Domain classes in my grails project. How does this work in grails? Can I use something like Castor, and create a mapping using the names of my groovy classes? The idea is I want to generate new entries in the database and save it for the user based on whatever text was parsed out of the document they uploaded.
How does this even work in grails anyway? Can I create a new Domain object from another object's controller with something like
Project p = new Project();
and then do a p.save()?
Download the Castor Core and Castor XML jars from here and put them in the lib directory (there's probably a better way to manage this dependency using Grails' dependency management, but this one's a quick and dirty).
With Castor introspection mode you don't need to worry about creating mapping files if your XML matches up nicely with your domains. Here's an example:
grails-app/domain/MyDomain.groovy
class MyDomain {
String foo
String bar
}
grails-app/controllers/MyController.groovy
import org.exolab.castor.xml.Unmarshaller
import java.io.ByteArrayInputStream
class MyController {
def myAction = {
def xml = '''
<myDomain>
<foo>My Foo String</foo>
<bar>My Bar String</bar>
</myDomain>
'''
def reader = new ByteArrayInputStream(xml.bytes).newReader()
def domain = (MyDomain)Unmarshaller.unmarshal(MyDomain.class, reader)
domain.save()
def count = MyDomain.countByFoo('My Foo String')
render "Found $count results"
}
}
Navigate to localhost:8080/appname/my/myAction and it should display "Found N results", N > 0.