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}