package netflix.karyon.jersey.blocking;
import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.api.core.ScanningResourceConfig;
import com.sun.jersey.core.spi.scanning.PackageNamesScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.core.MediaType;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import static com.netflix.config.ConfigurationManager.getConfigInstance;
/**
* An implementation of {@link ResourceConfig} that enables users to define all jersey properties in a property file
* loaded by karyon via archaius. <br>
* This supports scanning of classpath (using {@link ScanningResourceConfig}) to discover provider and other resource
* classes. The scanning of classpath is done lazily, at the first call to {@link #getClasses()} in order to make sure
* that we do not do scanning too early, even before all properties are loaded.
*
* @author Nitesh Kant
*/
public class PropertiesBasedResourceConfig extends ScanningResourceConfig {
private static final Logger logger = LoggerFactory.getLogger(PropertiesBasedResourceConfig.class);
private static final String JERSEY_ROOT_PACKAGE = "com.sun.jersey";
private volatile boolean initialized;
@Override
public Set<Class<?>> getClasses() {
initIfRequired();
return super.getClasses();
}
@Override
public Set<Object> getSingletons() {
initIfRequired();
return super.getSingletons();
}
@Override
public Map<String, MediaType> getMediaTypeMappings() {
initIfRequired();
return super.getMediaTypeMappings();
}
@Override
public Map<String, String> getLanguageMappings() {
initIfRequired();
return super.getLanguageMappings();
}
@Override
public Map<String, Object> getExplicitRootResources() {
initIfRequired();
return super.getExplicitRootResources();
}
@Override
public Map<String, Boolean> getFeatures() {
initIfRequired();
return super.getFeatures();
}
@Override
public boolean getFeature(String featureName) {
initIfRequired();
return super.getFeature(featureName);
}
@Override
public Map<String, Object> getProperties() {
initIfRequired();
return super.getProperties();
}
@Override
public Object getProperty(String propertyName) {
initIfRequired();
return super.getProperty(propertyName);
}
private synchronized void initIfRequired() {
if (initialized) {
return;
}
initialized = true;
String pkgNamesStr = getConfigInstance().getString(PackagesResourceConfig.PROPERTY_PACKAGES, null);
if (null == pkgNamesStr) {
logger.warn("No property defined with name: " + PackagesResourceConfig.PROPERTY_PACKAGES +
", this means that jersey can not find any of your resource/provider classes.");
} else {
String[] pkgNames = getElements(new String[]{pkgNamesStr}, ResourceConfig.COMMON_DELIMITERS);
logger.info("Packages to scan by jersey {}", Arrays.toString(pkgNames));
init(new PackageNamesScanner(pkgNames));
}
Map<String, Object> jerseyProperties = createPropertiesMap();
setPropertiesAndFeatures(jerseyProperties);
}
private static Map<String, Object> createPropertiesMap() {
Properties properties = new Properties();
Iterator<String> iter = getConfigInstance().getKeys(JERSEY_ROOT_PACKAGE);
while (iter.hasNext()) {
String key = iter.next();
properties.setProperty(key, getConfigInstance().getString(key));
}
return new TypeSafePropertiesDelegate(properties);
}
private static class TypeSafePropertiesDelegate implements Map<String, Object> {
private final Properties properties;
// This intends to not make a copy of the properties but just refer to the property name & delegate to the
// properties instance for values.
private final Set<Entry<String, Object>> entrySet;
public TypeSafePropertiesDelegate(Properties properties) {
this.properties = properties;
entrySet = new HashSet<Entry<String, Object>>(properties.size());
for (final String propName : properties.stringPropertyNames()) {
entrySet.add(new Entry<String, Object>() {
@Override
public String getKey() {
return propName;
}
@Override
public Object getValue() {
return TypeSafePropertiesDelegate.this.properties.getProperty(propName);
}
@Override
public Object setValue(Object value) {
throw new UnsupportedOperationException("Writes are not supported on jersey features and properties map.");
}
});
}
}
@Override
public int size() {
return properties.size();
}
@Override
public boolean isEmpty() {
return properties.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return properties.contains(key);
}
@Override
public boolean containsValue(Object value) {
return properties.containsValue(value);
}
@Override
public Object get(Object key) {
return properties.getProperty(String.valueOf(key));
}
@Override
public Object put(String key, Object value) {
throw new UnsupportedOperationException("Writes are not supported on jersey features and properties map.");
}
@Override
public Object remove(Object key) {
throw new UnsupportedOperationException("Writes are not supported on jersey features and properties map.");
}
@Override
public void putAll(Map<? extends String, ?> m) {
throw new UnsupportedOperationException("Writes are not supported on jersey features and properties map.");
}
@Override
public void clear() {
throw new UnsupportedOperationException("Writes are not supported on jersey features and properties map.");
}
@Override
public Set<String> keySet() {
return properties.stringPropertyNames();
}
@Override
public Collection<Object> values() {
return properties.values();
}
@Override
public Set<Entry<String, Object>> entrySet() {
return entrySet;
}
}
}