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}