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.adapters; 041 042import com.google.common.util.concurrent.SettableFuture; 043import org.dcm4che3.conf.core.api.*; 044import org.dcm4che3.conf.core.api.internal.ConfigProperty; 045import org.dcm4che3.conf.core.api.internal.ConfigReflection; 046import org.dcm4che3.conf.core.api.internal.ConfigTypeAdapter; 047import org.dcm4che3.conf.core.context.LoadingContext; 048import org.dcm4che3.conf.core.context.ProcessingContext; 049import org.dcm4che3.conf.core.context.Referable; 050import org.dcm4che3.conf.core.context.SavingContext; 051import org.dcm4che3.conf.core.util.PathFollower; 052import org.slf4j.Logger; 053import org.slf4j.LoggerFactory; 054 055import java.lang.reflect.Field; 056import java.util.*; 057import java.util.concurrent.Future; 058 059/** 060 * Reflective adapter that handles classes with ConfigurableClass annotations.<br/> 061 * <br/> 062 * <p/> 063 * User has to use the special constructor and initialize providedConfObj when the 064 * already created conf object should be used instead of instantiating one 065 */ 066@SuppressWarnings("unchecked") 067public class ReflectiveAdapter<T> implements ConfigTypeAdapter<T, Map<String, Object>> { 068 069 070 private static Logger log = LoggerFactory.getLogger(ReflectiveAdapter.class); 071 072 private T providedConfObj; 073 074 /** 075 * stateless 076 */ 077 public ReflectiveAdapter() { 078 } 079 080 /** 081 * stateful 082 */ 083 public ReflectiveAdapter(T providedConfigurationObjectInstance) { 084 this.providedConfObj = providedConfigurationObjectInstance; 085 } 086 087 @Override 088 public T fromConfigNode(Map<String, Object> configNode, ConfigProperty property, LoadingContext ctx, Object parent) throws ConfigurationException { 089 090 if (configNode == null) return null; 091 092 Class<T> clazz = (Class<T>) property.getType(); 093 094 if (!Map.class.isAssignableFrom(configNode.getClass())) 095 throw new ConfigurationException("Provided configuration node is not a map (type " + clazz.getName() + ")"); 096 097 098 // figure out UUID 099 String uuid; 100 try { 101 uuid = (String) configNode.get(Configuration.UUID_KEY); 102 } catch (RuntimeException e) { 103 throw new ConfigurationException("UUID is malformed: " + configNode.get(Configuration.UUID_KEY)); 104 } 105 106 107 // if the object is provided - just populate and return 108 if (providedConfObj != null) { 109 populate(configNode, ctx, clazz, providedConfObj, parent, uuid); 110 return providedConfObj; 111 } 112 113 114 // if uuid not present - simply create new instance 115 if (uuid == null) { 116 T confObj = ctx.getVitalizer().newInstance(clazz); 117 populate(configNode, ctx, clazz, confObj, parent, uuid); 118 return confObj; 119 } 120 121 //// uuid present - need to coordinate with the context 122 123 // first check the context 124 Referable existingReferable = ctx.getReferable(uuid); 125 if (existingReferable != null) { 126 // TODO: proper cast! 127 return (T) existingReferable.getConfObject(); 128 } 129 130 SettableFuture<Object> confObjFuture = SettableFuture.create(); 131 T confObj = ctx.getVitalizer().newInstance(clazz); 132 Referable createdReferable = new Referable(confObjFuture, confObj); 133 134 // cover non-atomicity above 135 Referable suddenlyExistingReferable = ctx.registerReferableIfAbsent(uuid, createdReferable); 136 if (suddenlyExistingReferable != null) { 137 // TODO: proper cast! 138 return (T) suddenlyExistingReferable.getConfObject(); 139 } 140 141 // now it's for sure me who is responsible for loading this object 142 try { 143 populate(configNode, ctx, clazz, confObj, parent, uuid); 144 confObjFuture.set(confObj); 145 return confObj; 146 } catch (RuntimeException e) { 147 confObjFuture.setException(e); 148 throw e; 149 } catch (Error e) { 150 confObjFuture.setException(e); 151 throw e; 152 } 153 } 154 155 private void populate(Map<String, Object> configNode, LoadingContext ctx, Class<T> clazz, T confObj, Object parent, String uuid) { 156 157 // this differentiation is needed for historical reasons .. two examples is Device and the addApplicationEntity method, another example is HL7DeviceExtension.... 158 159 if (ConfigReflection.getUUIDPropertyForClass(clazz) != null) { 160 // if class has uuid => it's 'standalone' conf class => initialize fields before parent 161 162 populateFields(configNode, ctx, clazz, confObj); 163 injectParent(ctx, clazz, confObj, parent, uuid); 164 } else { 165 // if class has no uuid => it's either an extension or a simple conf class => initialize parent before fields 166 167 injectParent(ctx, clazz, confObj, parent, uuid); 168 populateFields(configNode, ctx, clazz, confObj); 169 } 170 171 } 172 173 private void populateFields(Map<String, Object> configNode, LoadingContext ctx, Class<T> clazz, T confObj) { 174 for (ConfigProperty fieldProperty : ConfigReflection.getAllConfigurableFields(clazz)) 175 try { 176 Object fieldValue = DefaultConfigTypeAdapters.delegateGetChildFromConfigNode(configNode, fieldProperty, ctx, confObj); 177 ConfigReflection.setProperty(confObj, fieldProperty, fieldValue); 178 } catch (RuntimeException e) { 179 throw new ConfigurationException("Error while reading configuration property '" + fieldProperty.getAnnotatedName() + "' (field " + fieldProperty.getName() + ") in class " + clazz.getSimpleName(), e); 180 } 181 } 182 183 private void injectParent(LoadingContext ctx, Class<T> clazz, T confObj, Object parent, String uuid) { 184 185 Field parentField = ConfigReflection.getParentPropertyForClass(clazz); 186 if (parentField == null) return; 187 188 // if parent was provided - use it 189 if (parent != null) { 190 try { 191 ConfigReflection.setProperty(confObj, parentField.getName(), parent); 192 return; 193 } catch (RuntimeException e) { 194 throw new ConfigurationException("Could not 'inject' parent object into the @Parent field (class " + clazz.getName() + ")", e); 195 } 196 } 197 198 // if no provided parent and no uuid - we cannot really find the parent, so just leave it null 199 if (uuid == null) return; 200 201 202 // TODO: replace with proxy 203 204 // if no config context - leave the parent unset 205 TypeSafeConfiguration typeSafeConfig = ctx.getTypeSafeConfiguration(); 206 if (typeSafeConfig == null) return; 207 208 // Get path of this object in the storage 209 Path pathByUUID = typeSafeConfig.getLowLevelAccess().getPathByUUID(uuid); 210 if (pathByUUID == null) return; 211 Deque<ConfigProperty> configProperties = PathFollower.traceProperties(typeSafeConfig.getRootClass(), pathByUUID); 212 213 // parent is either the first or the second in the path (otherwise cannot really get the parent) 214 215 if (configProperties.size() < 1) return; 216 configProperties.removeLast(); 217 int nodesAbove = 1; 218 219 // this can be still a map/collection, try one level above 220 if (!configProperties.peekLast().isConfObject()) { 221 configProperties.removeLast(); 222 nodesAbove++; 223 if (configProperties.size() == 0) return; 224 if (!configProperties.peekLast().isConfObject()) return; 225 } 226 227 // now we are looking at the parent 228 ConfigProperty parentProp = configProperties.peekLast(); 229 230 if (!parentField.getType().isAssignableFrom(parentProp.getRawClass())) { 231 log.warn("Parent type mismatch: config structure denotes " + parentProp.getRawClass() + ", but the class has a field of type " + parentField.getType() 232 + " config object uuid=" + uuid + ", config class " + clazz); 233 return; 234 } 235 236 Path parentPath = pathByUUID.subPath(0, pathByUUID.getPathItems().size() - nodesAbove); 237 238 // load parent 239 Object loadedParent = typeSafeConfig.load(parentPath, parentProp.getRawClass(), ctx); 240 ConfigReflection.setProperty(confObj, parentField.getName(), loadedParent); 241 } 242 243 244 @Override 245 public Map<String, Object> toConfigNode(T object, ConfigProperty property, SavingContext ctx) throws ConfigurationException { 246 247 if (object == null) return null; 248 249 Class<T> clazz = (Class<T>) object.getClass(); 250 251 Map<String, Object> configNode = new TreeMap<String, Object>(); 252 253 // get data from all the configurable fields 254 for (ConfigProperty fieldProperty : ConfigReflection.getAllConfigurableFields(clazz)) { 255 try { 256 Object value = ConfigReflection.getProperty(object, fieldProperty); 257 DefaultConfigTypeAdapters.delegateChildToConfigNode(value, configNode, fieldProperty, ctx); 258 } catch (Exception e) { 259 throw new ConfigurationException("Error while serializing configuration field '" + fieldProperty.getName() + "' in class " + clazz.getSimpleName(), e); 260 } 261 } 262 263 return configNode; 264 } 265 266 267 @Override 268 public Map<String, Object> getSchema(ConfigProperty property, ProcessingContext ctx) throws ConfigurationException { 269 270 Class<T> clazz = (Class<T>) property.getType(); 271 272 Map<String, Object> classMetaDataWrapper = new HashMap<String, Object>(); 273 Map<String, Object> classMetaData = new HashMap<String, Object>(); 274 classMetaDataWrapper.put("properties", classMetaData); 275 classMetaDataWrapper.put("type", "object"); 276 classMetaDataWrapper.put("class", clazz.getSimpleName()); 277 278 // find out if we need to include uiOrder metadata 279 boolean includeOrder = false; 280 281 282 for (ConfigProperty configurableChildProperty : ConfigReflection.getAllConfigurableFields(clazz)) 283 if (configurableChildProperty.getAnnotation(ConfigurableProperty.class).order() != 0) includeOrder = true; 284 285 // populate properties 286 287 for (ConfigProperty prop : ConfigReflection.getAllConfigurableFields(clazz)) { 288 289 ConfigTypeAdapter childAdapter = ctx.getVitalizer().lookupTypeAdapter(prop); 290 Map<String, Object> childPropertyMetadata = new LinkedHashMap<String, Object>(); 291 classMetaData.put(prop.getAnnotatedName(), childPropertyMetadata); 292 293 if (!"".equals( prop.getLabel() ) ) 294 childPropertyMetadata.put("title", prop.getLabel()); 295 296 if (!"".equals( prop.getDescription() ) ) 297 childPropertyMetadata.put("description", prop.getDescription()); 298 try { 299 if (!prop.getDefaultValue().equals(ConfigurableProperty.NO_DEFAULT_VALUE)) 300 childPropertyMetadata.put("default", childAdapter.normalize(prop.getDefaultValue(), prop, ctx)); 301 } catch (ClassCastException e) { 302 childPropertyMetadata.put("default", 0); 303 } 304 if (!prop.getTags().isEmpty()) 305 childPropertyMetadata.put("tags", prop.getTags()); 306 307 if (includeOrder) 308 childPropertyMetadata.put("uiOrder", prop.getOrder()); 309 310 childPropertyMetadata.put("uiGroup", prop.getGroup()); 311 312 // also merge in the metadata from this child itself 313 Map<String, Object> childMetaData = childAdapter.getSchema(prop, ctx); 314 if (childMetaData != null) childPropertyMetadata.putAll(childMetaData); 315 } 316 317 return classMetaDataWrapper; 318 } 319 320 @Override 321 public Map<String, Object> normalize(Object configNode, ConfigProperty property, ProcessingContext ctx) throws ConfigurationException { 322 return (Map<String, Object>) configNode; 323 } 324}