001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 * http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014package org.atteo.evo.jaxb;
015
016import java.net.URL;
017import java.util.HashMap;
018import java.util.Map;
019import java.util.concurrent.Callable;
020
021import javax.xml.bind.ValidationEvent;
022import javax.xml.bind.ValidationEventHandler;
023import javax.xml.bind.ValidationEventLocator;
024
025import org.atteo.evo.config.Configurable;
026import org.w3c.dom.Node;
027import org.xml.sax.SAXException;
028
029import com.sun.xml.bind.IDResolver;
030
031/**
032 * Resolve Id references in the scope of the expected class.
033 *
034 * <p>
035 * @see <a href="http://weblogs.java.net/blog/2005/08/15/pluggable-ididref-handling-jaxb-20">Pluggable IdRef</a>
036 * </p>
037 */
038public class ScopedIdResolver extends IDResolver {
039    private static class Key {
040        private Class<?> klass;
041        private String id;
042
043        public Key(Class<?> klass, String id) {
044            this.klass = klass;
045            this.id = id;
046        }
047
048        @Override
049        public boolean equals(Object obj) {
050            if (obj == null) {
051                return false;
052            }
053            if (getClass() != obj.getClass()) {
054                return false;
055            }
056            final Key other = (Key) obj;
057            if (klass != other.getClass()
058                    && (klass == null || !klass.equals(other.getKlass()))) {
059                return false;
060            }
061            if ((id == null) ? (other.getId() != null) : !id.equals(other.getId())) {
062                return false;
063            }
064            return true;
065        }
066
067        @Override
068        public int hashCode() {
069            int hash = 7;
070            hash = 29 * hash + (klass != null ? klass.hashCode() : 0);
071            hash = 29 * hash + (id != null ? id.hashCode() : 0);
072            return hash;
073        }
074
075        public String getId() {
076            return id;
077        }
078
079        public Class<?> getKlass() {
080            return klass;
081        }
082    }
083
084    private static class NotUniqueIdValidationEvent implements ValidationEvent {
085        private Key key;
086
087        public NotUniqueIdValidationEvent(Key key) {
088            this.key = key;
089        }
090
091        @Override
092        public int getSeverity() {
093            return ValidationEvent.FATAL_ERROR;
094        }
095
096        @Override
097        public String getMessage() {
098            return "Cannot resolve XmlIDREF because pair ["  + key.klass + ", id = \"" + key.id + "\"] is not unique";
099        }
100
101        @Override
102        public Throwable getLinkedException() {
103            return null;
104        }
105
106        @Override
107        public ValidationEventLocator getLocator() {
108            return new ValidationEventLocator() {
109                @Override
110                public URL getURL() {
111                    return null;
112                }
113
114                @Override
115                public int getOffset() {
116                    return 0;
117                }
118
119                @Override
120                public int getLineNumber() {
121                    return -1;
122                }
123
124                @Override
125                public int getColumnNumber() {
126                    return -1;
127                }
128
129                @Override
130                public Object getObject() {
131                    return null;
132                }
133
134                @Override
135                public Node getNode() {
136                    return null;
137                }
138            };
139        }
140    }
141
142    private enum Values { NON_UNIQUE };
143
144    private Map<Key, Object> map = new HashMap<Key, Object>();
145    private ValidationEventHandler eventHandler = null;
146
147    @Override
148    public void startDocument(ValidationEventHandler eventHandler) throws SAXException {
149        super.startDocument(eventHandler);
150        this.eventHandler = eventHandler;
151    }
152
153
154    @Override
155    public void endDocument() throws SAXException {
156        map.clear();
157        eventHandler = null;
158
159        super.endDocument();
160    }
161
162    @Override
163    public void bind(final String id, final Object object) throws SAXException {
164        Class<?> klass = object.getClass();
165        while (klass != Object.class) {
166            Key key = new Key(klass, id);
167            Object value = map.get(key);
168            if (value != null) {
169                if (value != Values.NON_UNIQUE) {
170                    map.put(key, Values.NON_UNIQUE);
171                }
172            } else {
173                map.put(key, object);
174            }
175            klass = klass.getSuperclass();
176        }
177    }
178
179    @Override
180    public Callable<?> resolve(final String id, @SuppressWarnings("rawtypes") final Class targetType)
181            throws SAXException {
182        return new Callable<Object>() {
183            @Override
184            public Object call() throws Exception {
185                Key key = new Key(targetType, id);
186                Object value =  map.get(key);
187                if (value == Values.NON_UNIQUE) {
188                    ValidationEvent event = new NotUniqueIdValidationEvent(key);
189                    if (!eventHandler.handleEvent(event)) {
190                        throw new SAXException(event.getMessage());
191                    }
192                }
193                return value;
194            }
195        };
196    }
197}