001/* 002 * **** BEGIN LICENSE BLOCK ***** 003 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 004 * 005 * The contents of this file are subject to the Mozilla Public License Version 006 * 1.1 (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * http://www.mozilla.org/MPL/ 009 * 010 * Software distributed under the License is distributed on an "AS IS" basis, 011 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 012 * for the specific language governing rights and limitations under the 013 * License. 014 * 015 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 016 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 017 * 018 * The Initial Developer of the Original Code is 019 * Agfa Healthcare. 020 * Portions created by the Initial Developer are Copyright (C) 2014 021 * the Initial Developer. All Rights Reserved. 022 * 023 * Contributor(s): 024 * See @authors listed below 025 * 026 * Alternatively, the contents of this file may be used under the terms of 027 * either the GNU General Public License Version 2 or later (the "GPL"), or 028 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 029 * in which case the provisions of the GPL or the LGPL are applicable instead 030 * of those above. If you wish to allow use of your version of this file only 031 * under the terms of either the GPL or the LGPL, and not to allow others to 032 * use your version of this file under the terms of the MPL, indicate your 033 * decision by deleting the provisions above and replace them with the notice 034 * and other provisions required by the GPL or the LGPL. If you do not delete 035 * the provisions above, a recipient may use your version of this file under 036 * the terms of any one of the MPL, the GPL or the LGPL. 037 * 038 * ***** END LICENSE BLOCK ***** 039 */ 040package org.dcm4che3.conf.core; 041 042/** 043 * @author Roman K 044 */ 045 046import org.dcm4che3.conf.core.adapters.*; 047import org.dcm4che3.conf.core.api.ConfigurationException; 048import org.dcm4che3.conf.core.api.internal.ConfigProperty; 049import org.dcm4che3.conf.core.api.internal.BeanVitalizer; 050import org.dcm4che3.conf.core.api.internal.ConfigReflection; 051import org.dcm4che3.conf.core.api.internal.ConfigTypeAdapter; 052import org.dcm4che3.conf.core.context.ContextFactory; 053import org.dcm4che3.conf.core.context.LoadingContext; 054 055import java.util.*; 056import java.util.concurrent.ExecutionException; 057import java.util.concurrent.Future; 058import java.util.concurrent.TimeUnit; 059import java.util.concurrent.TimeoutException; 060 061/** 062 * Handles the conversion between annotated Java objects and config nodes from a configuration backend. 063 */ 064public class DefaultBeanVitalizer implements BeanVitalizer { 065 066 private final Map<Class, ConfigTypeAdapter> customConfigTypeAdapters = new HashMap<Class, ConfigTypeAdapter>(); 067 private int loadingTimeoutSec = 5; 068 069 private final Map<Class, List<Class>> extensionsByClass; 070 071 private final ConfigTypeAdapter referenceTypeAdapter = DefaultConfigTypeAdapters.getReferenceAdapter(); 072 073 private final ContextFactory contextFactory; 074 075 /** 076 * "Standalone" vitalizer. This should only be used for e.g. tests. 077 * To be able to handle references, custom context factories, etc, vitalizer must be bound to typeSafeConfiguration 078 */ 079 public DefaultBeanVitalizer() { 080 contextFactory = new ContextFactory(this); 081 extensionsByClass = new HashMap<Class, List<Class>>(); 082 } 083 084 public DefaultBeanVitalizer(Map<Class, List<Class>> extensionsByClass, ContextFactory contextFactory) { 085 this.extensionsByClass = extensionsByClass; 086 this.contextFactory = contextFactory; 087 } 088 089 /** 090 * Sets the timeout for resolving futures (objs being loaded by other threads) while loading the config. 091 * <p/> 092 * This is rather a defence-against-ourselves measure, i.e. normally the config futures should always get resolved/fail with an exception at some point. 093 * 094 * @param loadingTimeoutSec timeout. If <b>0</b> is passed, timeout is disabled. 095 */ 096 public void setLoadingTimeoutSec(int loadingTimeoutSec) { 097 this.loadingTimeoutSec = loadingTimeoutSec; 098 } 099 100 101 @Override 102 public ConfigTypeAdapter getReferenceTypeAdapter() { 103 return referenceTypeAdapter; 104 } 105 106 107 @Override 108 public Object resolveFutureOrFail(String uuid, Future<Object> f) { 109 try { 110 111 if (loadingTimeoutSec == 0) { 112 return f.get(); 113 } else { 114 return f.get(loadingTimeoutSec, TimeUnit.SECONDS); 115 } 116 117 } catch (InterruptedException e) { 118 Thread.currentThread().interrupt(); 119 throw new ConfigurationException("Loading of configuration unexpectedly interrupted", e); 120 121 } catch (ExecutionException e) { 122 if (e.getCause() instanceof ConfigurationException) { 123 throw (ConfigurationException) e.getCause(); 124 } else { 125 throw new ConfigurationException("Error while loading configuration", e.getCause()); 126 } 127 128 } catch (TimeoutException e) { 129 throw new ConfigurationException("Time-out while waiting for the object [uuid=" + uuid + "] to be loaded", e); 130 } 131 } 132 133 134 @Override 135 public <T> T newConfiguredInstance(Map<String, Object> configNode, Class<T> clazz) throws ConfigurationException { 136 return newConfiguredInstance(configNode, clazz, contextFactory.newLoadingContext()); 137 } 138 139 @SuppressWarnings("unchecked") 140 @Override 141 public <T> T newConfiguredInstance(Map<String, Object> configurationNode, Class<T> clazz, LoadingContext ctx) { 142 ConfigProperty propertyForClass = ConfigReflection.getDummyPropertyForClass(clazz); 143 return (T) lookupTypeAdapter(propertyForClass) 144 .fromConfigNode(configurationNode, propertyForClass, ctx, null); 145 } 146 147 /** 148 * Creates a new instance. 149 * 150 * @param clazz 151 * @param <T> 152 * @return 153 * @throws ConfigurationException 154 */ 155 @Override 156 public <T> T newInstance(Class<T> clazz) throws ConfigurationException { 157 try { 158 159 return clazz.newInstance(); 160 161 } catch (InstantiationException e) { 162 throw new ConfigurationException(e); 163 } catch (IllegalAccessException e) { 164 throw new ConfigurationException(e); 165 } 166 } 167 168 169 @Override 170 public Map<String, Object> createConfigNodeFromInstance(Object object) throws ConfigurationException { 171 if (object == null) return null; 172 return createConfigNodeFromInstance(object, object.getClass()); 173 } 174 175 176 @SuppressWarnings("unchecked") 177 @Override 178 public Map<String, Object> createConfigNodeFromInstance(Object object, Class clazz) throws ConfigurationException { 179 ConfigProperty propertyForClass = ConfigReflection.getDummyPropertyForClass(clazz); 180 return (Map<String, Object>) lookupTypeAdapter(propertyForClass) 181 .toConfigNode(object, propertyForClass, contextFactory.newSavingContext()); 182 } 183 184 @Override 185 public List<Class> getExtensionClassesByBaseClass(Class extensionBaseClass) { 186 187 List<Class> classes = extensionsByClass.get(extensionBaseClass); 188 189 if (classes == null) 190 return Collections.emptyList(); 191 192 return classes; 193 } 194 195 196 @Override 197 @SuppressWarnings("unchecked") 198 public ConfigTypeAdapter lookupTypeAdapter(ConfigProperty property) throws ConfigurationException { 199 200 Class clazz = property.getRawClass(); 201 202 // check if it is a reference 203 if (property.isReference()) 204 return getReferenceTypeAdapter(); 205 206 // check for a custom adapter 207 ConfigTypeAdapter typeAdapter = customConfigTypeAdapters.get(clazz); 208 if (typeAdapter != null) return typeAdapter; 209 210 // check if it is an extensions map 211 if (property.isExtensionsProperty()) 212 return DefaultConfigTypeAdapters.getExtensionTypeAdapter(); 213 214 // delegate to default otherwise 215 return lookupDefaultTypeAdapter(clazz); 216 } 217 218 @Override 219 @SuppressWarnings("unchecked") 220 public ConfigTypeAdapter lookupDefaultTypeAdapter(Class clazz) throws ConfigurationException { 221 222 ConfigTypeAdapter adapter; 223 224 // if it is a config class, use reflective adapter 225 if (ConfigReflection.isConfigurableClass(clazz)) 226 adapter = DefaultConfigTypeAdapters.getReflectiveAdapter(); 227 else if (clazz.isArray()) 228 adapter = DefaultConfigTypeAdapters.getArrayTypeAdapter(); 229 else if (clazz.isEnum()) 230 adapter = DefaultConfigTypeAdapters.get(Enum.class); 231 else 232 adapter = DefaultConfigTypeAdapters.get(clazz); 233 234 if (adapter == null) 235 throw new ConfigurationException("TypeAdapter not found for class " + clazz.getName()); 236 237 return adapter; 238 } 239 240 /** 241 * Registers a custom type adapter for configurable properties for the specified class 242 * 243 * @param clazz 244 * @param typeAdapter 245 */ 246 @Override 247 public void registerCustomConfigTypeAdapter(Class clazz, ConfigTypeAdapter typeAdapter) { 248 customConfigTypeAdapters.put(clazz, typeAdapter); 249 } 250 251 @SuppressWarnings("unchecked") 252 @Override 253 public Map<String, Object> getSchemaForConfigurableClass(Class<?> clazz) { 254 return lookupDefaultTypeAdapter(clazz).getSchema(new ConfigProperty(clazz), contextFactory.newProcessingContext()); 255 } 256}