I am using SnakeYAML as my YAML parser for a project, and I don't know how to set keys that are nested. For instance, here is a YAML file with nested keys in it.
control:
advertising:
enabled: true
logging:
chat: true
commands: true
format: '%id% %date% %username% | %value%'
My goal is to be able to easily set the path control.advertising.enabled or any other path to any value.
When I use
void Set(String key, Object value, String configName){
Yaml yaml = new Yaml();
OutputStream oS;
try {
oS = new FileOutputStream(main.getDataFolder() + File.separator + configName);
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
Map<String, Object> data = new HashMap<String, Object>();
// set data based on original + modified
data.put(key, value);
String output = yaml.dump(data);
try {
oS.write(output.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
to set the value, instead of getting
logging:
chat: true
commands: true
format: '%id% %date% %username% | %value%'
the entire yaml file is cleared and I only get
{logging.chat: false}
Thank you!
Define your structure with Java classes:
public class Config {
public static class Advertising {
public boolean enabled;
}
public static class Control {
public Advertising advertising;
}
public static class Logging {
public boolean chat;
public boolean commands;
public String format;
}
Control control;
Logging logging;
}
Then you can modify it like this:
Yaml yaml = new Yaml();
Config config = yaml.loadAs(inputStream, Config.class);
config.control.advertising.enabled = false;
String output = yaml.dump(config);
Note that loading & saving YAML data this way might mess with the order of mapping keys, because this order is not preserved. I assume that the output order will be according to the field order in the Java classes, but I'm not sure.
Related
I have the following legacy class which used PropertyConfigurator under Log4J 1.x
There's no equivalent under Log4J 2.x but I am trying to preserve the functionality at method level.
The existing answers on Stack Overflow are for locating the configuration file, but the legacy code I have in loadConfiguration is setting properties inside the configuration file about where the actual log file will be output.
I wish to know what the equivalent would be under Log4J2. It's not clear to me how the
loadConfiguration method will change due to not having a PropertyConfigurator under log4j 2.x.
Thanks in advance.
Existing code:
public class BasicLogConfigurator {
public static final String LOG_LEVEL_KEY = "log.level.override";
private static final Level DEFAULT_LOG_LEVEL = Level.ERROR;
private static final String LOG4J_PROPS_LOCATION = "/log4j.properties";
private static final String LOG_FILE_PATTERN = "CustomLog_%s.log";
private static final Date TIMESTAMP = new Date();
private static final String METADATA_FOLDER = ".metadata";
public static void loadDefaultConfiguration() {
// 1. Grab the Log file location
File logLocation = getLogFileLocationInMetadataDirectory();
// 2. Grab default Log level
Level logLevel = DEFAULT_LOG_LEVEL;
loadConfiguration(logLocation, logLevel);
}
public static void loadConfiguration(File logLocation, Level logLevel) {
Properties properties = getConfigurationProperties();
// 1. Setup the Log file location
properties.setProperty("log4j.appender.FILE.file", logLocation.getAbsolutePath());
// 2. Setup the default Log level
properties.setProperty("log4j.rootLogger", String.format("%s, CONSOLE, FILE", sanitizeLogLevel(logLevel)));
LogManager.resetConfiguration();
PropertyConfigurator.configure(properties);
}
public static File getLogFileLocationInMetadataDirectory() {
String workspaceDirectory = getWorkspaceDirectory();
File metadataDirectory = new File(workspaceDirectory, METADATA_FOLDER);
File logLocation = getLogFileLocation(metadataDirectory);
return logLocation;
}
public static File getLogFileLocation(File directory) {
return new File(directory, String.format(LOG_FILE_PATTERN, formatTimeStamp(TIMESTAMP)));
}
private BasicLogConfigurator() {
// prevent instantiation
}
private static Level sanitizeLogLevel(Level selectedLevel) {
String overridingLevel = System.getProperty(LOG_LEVEL_KEY);
if (overridingLevel != null) {
return Level.toLevel(overridingLevel, Level.DEBUG);
}
if (selectedLevel != null) {
return selectedLevel;
}
return DEFAULT_LOG_LEVEL;
}
public static String getWorkspaceDirectory() {
return ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString();
}
private static String formatTimeStamp(Date date) {
return new SimpleDateFormat("yyyyMMdd").format(date).toString();
}
public static Properties getConfigurationProperties() {
try {
Properties properties = new Properties();
properties.load(BasicLogConfigurator.class.getResourceAsStream(LOG4J_PROPS_LOCATION));
return properties;
} catch (IOException e) {
throw new RuntimeException(
String.format("Error while loading {%s}", LOG4J_PROPS_LOCATION), e);
}
}
}
So how is this done for Log4J2 given the inputs of the loadConfiguration method? And does getConfigurationProperties need to change to work with Log4J2?
I am new to Spring-boot/Java and trying to read the contents of a file in a String.
What's the issue:
I'm getting "File not found exception" and unable to read the file. Apparently, I'm not giving the correct file path.
i've attached the directory structure and my code. I'm in FeedProcessor file and want to read feed_template.php (see image)
public static String readFileAsString( ) {
String text = "";
try {
// text = new String(Files.readAllBytes(Paths.get("/src/main/template/feed_template_head.php")));
text = new String(Files.readAllBytes(Paths.get("../../template/feed_template_head.php")));
} catch (IOException e) {
e.printStackTrace();
}
return text;
}
You need to put template folder inside resource folder. And then use following code.
#Configuration
public class ReadFile {
private static final String FILE_NAME =
"classpath:template/feed_template_head.php";
#Bean
public void initSegmentPerformanceReportRequestBean(
#Value(FILE_NAME) Resource resource,
ObjectMapper objectMapper) throws IOException {
new BufferedReader(resource.getInputStream()).lines()
.forEach(eachLine -> System.out.println(eachLine));
}
}
I suggest you to go though once Resource topic in spring.
https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/resources.html
In my application.properties I add some custom attributes.
custom.mail.property.subject-message=This is a ä ö ü ß problem
In this class I have the representation of the custom attributes.
#Component
#ConfigurationProperties(prefix="custom.mail.property")
public class MailProperties {
private String subjectMessage;
public String getSubjectMessage() {
return subjectMessage;
}
public void setSubjectMessage(String subjectMessage) {
this.subjectMessage = subjectMessage;
}
And here I use my MailProperties:
#Service
public class SimpleUnknownResponseMessage extends MailProperties implements UnknownResponseMessage{
private JavaMailSender javaMailSender;
#Autowired
public SimpleUnknownResponseMessage(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
}
#Override
public void placeUnknownResponse(BookResponse bookResponse) {
MimeMessage message = javaMailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, "UTF-8");
helper.setSubject(this.getSubjectMessage());
javaMailSender.send(message);
} catch (MessagingException e) {
e.printStackTrace();
}
}
While debugging I can see that my this.getSubjectMessage() variable has this value inside: This is a ä ö ü à problem. So before sending my mail I already have an UTF-8 encoding problem.
I already checked the encoding of the application.properties file and its UTF-8.
My IDE(STS/Eclipse) and the project properties are also set on UTF-8.
How can I set the UTF-8 encoding for the text of my custom attributes in the application.properties file?
As already mentioned in the comments .properties files are expected to be encoded in ISO 8859-1. One can use unicode escapes to specify other characters. There is also a tool available to do the conversion. This can for instance be used in the automatic build so that you still can use your favorite encoding in the source.
Please, try to add PropertySource annotation with encoding parameter into your Configuaration file:
#PropertySource(value = "classpath:application-${env}.properties", encoding = "UTF-8")
Hope it helps.
I've faced with the same problem.
In Spring Boot there are 2 PropertySourceLoader which are used to load properties in application:
PropertiesPropertySourceLoader - supports UTF-8 only when load from XML
YamlPropertySourceLoader - supports UTF-8, but you have to change configuration format to use it
They're listed in the file https://github.com/spring-projects/spring-boot/blob/master/spring-boot/src/main/resources/META-INF/spring.factories
So we decided to write our own implementation of PropertySourceLoader which would be able to load properties from UTF-8 file correctly. The idea is from answer #BalusC - How to use UTF-8 in resource properties with ResourceBundle
Our PropertySourceLoader implementation:
public class UnicodePropertiesPropertySourceLoader implements PropertySourceLoader {
#Override
public String[] getFileExtensions() {
return new String[]{"properties"};
}
#Override
public PropertySource<?> load(String name, Resource resource, String profile) throws IOException {
if (profile == null) {
Properties properties = new Properties();
PropertyResourceBundle bundle = new PropertyResourceBundle(new InputStreamReader(resource.getInputStream(), "UTF-8"));
Enumeration<String> keys = bundle.getKeys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
properties.setProperty(key, bundle.getString(key));
}
if (!properties.isEmpty()) {
return new PropertiesPropertySource(name, properties);
}
}
return null;
}
}
Then we created file resources/META-INF/spring.factories with content:
# Custom PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
your.own.package.UnicodePropertiesPropertySourceLoader
Now we have 3 PropertySourceLoader in our application in following order:
UnicodePropertiesPropertySourceLoader
PropertiesPropertySourceLoader
YamlPropertySourceLoader
NOTES!
I'm not sure that it is proper usage of PropertyResourceBundle
I'm not sure that order of PropertySourceLoaders in Spring Boot will be the same if you make a dedicated library to reuse it in other projects.
In our project this solution works fine.
UPDATE!
It's better to implement load method of UnicodePropertiesPropertySourceLoader without PropertyResourceBundle:
#Override
public PropertySource<?> load(String name, Resource resource, String profile) throws IOException {
if (profile == null) {
Properties properties = new Properties();
properties.load(new InputStreamReader(resource.getInputStream(), "UTF-8"));
if (!properties.isEmpty()) {
return new PropertiesPropertySource(name, properties);
}
}
return null;
}
just converted the text with the special chars with https://native2ascii.net/
To set the UTF-8 encoding for the text in the application.properties (and any other Java properties as well as environment variables) add -Dfile.encoding=UTF-8 to java command line agrs.
How to read this (fixedRate = 12000) 12000 form property file in Spring.
#Scheduled(fixedRate=120000)
public void tlogZipping() throws MposWSException {
LOGGER.info("Started tlog Zipping Job............. {}" + new Date());
try {
//......................
} catch (Exception e) {
LOGGER.error("FAIL TO CREATE RECEIPT ZIP FILE: {}",e);
throw new MposWSException(MposWSErrorCodes.FAIL_TO_CREATE_RECEIPT_ZIP_FILE, e);
}
LOGGER.info("Stopped tlog Zipping Job.............");
}
You can add your properties file to the folder where your classes are exists.
and then try this code.
#PropertySource("classpath:config.properties") //set your Properties file source.
public class YourClass{
//1.2.3.4
#Value("${TLOG_ZIPPING_TIME_INTERVEL_IN_MINUTES }") //read your Property Key
private String IntervalTimeInMin; //Store in this Variable.
//hello
#Value("${anotherProperty}") //readd another Property Key
private String anotherProperty; //Store in this Variable.
For more assistence you can refer this Link Here
With Spring 4 and Hibernate 4, I was able to use Reflection to get the Hibernate Configuration object from the current environment, using this code:
#Autowired LocalContainerEntityManagerFactoryBean lcemfb;
EntityManagerFactoryImpl emf = (EntityManagerFactoryImpl) lcemfb.getNativeEntityManagerFactory();
SessionFactoryImpl sf = emf.getSessionFactory();
SessionFactoryServiceRegistryImpl serviceRegistry = (SessionFactoryServiceRegistryImpl) sf.getServiceRegistry();
Configuration cfg = null;
try {
Field field = SessionFactoryServiceRegistryImpl.class.getDeclaredField("configuration");
field.setAccessible(true);
cfg = (Configuration) field.get(serviceRegistry);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
SchemaUpdate update = new SchemaUpdate(serviceRegistry, cfg);
With Hibernate 5, I must use some MetadataImplementor, which doesn't seems to be available from any of those objects. I also tried to use MetadataSources with the serviceRegistry. But it did say that it's the wrong kind of ServiceRegistry.
Is there any other way to get this working?
Basic idea for this problem is:
implementation of org.hibernate.integrator.spi.Integrator which stores required data to some holder. Register implementation as a service and use it where you need.
Work example you can find here https://github.com/valery-barysok/spring4-hibernate5-stackoverflow-34612019
create org.hibernate.integrator.api.integrator.Integrator class
import hello.HibernateInfoHolder;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
public class Integrator implements org.hibernate.integrator.spi.Integrator {
#Override
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
HibernateInfoHolder.setMetadata(metadata);
HibernateInfoHolder.setSessionFactory(sessionFactory);
HibernateInfoHolder.setServiceRegistry(serviceRegistry);
}
#Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
}
}
create META-INF/services/org.hibernate.integrator.spi.Integrator file
org.hibernate.integrator.api.integrator.Integrator
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
new SchemaExport((MetadataImplementor) HibernateInfoHolder.getMetadata()).create(true, true);
new SchemaUpdate(HibernateInfoHolder.getServiceRegistry(), (MetadataImplementor) HibernateInfoHolder.getMetadata()).execute(true, true);
}
}
I would like to add up on Aviad's answer to make it complete as per OP's request.
The internals:
In order to get an instance of MetadataImplementor, the workaround is to register an instance of SessionFactoryBuilderFactory through Java's ServiceLoader facility. This registered service's getSessionFactoryBuilder method is then invoked by MetadataImplementor with an instance of itself, when hibernate is bootstrapped. The code references are below:
Service Loading
Invocation of getSessionFactoryBuilder
So, ultimately to get an instance of MetadataImplementor, you have to implement SessionFactoryBuilderFactory and register so ServiceLoader can recognize this service:
An implementation of SessionFactoryBuilderFactory:
public class MetadataProvider implements SessionFactoryBuilderFactory {
private static MetadataImplementor metadata;
#Override
public SessionFactoryBuilder getSessionFactoryBuilder(MetadataImplementor metadata, SessionFactoryBuilderImplementor defaultBuilder) {
this.metadata = metadata;
return defaultBuilder; //Just return the one provided in the argument itself. All we care about is the metadata :)
}
public static MetadataImplementor getMetadata() {
return metadata;
}
}
In order to register the above, create simple text file in the following path(assuming it's a maven project, ultimately we need the 'META-INF' folder to be available in the classpath):
src/main/resources/META-INF/services/org.hibernate.boot.spi.SessionFactoryBuilderFactory
And the content of the text file should be a single line(can even be multiple lines if you need to register multiple instances) stating the fully qualified class path of your implementation of SessionFactoryBuilderFactory. For example, for the above class, if your package name is 'com.yourcompany.prj', the following should be the content of the file.
com.yourcompany.prj.MetadataProvider
And that's it, if you run your application, spring app or standalone hibernate, you will have an instance of MetadataImplementor available through a static method once hibernate is bootstraped.
Update 1:
There is no way it can be injected via Spring. I digged into Hibernate's source code and the metadata object is not stored anywhere in SessionFactory(which is what we get from Spring). So, it's not possible to inject it. But there are two options if you want it in Spring's way:
Extend existing classes and customize all the way from
LocalSessionFactoryBean -> MetadataSources -> MetadataBuilder
LocalSessionFactoryBean is what you configure in Spring and it has an object of MetadataSources. MetadataSources creates MetadataBuilder which in turn creates MetadataImplementor. All the above operations don't store anything, they just create object on the fly and return. If you want to have an instance of MetaData, you should extend and modify the above classes so that they store a local copy of respective objects before they return. That way you can have a reference to MetadataImplementor. But I wouldn't really recommend this unless it's really needed, because the APIs might change over time.
On the other hand, if you don't mind building a MetaDataImplemetor from SessionFactory, the following code will help you:
EntityManagerFactoryImpl emf=(EntityManagerFactoryImpl)lcemfb.getNativeEntityManagerFactory();
SessionFactoryImpl sf=emf.getSessionFactory();
StandardServiceRegistry serviceRegistry = sf.getSessionFactoryOptions().getServiceRegistry();
MetadataSources metadataSources = new MetadataSources(new BootstrapServiceRegistryBuilder().build());
Metadata metadata = metadataSources.buildMetadata(serviceRegistry);
SchemaUpdate update=new SchemaUpdate(serviceRegistry,metadata); //To create SchemaUpdate
// You can either create SchemaExport from the above details, or you can get the existing one as follows:
try {
Field field = SessionFactoryImpl.class.getDeclaredField("schemaExport");
field.setAccessible(true);
SchemaExport schemaExport = (SchemaExport) field.get(serviceRegistry);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
Take a look on this one:
public class EntityMetaData implements SessionFactoryBuilderFactory {
private static final ThreadLocal<MetadataImplementor> meta = new ThreadLocal<>();
#Override
public SessionFactoryBuilder getSessionFactoryBuilder(MetadataImplementor metadata, SessionFactoryBuilderImplementor defaultBuilder) {
meta.set(metadata);
return defaultBuilder;
}
public static MetadataImplementor getMeta() {
return meta.get();
}
}
Take a look on This Thread which seems to answer your needs
Well, my go to on this:
public class SchemaTranslator {
public static void main(String[] args) throws Exception {
new SchemaTranslator().run();
}
private void run() throws Exception {
String packageName[] = { "model"};
generate(packageName);
}
private List<Class<?>> getClasses(String packageName) throws Exception {
File directory = null;
try {
ClassLoader cld = getClassLoader();
URL resource = getResource(packageName, cld);
directory = new File(resource.getFile());
} catch (NullPointerException ex) {
throw new ClassNotFoundException(packageName + " (" + directory + ") does not appear to be a valid package");
}
return collectClasses(packageName, directory);
}
private ClassLoader getClassLoader() throws ClassNotFoundException {
ClassLoader cld = Thread.currentThread().getContextClassLoader();
if (cld == null) {
throw new ClassNotFoundException("Can't get class loader.");
}
return cld;
}
private URL getResource(String packageName, ClassLoader cld) throws ClassNotFoundException {
String path = packageName.replace('.', '/');
URL resource = cld.getResource(path);
if (resource == null) {
throw new ClassNotFoundException("No resource for " + path);
}
return resource;
}
private List<Class<?>> collectClasses(String packageName, File directory) throws ClassNotFoundException {
List<Class<?>> classes = new ArrayList<>();
if (directory.exists()) {
String[] files = directory.list();
for (String file : files) {
if (file.endsWith(".class")) {
// removes the .class extension
classes.add(Class.forName(packageName + '.' + file.substring(0, file.length() - 6)));
}
}
} else {
throw new ClassNotFoundException(packageName + " is not a valid package");
}
return classes;
}
private void generate(String[] packagesName) throws Exception {
Map<String, String> settings = new HashMap<String, String>();
settings.put("hibernate.hbm2ddl.auto", "drop-create");
settings.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL94Dialect");
MetadataSources metadata = new MetadataSources(
new StandardServiceRegistryBuilder()
.applySettings(settings)
.build());
for (String packageName : packagesName) {
System.out.println("packageName: " + packageName);
for (Class<?> clazz : getClasses(packageName)) {
System.out.println("Class: " + clazz);
metadata.addAnnotatedClass(clazz);
}
}
SchemaExport export = new SchemaExport(
(MetadataImplementor) metadata.buildMetadata()
);
export.setDelimiter(";");
export.setOutputFile("db-schema.sql");
export.setFormat(true);
export.execute(true, false, false, false);
}
}