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 org.dcm4che3.conf.core.api.*;
043import org.dcm4che3.conf.core.context.LoadingContext;
044import org.dcm4che3.conf.core.context.ProcessingContext;
045import org.dcm4che3.conf.core.context.SavingContext;
046import org.dcm4che3.conf.core.api.internal.*;
047import org.dcm4che3.conf.core.util.PathPattern;
048import org.slf4j.Logger;
049import org.slf4j.LoggerFactory;
050
051import java.util.HashMap;
052import java.util.Iterator;
053import java.util.Map;
054
055/**
056 * Default de/referencer.
057 */
058public class DefaultReferenceAdapter implements ConfigTypeAdapter {
059
060    private static Logger log = LoggerFactory.getLogger(DefaultReferenceAdapter.class);
061
062    // generic uuid-based reference
063    private static final PathPattern uuidReferencePath = new PathPattern(Configuration.REFERENCE_BY_UUID_PATTERN);
064
065    private final Map<String, String> metadata = new HashMap<String, String>();
066
067    public DefaultReferenceAdapter() {
068        metadata.put("type", "string");
069        metadata.put("class", "Reference");
070    }
071
072    @Override
073    public Object fromConfigNode(Object configNode, ConfigProperty property, LoadingContext ctx, Object parent) throws ConfigurationException {
074
075        // old deprecated style ref, for backwards-compatibility
076        if (configNode instanceof String) {
077
078            return resolveDeprecatedReference((String) configNode, property, ctx);
079        }
080        // new style
081        else {
082            String uuidRefStr = (String) ((Map) configNode).get(Configuration.REFERENCE_KEY);
083
084            String uuid = null;
085            try {
086                uuid = uuidReferencePath.parse(uuidRefStr).getParam("uuid");
087            } catch (RuntimeException e) {
088                // in case if it's not a map or is null or has no property
089                throw new IllegalArgumentException("Unexpected value for reference property " + property.getAnnotatedName() + ", value" + configNode);
090            }
091
092            return getReferencedConfigurableObject(uuid, ctx, property);
093        }
094    }
095
096    private Object resolveDeprecatedReference(String configNode, ConfigProperty property, LoadingContext ctx) {
097        String refStr = configNode;
098
099        log.warn("Using deprecated reference format for configuration: " + refStr);
100
101        Configuration config = ctx.getTypeSafeConfiguration().getLowLevelAccess();
102        Iterator search = config.search(refStr);
103
104        Map<String, Object> referencedNode = null;
105        if (search.hasNext())
106            referencedNode = (Map<String, Object>) search.next();
107
108        if (referencedNode == null) {
109            if (property.isWeakReference())
110                return null;
111            else
112                throw new ConfigurationException("Referenced node '" + refStr + "' not found");
113        }
114
115        // there is always uuid
116        String uuid;
117        try {
118            uuid = (String) referencedNode.get(Configuration.UUID_KEY);
119            if (uuid == null) throw new RuntimeException();
120        } catch (RuntimeException e) {
121            throw new IllegalArgumentException("A referable node MUST have a UUID. A node referenced by " + refStr + " does not have UUID property.");
122        }
123
124        return getReferencedConfigurableObject(uuid, ctx, property);
125    }
126
127
128    @SuppressWarnings("unchecked")
129    private Object getReferencedConfigurableObject(String uuid, LoadingContext ctx, ConfigProperty property) {
130        Object byUUID = ctx.getTypeSafeConfiguration().findByUUID(uuid, property.getRawClass(), ctx);
131
132        if (byUUID == null && !property.isWeakReference()) {
133
134            ConfigurationException e = new ConfigurationException("Referenced node with uuid '" + uuid + "' not found");
135
136            if (ctx.isIgnoreUnresolvedReferences()) {
137                log.error("Ignoring unresolved reference in configuration", e);
138            } else {
139                throw e;
140            }
141        }
142
143        return byUUID;
144    }
145
146    @Override
147    public Object toConfigNode(Object object, ConfigProperty property, SavingContext ctx) throws ConfigurationException {
148        Map<String, Object> node = Configuration.NodeFactory.emptyNode();
149
150        ConfigProperty uuidPropertyForClass = ConfigReflection.getUUIDPropertyForClass(property.getRawClass());
151        if (uuidPropertyForClass == null)
152            throw new ConfigurationException("Class " + property.getRawClass().getName() + " cannot be referenced, because it lacks a UUID property");
153
154        String uuid;
155        uuid = (String) ConfigReflection.getProperty(object, uuidPropertyForClass);
156        node.put(Configuration.REFERENCE_KEY, uuidReferencePath.set("uuid", uuid).path());
157
158        if (property.isWeakReference())
159            node.put(Configuration.WEAK_REFERENCE_KEY, true);
160
161        return node;
162    }
163
164    @Override
165    public Map<String, Object> getSchema(ConfigProperty property, ProcessingContext ctx) throws ConfigurationException {
166        Map<String, Object> schema = new HashMap<String, Object>();
167        schema.putAll(metadata);
168        schema.put("referencedClass", property.getRawClass().getSimpleName());
169        return schema;
170    }
171
172    @Override
173    public Object normalize(Object configNode, ConfigProperty property, ProcessingContext ctx) throws ConfigurationException {
174        return configNode;
175    }
176
177}