/*
* Copyright 2016 Stormpath, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.stormpath.sdk.impl.resource;
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
import com.stormpath.sdk.impl.ds.Enlistment;
import com.stormpath.sdk.lang.Assert;
import com.stormpath.sdk.lang.Classes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Constructor;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Refactored methods from {@link AbstractResource} to make them common to subclasses.
*
* @since 1.2.0
*/
public abstract class AbstractPropertyRetriever {
private static final Logger log = LoggerFactory.getLogger(AbstractPropertyRetriever.class);
private static final DateFormat dateFormatter = new ISO8601DateFormat();
protected final Lock readLock;
protected final Lock writeLock;
protected AbstractPropertyRetriever() {
ReadWriteLock rwl = new ReentrantReadWriteLock();
this.readLock = rwl.readLock();
this.writeLock = rwl.writeLock();
}
public abstract Object getProperty(String name);
/**
* @since 0.8
*/
protected String getString(StringProperty property) {
return getStringProperty(property.getName());
}
protected String getStringProperty(String key) {
Object value = getProperty(key);
if (value == null) {
return null;
}
return String.valueOf(value);
}
/**
* @since 0.8
*/
protected int getInt(IntegerProperty property) {
return getIntProperty(property.getName());
}
protected int getIntProperty(String key) {
Object value = getProperty(key);
if (value != null) {
if (value instanceof String) {
return parseInt((String) value);
} else if (value instanceof Number) {
return ((Number) value).intValue();
}
}
return -1;
}
/**
* @since 0.9
*/
protected boolean getBoolean(BooleanProperty property) {
return getBooleanProperty(property.getName());
}
/**
* Returns an actual boolean value instead of a possible null Boolean value since desired usage
* is to have either a true or false.
*
* @since 0.9
*/
protected boolean getBooleanProperty(String key) {
return getNullableBooleanProperty(key) == Boolean.TRUE;
}
protected Boolean getNullableBoolean(BooleanProperty property) {
return getNullableBooleanProperty(property.getName());
}
protected Boolean getNullableBooleanProperty(String key) {
Object value = getProperty(key);
if (value != null) {
if (value instanceof Boolean) {
return (Boolean) value;
} else if (value instanceof String) {
return Boolean.valueOf((String) value);
}
}
return null;
}
private int parseInt(String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
if (log.isErrorEnabled()) {
String msg = "Unabled to parse string '{}' into an integer value. Defaulting to -1";
log.error(msg, e);
}
}
return -1;
}
/**
* @since 0.8
*/
protected Date getDateProperty(DateProperty key) {
Object value = getProperty(key.getName());
if (value == null) {
return null;
}
try {
return dateFormatter.parse(String.valueOf(value));
} catch (ParseException e) {
if (log.isErrorEnabled()) {
String msg = "Unabled to parse string '{}' into an date value. Defaulting to null.";
log.error(msg, e);
}
}
return null;
}
/**
* Returns the {@link List} property identified by {@code key}
*
* @since 1.0.RC8
*/
protected List getListProperty(String key) {
Object list = getProperty(key);
return (List) list;
}
/**
* Returns the {@link Set} property identified by {@code key}
*
* @since 1.0.RC8
*/
protected Set getSetProperty(String key) {
Object set = getProperty(key);
return (Set) set;
}
/**
* @since 1.0.RC4
*/
protected Map getMap(MapProperty mapProperty) {
return getMapProperty(mapProperty.getName());
}
/**
* @since 1.0.RC4
*/
protected Map getMapProperty(String key) {
Object value = getProperty(key);
if (value != null) {
if (value instanceof Map) {
return (Map) value;
}
String msg = "'" + key + "' property value type does not match the specified type. Specified type: Map. " +
"Existing type: " + value.getClass().getName();
msg += (isPrintableProperty(key) ? ". Value: " + value : ".");
throw new IllegalArgumentException(msg);
}
return null;
}
protected <E extends Enum<E>> E getEnumProperty(EnumProperty<E> enumProperty) {
return getEnumProperty(enumProperty.getName(), enumProperty.getType());
}
protected <E extends Enum<E>> E getEnumProperty(String key, Class<E> type) {
Assert.notNull(type, "type cannot be null.");
Object value = getProperty(key);
if (value != null) {
if (value instanceof String) {
return Enum.valueOf(type, value.toString());
}
if (type.isAssignableFrom(value.getClass())) {
//noinspection unchecked
return (E) value;
}
}
return null;
}
protected <T, P> T getParentAwareObjectProperty(ParentAwareObjectProperty<T, P> objectProperty) {
return getParentAwareObjectProperty(objectProperty.getName(), objectProperty.getType(), objectProperty.getParentType());
}
protected <T, P> T getParentAwareObjectProperty(String name, Class<T> type, Class<P> parentType) {
Object value = getProperty(name);
if (value == null) {
return null;
}
if (type.isAssignableFrom(value.getClass())) {
return (T) value;
}
if (value instanceof Map) {
writeLock.lock();
try {
Constructor<T> propertyConstructor = Classes.getConstructor(type, String.class, Map.class, parentType);
@SuppressWarnings("unchecked")
T instance = propertyConstructor.newInstance(name, new Enlistment((Map<String, Object>) value) , this);
getProperties().put(name, instance);
return instance;
} catch (Exception e) {
throw new IllegalArgumentException("Unable to create instace", e);
} finally {
writeLock.unlock();
}
}
String msg = "'" + name + "' property value type does not match the specified property type. " +
"Existing type: " + value.getClass().getName();
msg += (isPrintableProperty(name) ? ". Value: " + value : ".");
throw new IllegalArgumentException(msg);
}
/**
* Returns {@code true} if the internal property is safe to print in toString(), {@code false} otherwise.
*
* @param name The name of the property to check for safe printing
* @return {@code true} if the internal property is safe to print in toString(), {@code false} otherwise.
* @since 0.4.1
*/
protected boolean isPrintableProperty(String name) {
return true;
}
/**
* @since 0.8
*/
protected void setProperty(Property property, Object value) {
setProperty(property.getName(), value, true);
}
public void setProperty(String name, Object value) {
setProperty(name, value, true);
}
/**
* @since 0.6.0
*/
protected abstract Object setProperty(String name, Object value, final boolean dirty);
/**
* @since 1.2.0
*/
protected abstract Map<String, Object> getProperties();
}