How can I throw an Exception when a properties file contains a duplicate property?
Here is an example demonstrating this situation:
# Properties-file
directory=D:\\media\\D-Downloads\\Errorfile\\TEST_A
directory=D:\\media\\D-Downloads\\Errorfile\\TEST_B
#directory=D:\\media\\D-Downloads\\Errorfile\\TEST_C
I suppose you are reading the file with something like Properties.load(). It sets the parameter internally using put(key, value). You can override that method to get the desired behaviour like e.g.
new Properties() {
#Override
public synchronized Object put(Object key, Object value) {
if (get(key) != null) {
throw new IllegalArgumentException(key + " already present.");
}
return super.put(key, value);
}
}.load(...);
EDIT:
Integrating this into the OP's code:
File propertiesFile = new File("D:/media/myProperties.properties");
Properties properties = new Properties() {
#Override
public synchronized Object put(Object key, Object value) {
if (get(key) != null) {
// or some other RuntimeException you like better...
throw new IllegalArgumentException(key + " already present.");
}
return super.put(key, value);
}
}
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(propertiesFile))) {
properties.load(bis);
} catch (IllegalArgumentException ex) {
//
}
By the way, why would you want to catch the exception? I'd not continue a program if its configuration is corrupt (maybe catching at top-level to log the event). But exception-handling is a different topic...
(EDIT: my original code samles didn't compile, I corrected them)
As mentioned here Tool to find duplicate keys and value in properties file
"
There are two nice tools that I use
unival npm package: this is a command line tool to detect duplicate keys, values or lines.
npm command to install the package: npm i unival
Link: https://www.npmjs.com/package/unival
unival extension: if you use vscode, this is a extremely helpful extension to detect duplicates on the fly.
"
The best way is to have a test to run unival command, this will prevent duplicate values going to properties file
Here is how I am loading the properties:
File propertiesFile = new File("D:/media/myProperties.properties");
Properties properties = new Properties();
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(propertiesFile))) {
properties.load(bis);
} catch (Exception ex) {
//
}
#Ralf How can I adapt your code?
The Ralf Kleberhoff answer is correct;
however, I would not use an anonymous class.
It seems likely that you want to use this functionality more than once,
so I would create a class that extends Properties and override the put as did Ralf
Note that the put method is from the Hashtable class which Properties extends.
Here is an example (I didn't try to compile it):
public class UniqueProperties extends Properties
{
#Override
public synchronized String put(Object key, Object value)
{
if (get(key) != null)
{
throw new IllegalArgumentException(key + " already present.");
}
super.put(key, value);
}
}
Related
I attempted to create a Spigot plugin today and I'm coming up with an error that's hard to fix. When I was trying to create a file system to save some data I downloaded the source code for the library SnakeYAML and put it in my src. Then I followed this tutorial and created a config and followed all the instructions. I'm getting a NullPointerException. It turns out the config object is null. I'm not sure what's happening.
PlayerYML getConfig(File playerYml) {
try {
InputStream ymlStream = new FileInputStream(playerYml.getAbsolutePath());
System.out.println(ymlStream);
PlayerYML config = yaml.loadAs(ymlStream, PlayerYML.class);
return config;
} catch (Exception ex) {
System.out.println("getConfig() error");
ex.printStackTrace();
return null;
}
}
Here is my PlayerYML class:
import java.util.Map;
public class PlayerYML {
private int reputation;
private Map<String/*UUID*/, String/*Date*/> map;
public int getReputation() {
return reputation;
}
public void setReputation(int reputation) {
this.reputation = reputation;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
I appreciate all help! Thank you :)
You are trying to load an empty file/stream as on object which will result in null using SnakeYAML.
If you want to handle the absense of the file properly instead of just creating an empty file, you should check if it exists and directly create a default instance of the object if the file doesn't exist. If you then want to create the file with defaults for the user to edit them, just store the default instance of the object using one of the Yaml.dump* methods. That way you avoid creating an empty object by yourself. You still have to handle empty files in case of user errors.
What happened to me was that my InputStream was already entirely consumed by a BufferedReader just above, so there was nothing left in the Stream to be treated.
What I had to do was to reset the InputStream or simply open a new one.
Like we do the following in Spring
#Value("${varName:0}")
int varName;
Is there a way to do this using Google Guice?
In Guice you would annotate the method and make it optional. You then just assign the default value. If no property is there to be injected, it will be the default value.
For example:
public class TestModule3 extends AbstractModule {
#Override
protected void configure() {
// Properties p = new Properties();
// p.setProperty("myValue", "12");
// Names.bindProperties(binder(), p); // this binds the properties that usually come for a file
bind(Manager.class).to(ManagerImpl.class).in(Singleton.class);
}
public static interface Manager {
public void talk();
}
public static class ManagerImpl implements Manager {
#Inject(optional = true)
#Named("myValue")
int test = 0;
#Override
public void talk() {
System.out.println(test);
}
}
public static void main(String[] args) {
Manager instance = Guice.createInjector(new TestModule3()).getInstance(Manager.class);
instance.talk();
}
}
This will print "0" for you, because I commented out the property binding. If you remove the comments, it will bind the value 12 to the String myValue. The inject annotation takes care of the rest.
Hope that helps,
EDIT:
As #TavianBarnes pointed out, Guice 4+ has an OptionalBinder. I tried this for your usecase and could not make it work out of the box.
It appears that OptionalBinding is very useful for classes (actual instances), not for properties. Here is why:
You have to know all the properties in advance and bind them to their defaults. It is easy to forget them. The example shown by OP also shows that he does not know if he has the property available (based on the name).
Default implementation of property bindings don't work in combo with the OptionalBinding.
So the way you can make that work is like this:
OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, Names.named("myValue"))).setDefault()
.toInstance("777");
Properties p = new Properties();
p.setProperty("myValue", "12");
// use enumeration to include the default properties
for (Enumeration<?> e = p.propertyNames(); e.hasMoreElements();) {
String propertyName = (String) e.nextElement();
String value = p.getProperty(propertyName);
OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, Names.named(propertyName))).setBinding()
.toInstance(value);
}
I had to copy the Named binding code and change it to support optional bindings.
In summary:
I would prefer to use the optional=true flag + default value in code for properties.
Use the OptionalBinding for actual classes that can be optional.
Finally, there is one more thing you could do - this is my solution in my code. I have a similar requirement (not the optional, but default values).
I want:
Bind my properties
Check if my properties are a variable
Replace the variable
If the variable is not available set a default
Apache offers a handy library for this already which I reuse. This is how my properties look like:
myProperty=${ENV_VAR_NAME:-600}
This is the default annotation of how to define a default value.
The above property says:
Use the evnironment variable "ENV_VAR_NAME".
If "ENV_VAR_NAME" is not set, use the value "600"
Then I bind it as follows:
InputStream resourceAsStream = getClass().getResourceAsStream(path);
if(resourceAsStream == null) {
throw new IllegalArgumentException("No property file found for path: " + path);
}
try {
p.load(resourceAsStream);
EnvironmentVariableSubstitutor envSubstitutor = new EnvironmentVariableSubstitutor(false);
Set<Object> keys = p.keySet();
for(Object k : keys) {
String property = p.getProperty(k.toString());
property = envSubstitutor.replace(property);
p.put(k, property);
}
} catch (IOException e) {
throw new IllegalStateException("Could not load properties", e);
} finally {
try {
resourceAsStream.close();
} catch (IOException e) {
log.error("Could not close stream for resource " + path);
}
}
Names.bindProperties(binder(), p);
What this code does is:
Load the properties from a resource file
Use the EnvironmentVariableSubstitutor to process the values of the properties and overwrite the result. (see loop)
finally, bind the modified properties to their names.
These are all the solutions I can come up with at short notice :) let me know if something's unclear
Edit 2:
there is some info on OptionalBindings and properties + how to handle default values in this google thread as well: https://groups.google.com/forum/#!topic/google-guice/7Ga79iU_sb0
Artur
I've been working on a plugin that requires a fair amount of data being stored.
I have it being stored in a custom config file I found online that works basically the same as the default config.
The problem I'm having is that I am not sure how to actually close the file or if I even need to, as I know little about yaml configurations.
The code for the template I used is below.
I'm also curious as to advice on how I should store larger amounts of data in the future.
public class CustomConfig {
//store name of file to load/edit
private final String fileName;
//store plugin, to get file directory
private final JavaPlugin plugin;
//store actual hard disk file location
private File configFile;
//store ram file copy location
private FileConfiguration fileConfiguration;
//constructor taking a plugin and filename
public CustomConfig(JavaPlugin plugin, String fileName) {
//ensure plugin exists to get folder path
if (plugin == null)
throw new IllegalArgumentException("plugin cannot be null");
//set this classes plugin variable to the one passed to this method
this.plugin = plugin;
//get name of file to load/edit
this.fileName = fileName;
//get directory/folder of file to load/edit
File dataFolder = plugin.getDataFolder();
if (dataFolder == null)
throw new IllegalStateException();
//load config file from hard disk
this.configFile = new File(plugin.getDataFolder(), fileName);
reloadConfig();
}
public void reloadConfig() {
//load memory file from the hard copy
fileConfiguration = YamlConfiguration.loadConfiguration(configFile);
// Look for defaults in the jar
File configFile = new File(plugin.getDataFolder(), fileName);
if (configFile != null) {
YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(configFile);
fileConfiguration.setDefaults(defConfig);
}
}
public FileConfiguration getConfig() {
if (fileConfiguration == null) {
this.reloadConfig();
}
return fileConfiguration;
}
public void saveConfig() {
if (fileConfiguration == null || configFile == null) {
return;
} else {
try {
getConfig().save(configFile);
} catch (IOException ex) {
plugin.getLogger().log(Level.SEVERE, "Could not save config to " + configFile, ex);
}
}
}
public void saveDefaultConfig() {
if (!configFile.exists()) {
this.plugin.saveResource(fileName, false);
}
}
}
No. You do not have to close YamlConfiguration objects.
While the default config (JavaPlugin.getConfig()) is bound to the lifecycle of the plugin, custom ones are disposed when any other Java object is, i.e. when the garbage collector determines that there are no more references pointing to them in the code.
You don't need to close the config. It's not a BufferedWriter. The config keeps all of the data in the memory until the server shuts down. This means that if you change something in the config during the time your plugin is enabled, you will need to use your reloadConfig() method. The only clean up you need to do after using the FileConfiguration#set(String, Object) method is to use FileConfiguration#saveConfig() to tell Bukkit to take the current state of your config and copy it into your config file.
I'm trying to extend my library for integrating Swing and JPA by making JPA config as automatic (and portable) as can be done, and it means programmatically adding <class> elements. (I know it can be done via Hibernate's AnnotationConfiguration or EclipseLInk's ServerSession, but - portability). I'd also like to avoid using Spring just for this single purpose.
I can create a persistence.xml on the fly, and fill it with <class> elements from specified packages (via the Reflections library). The problem starts when I try to feed this persistence.xml to a JPA provider. The only way I can think of is setting up a URLClassLoader, but I can't think of a way what wouldn't make me write the file to the disk somewhere first, for sole ability to obtain a valid URL. Setting up a socket for serving the file via an URL(localhost:xxxx) seems... I don't know, evil?
Does anyone have an idea how I could solve this problem? I know it sounds like a lot of work to avoid using one library, but I'd just like to know if it can be done.
EDIT (a try at being more clear):
Dynamically generated XML is kept in a String object. I don't know how to make it available to a persistence provider. Also, I want to avoid writing the file to disk.
For purpose of my problem, a persistence provider is just a class which scans the classpath for META-INF/persistence.xml. Some implementations can be made to accept dynamic creation of XML, but there is no common interface (especially for a crucial part of the file, the <class> tags).
My idea is to set up a custom ClassLoader - if you have any other I'd be grateful, I'm not set on this one.
The only easily extendable/configurable one I could find was a URLClassLoader. It works on URL objects, and I don't know if I can create one without actually writing XML to disk first.
That's how I'm setting things up, but it's working by writing the persistenceXmlFile = new File("META-INF/persistence.xml") to disk:
Thread.currentThread().setContextClassLoader(
new URLResourceClassLoader(
new URL[] { persistenceXmlFile.toURI().toURL() },
Thread.currentThread().getContextClassLoader()
)
);
URLResourceClassLoader is URLCLassLoader's subclass, which allows for looking up resources as well as classes, by overriding public Enumeration<URL> findResources(String name).
Maybe a bit late (after 4 years), but for others that are looking for a similar solution, you may be able to use the URL factory I created:
public class InMemoryURLFactory {
public static void main(String... args) throws Exception {
URL url = InMemoryURLFactory.getInstance().build("/this/is/a/test.txt", "This is a test!");
byte[] data = IOUtils.toByteArray(url.openConnection().getInputStream());
// Prints out: This is a test!
System.out.println(new String(data));
}
private final Map<URL, byte[]> contents = new WeakHashMap<>();
private final URLStreamHandler handler = new InMemoryStreamHandler();
private static InMemoryURLFactory instance = null;
public static synchronized InMemoryURLFactory getInstance() {
if(instance == null)
instance = new InMemoryURLFactory();
return instance;
}
private InMemoryURLFactory() {
}
public URL build(String path, String data) {
try {
return build(path, data.getBytes("UTF-8"));
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException(ex);
}
}
public URL build(String path, byte[] data) {
try {
URL url = new URL("memory", "", -1, path, handler);
contents.put(url, data);
return url;
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
private class InMemoryStreamHandler extends URLStreamHandler {
#Override
protected URLConnection openConnection(URL u) throws IOException {
if(!u.getProtocol().equals("memory")) {
throw new IOException("Cannot handle protocol: " + u.getProtocol());
}
return new URLConnection(u) {
private byte[] data = null;
#Override
public void connect() throws IOException {
initDataIfNeeded();
checkDataAvailability();
// Protected field from superclass
connected = true;
}
#Override
public long getContentLengthLong() {
initDataIfNeeded();
if(data == null)
return 0;
return data.length;
}
#Override
public InputStream getInputStream() throws IOException {
initDataIfNeeded();
checkDataAvailability();
return new ByteArrayInputStream(data);
}
private void initDataIfNeeded() {
if(data == null)
data = contents.get(u);
}
private void checkDataAvailability() throws IOException {
if(data == null)
throw new IOException("In-memory data cannot be found for: " + u.getPath());
}
};
}
}
}
We can use the Jimfs google library for that.
First, we need to add the maven dependency to our project:
<dependency>
<groupId>com.google.jimfs</groupId>
<artifactId>jimfs</artifactId>
<version>1.2</version>
</dependency>
After that, we need to configure our filesystem behavior, and write our String content to the in-memory file, like this:
public static final String INPUT =
"\n"
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<note>\n"
+ " <to>Tove</to>\n"
+ " <from>Jani</from>\n"
+ " <heading>Reminder</heading>\n"
+ " <body>Don't forget me this weekend!</body>\n"
+ "</note>";
#Test
void usingJIMFS() throws IOException {
try (var fs = Jimfs.newFileSystem(Configuration.unix())) {
var path = fs.getPath(UUID.randomUUID().toString());
Files.writeString(path, INPUT);
var url = path.toUri().toURL();
assertThat(url.getProtocol()).isEqualTo("jimfs");
assertThat(Resources.asCharSource(url, UTF_8).read()).isEqualTo(INPUT);
}
}
We can find more examples in the official repository.
If we look inside the jimfs source code we will find the implementation is similar to #NSV answer.
I got another JCo-related question and hopefully finding help.
With JCo you can easily build up a connection like it is explained in the example sheets which came with the JCo-library. Unfortunately, the only way building a connection is handled with a created property file. It wouldn´t be that bad, if there wasn´t any sensible data in it. But at least, the password for the SAP user stands in the file, so it is a lack of safety in this way of connection-handling. The manual of JCo says so, too :
"For this example the destination configuration is stored in a file that is called by the program. In practice you should avoid this for security reasons."
but couldn´t find a working solution after all. There are a palmful threads about this theme, like this
http://forums.sdn.sap.com/thread.jspa?messageID=7303957
but none of them are helpful. I really can´t figure out a solution and neither find one. Actually I solved the security-problem with deleting the file after building the connection, but this is not a satisfying solution. There have to be a better way getting the parameter for the connection, especially when it stands in the manual, but I have no glue how.
Anybody already worked with JCo 3.0 and knows this problem?
Yes, that's possible. You have to create your own implementation of DestinationDataProvider and register it using Environment.registerDestinationDataProvider(). However your DDP obtains the connection data and credentials is up to you. Take a look at net.sf.rcer.conn.connections.ConnectionManager, there's a working example in there.
You need to
copy the private class starting on line 66 and adapt it to your own needs (that is, fetch the connection data from wherever you want to)
perform the registration (line 204) somewhere during the startup of your application
get the connection using some string identifier that will be passed to your DestinationDataProvider.
It's a bit confusing, it was dificult to me how to figure this too.
All you need is an object of type java.util.Properties to fill the desired fields, but it's up to ou how to fill this object.
I dit it through a ValueObject, I can fill this VO from a file, database, web form...
JCOProvider jcoProvider = null;
SAPVO sap = new SAPVO(); // Value Object
Properties properties = new Properties();
if(jcoProvider == null) {
// Get SAP config from DB
try {
sap = SAPDAO.getSAPConfig(); // DAO object that gets conn data from DB
} catch (Exception ex) {
throw new ConexionSAPException(ex.getMessage());
}
// Create new conn
jcoProvider = new JCOProvider();
}
properties.setProperty(DestinationDataProvider.JCO_ASHOST, sap.getJCO_ASHOST());
properties.setProperty(DestinationDataProvider.JCO_SYSNR, sap.getJCO_SYSNR());
properties.setProperty(DestinationDataProvider.JCO_CLIENT, sap.getJCO_CLIENT());
properties.setProperty(DestinationDataProvider.JCO_USER, sap.getJCO_USER());
properties.setProperty(DestinationDataProvider.JCO_PASSWD, sap.getJCO_PASSWD());
properties.setProperty(DestinationDataProvider.JCO_LANG, sap.getJCO_LANG());
// properties.setProperty(DestinationDataProvider.JCO_TRACE, "10");
try {
jcoProvider.changePropertiesForABAP_AS(properties);
} catch (Exception e) {
throw new ConexionSAPException(e.getMessage());
}
The JCOProvider class:
import com.sap.conn.jco.ext.DestinationDataEventListener;
import com.sap.conn.jco.ext.DestinationDataProvider;
import com.sap.conn.jco.ext.Environment;
import es.grupotec.ejb.util.ConexionSAPException;
import java.util.Properties;
public class JCOProvider implements DestinationDataProvider {
private String SAP_SERVER = "SAPSERVER";
private DestinationDataEventListener eventListener;
private Properties ABAP_AS_properties;
public JCOProvider() {
}
#Override
public Properties getDestinationProperties(String name) {
if (name.equals(SAP_SERVER) && ABAP_AS_properties != null) {
return ABAP_AS_properties;
} else {
return null;
}
// if(ABAP_AS_properties!=null) return ABAP_AS_properties;
// else throw new RuntimeException("Destination " + name + " is not available");
}
#Override
public boolean supportsEvents() {
return true;
}
#Override
public void setDestinationDataEventListener(DestinationDataEventListener eventListener) {
this.eventListener = eventListener;
}
public void changePropertiesForABAP_AS(Properties properties) throws ConexionSAPException {
try {
if (!Environment.isDestinationDataProviderRegistered()) {
if (ABAP_AS_properties == null) {
ABAP_AS_properties = properties;
}
Environment.registerDestinationDataProvider(this);
}
if (properties == null) {
if (eventListener != null) {
eventListener.deleted(SAP_SERVER);
}
ABAP_AS_properties = null;
} else {
ABAP_AS_properties = properties;
if (eventListener != null) {
eventListener.updated(SAP_SERVER);
}
}
} catch (Exception ex) {
throw new ConexionSAPException(ex.getMessage());
}
}
}
Regards