MBeanOperationInfo and MBeanAttributeInfo meta data? - java

I have written a JMX interface for one of our applications. Another application then connects and allows the user to see various state related attributes / invoke operations remotely via this management tool. I stumbled across a small bug where our database connection settings are being exposed over JMX, with the password unencrypted. I would like to tag the attributes / operations that should be obfuscated with some flag, but it doesnt appear as though the MBeanAttributeInfo or MBeanOperationInfo objects support adding any user defined values exception for name and description. I suppose I could delimit the description field like
String desc = getAttrDesc() + ";" + getIsObfuscated();
But I dont like this approach very much. The question then is, is there a better way to provide arbitrary key value pairs to an attribute / operation info object, or the Dynamic MBean itself? It doesnt have to be on the info objects themselves, just as long as I can match them up on the management tool side. Any insight would be appreciated.
Just for clarification, when I construct the MBeanOperationInfo (leaving out the attributes for the sake of example) I do so like this:
LinkedList<MBeanOperationInfo> opperInfos = new LinkedList<MBeanOperationInfo>();
for (Method m : m_InstObj.getMethods()) {
InstrumentedOperation anno = m.getAnnotation(InstrumentedOperation.class);
String desc = anno.description();
opperInfos.add(new MBeanOperationInfo(desc, m));
}
m_Operations = new MBeanOperationInfo[opperInfos.size()];
int I = 0;
for (MBeanOperationInfo info : opperInfos) {
m_Operations[I] = info;
I++;
}
I would like the InstrumentedOperation annotation to have a field for obfuscated that I can use like this:
anno.obfuscated(); // <- retreives a boolean set as a compile time constant on the annotation
and be able to include this value in the Info object.
Then on the receiving side I do this:
MBeanOperationInfo[] operInfos = conn.getMBeanInfo(name).getOperations();
for (MBeanOperationInfo info : operInfos) {
String propName = getPropNameFromInfo(info.getName());
if (!uniqueSettings.contains(propName)) {
// this setting hasn't been handled, get the getters and setters and make the method map
String getter = getGetterForSetting(operInfos, info.getName());
String setter = getSetterForSetting(operInfos, info.getName());
Object value = conn.invoke(name, getter, new Object[] {}, new String[] {});
if (getter != null && setter != null) {
SettingMethodMap map = new SettingMethodMap(name.getKeyProperty("type"), propName, info.getName(), setter, getter, value);
uniqueSettings.add(propName);
m_Settings.add(map);
}
}
}
Here I would like to be able to retreive the key value pair through some mechanism, so I would know that I need to handle this field different and obfuscate it in the editor.

This can be achieved using the javax.management.DescriptorKey.
For example, using a code sample that I adapted for this, using a standard mbean:
"Obfuscated" annotation:
import java.lang.annotation.*;
import javax.management.DescriptorKey;
#Documented
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Obfuscated {
#DescriptorKey("obfuscated")
boolean value() default true;
}
MBean interface:
public interface LoginMBean {
String getName();
#Obfuscated
String getPassword();
}
MBean implementation:
public class Login implements LoginMBean {
private final String user;
private final String password;
public Login(String user, String password) {
this.user = user;
this.password = password;
}
#Override public String getName() { return user; }
#Override public String getPassword() { return password; }
}
Some code to register the MBean and browse its information:
import java.lang.management.ManagementFactory;
import javax.management.*;
public class Main {
public static void main(String[] args) {
try {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
ObjectName mbeanName = new ObjectName("com.mydomain", "type", "login");
server.registerMBean(
new StandardMBean(new Login("John Doe", "password"), LoginMBean.class), mbeanName);
MBeanInfo mbeanInfo = server.getMBeanInfo(mbeanName);
MBeanAttributeInfo[] attrs = mbeanInfo.getAttributes();
for (MBeanAttributeInfo attr: attrs) {
Descriptor desc = attr.getDescriptor();
boolean obfuscated = false;
if (desc.getFieldValue("obfuscated") != null) {
obfuscated = (Boolean) desc.getFieldValue("obfuscated");
}
if (obfuscated) System.out.printf("field '%s' is obfuscated%n", attr.getName());
else {
Object value = server.getAttribute(mbeanName, attr.getName());
System.out.printf("value of field '%s' is '%s'%n",
attr.getName(), value == null ? "null" : value.toString());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Finally, the output after running Main:
value of field 'Name' is 'John Doe'
field 'Password' is obfuscated

Related

What is the Java pattern to return functions with different signatures based on some condition?

I'm trying to write a Client class which can look up entities from an external data store. The main parameter used to look up entities is a key. The problem is that sometimes a key is just a userId, sometimes it's a combination of userId and productId. So I'd like to enforce the consumer of my Client API to use a key builder specific to an entity. I tried the code below:
import java.util.function.BiFunction;
import java.util.function.Function;
public class Client {
public Function getBuilder(SomeEnum type) {
switch (type) {
case TYPE_X:
BiFunction<String, String, Entity> builder = (userId, productId) -> {
String key = userId + "-" + productId;
// lookup something in external data store based on the key and return the found entity
};
return builder;
case TYPE_Y:
Function<String, Entity> b = appId -> {
String key = userId;
// lookup something in external data store based on the key and return the found entity
};
return b;
}
}
}
The code doesn't compile of course because the return types don't match. I'm wondering if the code can be fixed and if not what is the correct Java pattern to enforce such builders with different signatures.
I would create a KeyBuilder interface as follows, and implement it differently for each type. No enums needed:
public interface KeyBuilder {
String buildKey();
}
// things that use userId & productId:
public class Foo implements KeyBuilder {
// ...
public String buildKey() { return userId + "-" + productId; }
}
// things that use appId {
public class Bar implements KeyBuilder {
// ...
public String buildKey() { return appId; }
}
Then, your code becomes cleaner and is still easy to test
// before: have to build explicitly
lookupSomethingExternal(foo.getUserId() + "-" + foo.getProductId());
lookupSomethingExternal(bar.getAppId());
// after: lookupSomethingInternal expects something implementing KeyBuilder
lookupSomethingExternal(foo);
lookupSomethingExternal(bar);
// after: can mock for unit-tests
lookupSomethingExternal(() -> "testKey");

Copy changed fields into an object in Java

I have a method like this:
public User getUpdatedUser(UserInfo userInfo, User user) throws ProvisioningException {
if (!userInfo.getUserExternalId().equals(user.getImmutableId()) || !userInfo.getAccountExternalId().equals(
getExternalAccountId(user.getAccountid())))
throw new ProvisioningException(Response.Status.BAD_REQUEST, ProvisioningErrorCodes.INVALID_INPUT);
if (user.getEmail() != userInfo.getEmail()) user.setEmail(userInfo.getEmail());
if (user.getFirstName() != userInfo.getFirstName()) user.setFirstName(userInfo.getFirstName());
if (user.getLastName() != userInfo.getLastName()) user.setLastName(userInfo.getLastName());
if (user.getPhoneNumber() != userInfo.getPhoneNumber()) user.setPhoneNumber(userInfo.getPhoneNumber());
if (user.getCompany() != userInfo.getCompany()) user.setCompany(userInfo.getEmail());
if (user.getJobTitle() != userInfo.getJobTitle()) user.setJobTitle(userInfo.getJobTitle());
if (user.getStatus() != ApiUtils.changeEnumClass(userInfo.getStatus(), DbConstants.UserStatus.class))
user.setStatus(ApiUtils.changeEnumClass(userInfo.getStatus(), DbConstants.UserStatus.class));
if (user.getAccountAdministratorInternalUse() != isAccountAdmin(userInfo.getRoles()))
user.setAccountAdministratorInternalUse(isAccountAdmin(userInfo.getRoles()));
if (user.getPodAdministratorInternalUse() != isPodAdmin(userInfo.getRoles()))
user.setPodAdministratorInternalUse(isPodAdmin(userInfo.getRoles()));
return user;
}
Basically, copying only those fields into user which are different. Is there a neater/cleaner way to do this in Java instead of all the if conditions?
Please, consider the use of JaVers.
The library will allow you mainly to compute diffs that you can apply latter in your objects.
You can take a different approach and use a mapper library, like Mapstruct.
After installing the required dependencies, define a Mapper interface, something like:
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
#Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
void update(UserInfo userInfo, #MappingTarget User user);
}
And use it to update the properties in the target object:
UserMapper.INSTANCE.update(userInfo, user);
The library will take care of map every property in the destination object. You can tweak the mapping process as appropriate.
A lot of more elaborated, but if your information is JSON you can apply a merge patch operation over the destination object with the received JSON information. Please, review this excellent article, it is focused on Spring, but it will provide you a great background about the proposed solution.
Finally, you can follow a simpler approach and use Apache Commons BeanUtils and copyProperties between your objects.
Please, be aware that with the exception of the first solution based on JaVers, the rest of the proposed approaches will copy all properties from the source to the destination object, not only the different ones. Unless for the possible performance overhead or any unindicated side effect, this fact will make no difference in the result obtained.
Alternative approach, use it with responsability.
Manual mode (reflection).
//user is the User instance you wish to modify
Class<User> rfkClass = User.class;
Field field = rfkClass.getDeclaredField("name"); //private final? Who cares
field.setAccessible(true);
field.set(user, "TheDestroyer");
field = rfkClass.getDeclaredField("email");
field.setAccessible(true);
field.set(user, "chuck#norris.com");
//... your changes here
// return user; --> same instance, now modified internally
Regardless the variables were declared as final, private, whatever, reflection just doesn't care.
You could define an API by which both objects abide, and then use reflection to create a generic method.
interface User {
void setFoo(String foo);
String getFoo();
}
class UserImpl implements User { ... }
class UserInfo implements User { ... }
public class Mirrorer<T> {
private final String[] methodNames;
private final MethodHandle[] methodHandles;
public Mirrorer(Class<T> interfaceClass) {
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException("interfaceClass must be an interface.");
}
Method[] methods = interfaceClass.getDeclaredMethods();
int methodsCount = methods.length;
methodNames = new String[methodsCount];
methodHandles = new MethodHandle[methodsCount];
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
for (int i = 0; i < methodsCount; i++) {
Method method = methods[i];
methodNames[i] = method.getName();
methodHandles[i] = lookup.unreflect(method);
}
} catch (IllegalAccessException e) {
throw new AssertionError();
}
}
public void mirror(T from, T to) throws Throwable {
for (int i = 0; i < methodNames.length; i++) {
String methodName = methodNames[i];
if (methodName.startsWith("get")) {
MethodHandle methodHandle = methodHandles[i];
Object fromValue = methodHandle.invoke(from);
Object toValue = methodHandle.invoke(to);
if (!Objects.equals(fromValue, toValue)) {
MethodHandle setter = getSetter(methodName.substring(3), methodHandle.type().returnType());
setter.invoke(to, fromValue);
}
}
}
}
private MethodHandle getSetter(String field, Class<?> type) {
for (int i = 0; i < methodNames.length; i++) {
String methodName = methodNames[i];
if (methodName.equals("set" + field)) {
MethodHandle methodHandle = methodHandles[i];
MethodType methodType = methodHandle.type();
if (methodType.parameterCount() != 0 && methodType.parameterType(0).equals(type)) {
return methodHandle;
}
}
}
throw new AssertionError();
}
}
Mirrorer<User> mirrorer = new Mirrorer<>(User.class);
UserInfo userInfo = ...
UserImpl user = ...
mirrorer.mirror(userInfo, user);
Looks like you just copy all different fields from userInfo to user and you are able not to compare all the valuse, just set it all together.
public User getUpdatedUser(UserInfo userInfo, User user) throws ProvisioningException {
if (!userInfo.getUserExternalId().equals(user.getImmutableId())
|| !userInfo.getAccountExternalId().equals(getExternalAccountId(user.getAccountid())))
throw new ProvisioningException(Response.Status.BAD_REQUEST, ProvisioningErrorCodes.INVALID_INPUT);
user.setEmail(userInfo.getEmail());
user.setFirstName(userInfo.getFirstName());
user.setLastName(userInfo.getLastName());
user.setPhoneNumber(userInfo.getPhoneNumber());
user.setCompany(userInfo.getEmail());
user.setJobTitle(userInfo.getJobTitle());
user.setStatus(userInfo.getStatus());
user.setAccountAdministratorInternalUse(isAccountAdmin(userInfo.getRoles()));
user.setPodAdministratorInternalUse(isPodAdmin(userInfo.getRoles()));
return user;
}

Passing values from database in "allowableValues"?

I am using Swagger version 2 with Java Spring. I have declared a property and it works fine and it generates a drop down list of value I assigned.
#ApiParam(value = "Pass any one Shuttle provider ID from the list", allowableValues = "1,2,3,4,10")
private Long hotelId;
Now, I need a way to populate this list which is passed in allowableValues from my database as it could be random list as well as huge data. How can I assign list of values dynamically from database in this allowableValues?
This question is bit old, I too faced the same problem so thought of adding here which may help some one.
//For ApiModelProperty
#ApiModelProperty(required = true, allowableValues = "dynamicEnum(AddressType)")
#JsonProperty("type")
private String type;
Created a component which implements ModelPropertyBuilderPlugin
#Component
#Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1)
public class ApiModelPropertyPropertyBuilderCustom implements ModelPropertyBuilderPlugin {
private final DescriptionResolver descriptions;
#Autowired
public ApiModelPropertyPropertyBuilderCustom(DescriptionResolver descriptions) {
this.descriptions = descriptions;
}
public void apply(ModelPropertyContext context) {
try {
AllowableListValues allowableListValues = (AllowableListValues) FieldUtils.readField(context.getBuilder(),
"allowableValues", true);
if(allowableListValues!=null) {
String allowableValuesString = allowableListValues.getValues().get(0);
if (allowableValuesString.contains("dynamicEnum")) {
String yourOwnStringOrDatabaseTable = allowableValuesString.substring(allowableValuesString.indexOf("(")+1, allowableValuesString.indexOf(")"));
//Logic to Generate dynamic values and create a list out of it and then create AllowableListValues object
context.getBuilder().allowableValues(allowableValues);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public boolean supports(DocumentationType delimiter) {
return SwaggerPluginSupport.pluginDoesApply(delimiter);
}
}
Similary for ApiParam we can create component which will implement ParameterBuilderPlugin
#Override
public void apply(ParameterContext context) {
#SuppressWarnings("Guava") final Optional<ApiParam> apiParam =
context.resolvedMethodParameter().findAnnotation(ApiParam.class);
if (apiParam.isPresent()) {
final String allowableValuesString = apiParam.get().allowableValues();
//Your logic here
context.parameterBuilder().allowableValues(allowableValues);
}
}
You need to create constructor in SwaggerConfiguration class.
#Autowire service and withdraw data you need from database
assign this to final variable
assign this final variable to allowableValues in annotation
enjoy not efficient api
private final String allowableValues;
public SwaggerConfiguration() {
List<YourEntitiy> list = someService.findAll();
//code to get every value you need and add create comma separated String
StringJoiner stringJoiner = new StringJoiner(",");
stringJoiner.add(list.get(0).getValue());
this.allowableValues = stringJoiner.toString();
}
#ApiParam(allowableValues = allowableValues)
But I think it's bad idea getting all ids from database just to create allowable values. Just validate in api method if that id exist and/or Create new api to get ids from database, use pagination from Spring Data project, like PageImpl<> javadocs

How to create custom Validation Messages based on an annotation property?

I'm using the Hibernate #NotNull validator, and I'm trying to create custom messages to tell the user which field has generated the error when it is null. Something like this:
notNull.custom = The field {0} can't be null.
(this will be in my ValidationMessages.properties file).
Where the {0} should be the field name passed to the validator this way:
#NotNull(field="field name")
There is any way I can do that?
To customize your annotation message you need to disable the existing violation message inside isValid() method and build a new violation message and add it.
constraintContext.disableDefaultConstraintViolation();
constraintContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();
In the given below example, I am creating an annotation for input date validation on the basis of "invalid date", "can't be greater than today date" and "date format is correct or not".
#CheckDateIsValid(displayPattern = "DD/MM/YYYY", programPattern = "dd/MM/yyyy", groups = Order2.class)
private String fromDate;
Annotation Interface -
public #interface CheckDateIsValid {
String message() default "default message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String displayPattern();
String programPattern();
}
Annotation implementation Class -
public class CheckDateIsValidValidator implements ConstraintValidator<CheckDateIsValid, String> {
#Value("${app.country.timeZone}")
private String timeZone;
private String displayPattern;
private String programPattern;
#Override
public void initialize(CheckDateIsValid constraintAnnotation) {
this.displayPattern = constraintAnnotation.displayPattern();
this.programPattern = constraintAnnotation.programPattern();
}
#Override
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
try {
// disable existing violation message
constraintContext.disableDefaultConstraintViolation();
if (object == null) {
return true;
}
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(programPattern);
LocalDateTime time = LocalDate.parse(object, formatter).atStartOfDay();
ZoneOffset zoneOffSet = ZoneOffset.of(timeZone);
OffsetDateTime todayDateTime = OffsetDateTime.now(zoneOffSet);
if (time == null) {
customMessageForValidation(constraintContext, "date is not valid");
return false;
} else if (todayDateTime.isBefore(time.atOffset(zoneOffSet))) {
customMessageForValidation(constraintContext, "can't be greater than today date");
return false;
}
return time != null;
} catch (Exception e) {
customMessageForValidation(constraintContext, "date format should be like " + displayPattern);
return false;
}
}
private void customMessageForValidation(ConstraintValidatorContext constraintContext, String message) {
// build new violation message and add it
constraintContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();
}
}
If your requirement can be satisfied by interpolating hibernate messages, so you can create/name your *property file like that:
ValidationMessages.properties
And inside that:
javax.validation.constraints.NotNull.message = CUSTOMIZED MESSAGE WHEN NOTNULL is violated!
Hibernate by default searches a ResourceBundle named ValidationMessages. Also a locale might be involved: ValidationMessages_en, ValidationMessages_de, <..>
Hibernate will provide your customized message through interpolatedMessage parameter, so all ConstraintViolationException relevant information (included your message ) will be showed. So you message will be a part of real exception. Some unwieldy information will be provided!
If you want to make your custom exception (without default ConstraintViolationException behavior) check this out:
Using GenericDao concept, consider the following
public void saveOrUpdate(IEntity<?> entity) {
try {
if(entity.getId == null) {
em.persist(entity);
} else {
em.merge(entity)l
}
} catch(ConstraintViolationException cve) {
throw new ConstraintViolationEx(constructViolationMessage(cve.getConstraintViolations()));
}
}
private String constructMessage(Set<ConstraintViolation<?>> pConstraintViolations) {
StringBuilder customMessages = new StringBuilder();
for(ConstraintViolation<?> violation : pConstraintViolations) {
String targetAnnotation = violation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName();
if(supportsCustomMessage(targetAnnotation)) {
applyMessage(violation, targetAnnotation, customMessages);
} else {
// do something with not customized constraints' messages e.g. append it to existing container
}
}
return customMessages.toString();
}
private void applyMessage(ConstraintViolation<?> pViolation, String pTargetAnnotation, StringBuilder pCustomMessages) {
String targetClass = pViolation.getRootBean().getClass().getName();
String targetField = pViolation.getPropertyPath().toString();
pCustomMessages.append(MessageFormat.format(getMessageByAnnotation(pTargetAnnotation), targetClass, targetField));
pCustomMessages.append(System.getProperty("line.separator"));
}
private String getBundleKey() {
return "ValidationMessages"; //FIXME: hardcoded - implement your true key
}
private String getMessageByAnnotation(String pTargetAnnotation) {
ResourceBundle messages = ResourceBundle.getBundle(getBundleKey());
switch(pTargetAnnotation) {
case "NotNull":
return messages.getString(pTargetAnnotation + ".message");
default:
return "";
}
}
private boolean supportsCustomMessage(String pTargetAnnotation) {
return customizedConstraintsTypes.contains(pTargetAnnotation);
}
Produced result:
test.model.exceptions.ConstraintViolationEx
test.model.Person : name cannot be null
test.model.Person : surname cannot be null
A hibernate ConstraintViolation provides relevant information about root class and restricted field. As you see, it applies for all hibernate supported constraints, so you need to check if current annotation can be customized by supportsCustomMessage(<..>)! If it can (it's up to you), you should get appropriate message by constraint annotation doing `getMessageByAnnotation(<..>)'.
All you need to do is implement not supported constraints logic. For example it can append it's cause message or interpolated with default message (and true exception goes to *log file)

Best-practice for documenting available/required Java properties file contents

Is there a well-established approach for documenting Java "properties" file contents, including:
specifying the data type/contents expected for a given key
specifying whether a key is required for the application to function
providing a description of the key's meaning
Currently, I maintain (by hand) a .properties file that is the default, and I write a prose description of the data type and description of each key in a comment before. This does not lead to a programmatically accessible properties file.
I guess what I'm looking for is a "getopt" equivalent for properties files...
[EDIT: Related]
Java Configuration Frameworks
You could use some of the features in the Apache Commons Configuration package. It at least provides type access to your properties.
There are only conventions in the traditional java properties file. Some I've seen include providing, like you said, an example properties file. Another is to provide the default configuration with all the properties, but commented out.
If you really want to require something, maybe you're not looking for a properties file. You could use an XML configuration file and specify a schema with datatypes and requirements. You can use jaxb to compile the schema into java and read it i that way. With validation you can make sure the required properties are there.
The best you could hope for is when you execute your application, it reads, parses, and validates the properties in the file. If you absolutely had to stay properties based and didn't want to go xml, but needed this parsing. You could have a secondary properties file that listed each property that could be included, its type, and whether it was required. You'd then have to write a properties file validator that would take in a file to validate as well as a validation schema-like properties file. Something like
#list of required properties
required=prop1,prop2,prop3
#all properties and their types
prop1.type=Integer
prop2.type=String
I haven't looked through all of the Apache Configuration package, but they often have useful utilities like this. I wouldn't be surprised if you could find something in there that would simplify this.
Another option to check out is the project called OWNER. There, you define the interface that serves as the configuration object in your application, using types and annotations. Then, OWNER does the finding and parsing of the correct Properties file. Thus, you could write a javadoc for your interface and use that as the documentation.
I have never seen a standard way of doing it. What I would probably do is:
wrap or extend the java.util.Properties class
override (of extending) or provide a method (if wrapping) the store method (or storeToXML, etc) that writes out a comment for each line.
have the method that stores the properties have some sort of input file where you describe the properties of each one.
It doesn't get you anything over what you are doing by hand, except that you can manage the information in a different way that might be easier to deal with - for example you could have a program that spit out the comments to read in. It would potentially give you the programmatic access that you need, but it is a roll-your-own sort of thing.
Or it might just be too much work for too little to gain (which is why there isn't something obvious out there).
If you can specify the sort of comments you want to see I could take a stab at writing something if I get bored :-) (it is the sort of thing I like to do for fun, sick I know :-).
Ok... I got bored... here is something that is at least a start :-)
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
public class PropertiesVerifier
{
private final Map<String, PropertyInfo> optionalInfo;
private final Map<String, PropertyInfo> requiredInfo;
{
optionalInfo = new HashMap<String, PropertyInfo>();
requiredInfo = new HashMap<String, PropertyInfo>();
}
public PropertiesVerifier(final PropertyInfo[] infos)
{
for(final PropertyInfo info : infos)
{
final Map<String, PropertyInfo> infoMap;
if(info.isRequired())
{
infoMap = requiredInfo;
}
else
{
infoMap = optionalInfo;
}
infoMap.put(info.getName(), info);
}
}
public void verifyProperties(final Properties properties)
{
for(final Entry<Object, Object> property : properties.entrySet())
{
final String key;
final String value;
key = (String)property.getKey();
value = (String)property.getValue();
if(!(isValid(key, value)))
{
throw new IllegalArgumentException(value + " is not valid for: " + key);
}
}
}
public boolean isRequired(final String key)
{
return (requiredInfo.get(key) != null);
}
public boolean isOptional(final String key)
{
return (optionalInfo.get(key) != null);
}
public boolean isKnown(final String key)
{
return (isRequired(key) || isOptional(key));
}
public Class getType(final String key)
{
final PropertyInfo info;
info = getPropertyInfoFor(key);
return (info.getType());
}
public boolean isValid(final String key,
final String value)
{
final PropertyInfo info;
info = getPropertyInfoFor(key);
return (info.verify(value));
}
private PropertyInfo getPropertyInfoFor(final String key)
{
PropertyInfo info;
info = requiredInfo.get(key);
if(info == null)
{
info = optionalInfo.get(key);
if(info == null)
{
// should be a better exception maybe... depends on how you
// want to deal with it
throw new IllegalArgumentException(key + "
is not a valid property name");
}
}
return (info);
}
protected final static class PropertyInfo
{
private final String name;
private final boolean required;
private final Class clazz;
private final Verifier verifier;
protected PropertyInfo(final String nm,
final boolean mandatory,
final Class c)
{
this(nm, mandatory, c, getDefaultVerifier(c));
}
protected PropertyInfo(final String nm,
final boolean mandatory,
final Class c,
final Verifier v)
{
// check for null
name = nm;
required = mandatory;
clazz = c;
verifier = v;
}
#Override
public int hashCode()
{
return (getName().hashCode());
}
#Override
public boolean equals(final Object o)
{
final boolean retVal;
if(o instanceof PropertyInfo)
{
final PropertyInfo other;
other = (PropertyInfo)o;
retVal = getName().equals(other.getName());
}
else
{
retVal = false;
}
return (retVal);
}
public boolean verify(final String value)
{
return (verifier.verify(value));
}
public String getName()
{
return (name);
}
public boolean isRequired()
{
return (required);
}
public Class getType()
{
return (clazz);
}
}
private static Verifier getDefaultVerifier(final Class clazz)
{
final Verifier verifier;
if(clazz.equals(Boolean.class))
{
// shoudl use a singleton to save space...
verifier = new BooleanVerifier();
}
else
{
throw new IllegalArgumentException("Unknown property type: " +
clazz.getCanonicalName());
}
return (verifier);
}
public static interface Verifier
{
boolean verify(final String value);
}
public static class BooleanVerifier
implements Verifier
{
public boolean verify(final String value)
{
final boolean retVal;
if(value.equalsIgnoreCase("true") ||
value.equalsIgnoreCase("false"))
{
retVal = true;
}
else
{
retVal = false;
}
return (retVal);
}
}
}
And a simple test for it:
import java.util.Properties;
public class Main
{
public static void main(String[] args)
{
final Properties properties;
final PropertiesVerifier verifier;
properties = new Properties();
properties.put("property.one", "true");
properties.put("property.two", "false");
// properties.put("property.three", "5");
verifier = new PropertiesVerifier(
new PropertiesVerifier.PropertyInfo[]
{
new PropertiesVerifier.PropertyInfo("property.one",
true,
Boolean.class),
new PropertiesVerifier.PropertyInfo("property.two",
false,
Boolean.class),
// new PropertiesVerifier.PropertyInfo("property.three",
// true,
// Boolean.class),
});
System.out.println(verifier.isKnown("property.one"));
System.out.println(verifier.isKnown("property.two"));
System.out.println(verifier.isKnown("property.three"));
System.out.println(verifier.isRequired("property.one"));
System.out.println(verifier.isRequired("property.two"));
System.out.println(verifier.isRequired("property.three"));
System.out.println(verifier.isOptional("property.one"));
System.out.println(verifier.isOptional("property.two"));
System.out.println(verifier.isOptional("property.three"));
System.out.println(verifier.getType("property.one"));
System.out.println(verifier.getType("property.two"));
// System.out.println(verifier.getType("property.tthree"));
System.out.println(verifier.isValid("property.one", "true"));
System.out.println(verifier.isValid("property.two", "false"));
// System.out.println(verifier.isValid("property.tthree", "5"));
verifier.verifyProperties(properties);
}
}
One easy way is to distribute your project with a sample properties file, e.g. my project has in svn a "build.properties.example",with properties commented as necessary. The locally correct properties don't go into svn.
Since you mention "getopt", though, I'm wondering if you're really thinking of cmd line arguments? If there's a "main" that needs specific properties, I usually put it the relevant instructions in a "useage" message that prints out if the arguments are incorrect or "-h".

Categories

Resources