001 /*
002 Copyright (c) 2012, Regents of the University of Colorado
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without modification,
006 are permitted provided that the following conditions are met:
007
008 * Redistributions of source code must retain the above copyright notice, this
009 list of conditions and the following disclaimer.
010
011 * Redistributions in binary form must reproduce the above copyright notice,
012 this list of conditions and the following disclaimer in the documentation
013 and/or other materials provided with the distribution.
014
015 * Neither the name of the University of Colorado nor the names of its
016 contributors may be used to endorse or promote products derived from this
017 software without specific prior written permission.
018
019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
023 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030 package edu.ucdenver.ccp.medline.parser;
031
032 import java.lang.reflect.Field;
033 import java.util.HashSet;
034 import java.util.Set;
035
036 import org.apache.log4j.Logger;
037
038 import com.thoughtworks.xstream.XStream;
039 import com.thoughtworks.xstream.annotations.XStreamAlias;
040 import com.thoughtworks.xstream.annotations.XStreamImplicit;
041 import com.thoughtworks.xstream.converters.ConverterLookup;
042 import com.thoughtworks.xstream.converters.ConverterRegistry;
043 import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
044 import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
045 import com.thoughtworks.xstream.mapper.CannotResolveClassException;
046 import com.thoughtworks.xstream.mapper.Mapper;
047 import com.thoughtworks.xstream.mapper.MapperWrapper;
048
049 import edu.ucdenver.ccp.common.reflection.PrivateAccessor;
050 import edu.ucdenver.ccp.common.string.StringUtil;
051
052 /**
053 * NOTE: The code to check to see if a field is valid is too complex. It is also not complete. It
054 * still breaks on XStreamImplicit annotations where the class in the collection has an
055 * XStreamAlias, e.g. List<PubMedId> b/c PubMedId has an @XStreamAlias("PMID") annotation.
056 *
057 * Simple extension of {@link XStream} that logs unexpected fields when parsing XML, but does not
058 * through an exception.
059 *
060 * {@link #wrapMapper(MapperWrapper)} code modified from: http://jira.codehaus.org/browse/XSTR-30
061 *
062 * @author Colorado Computational Pharmacology, UC Denver; ccpsupport@ucdenver.edu
063 *
064 */
065 public class IgnoreUnknownFieldXStream extends XStream {
066
067 private static final Logger logger = Logger.getLogger(IgnoreUnknownFieldXStream.class);
068
069 /**
070 *
071 */
072 public IgnoreUnknownFieldXStream() {
073 super();
074 }
075
076 /**
077 * @param hierarchicalStreamDriver
078 */
079 public IgnoreUnknownFieldXStream(HierarchicalStreamDriver hierarchicalStreamDriver) {
080 super(hierarchicalStreamDriver);
081 }
082
083 /**
084 * @param reflectionProvider
085 * @param driver
086 * @param classLoader
087 * @param mapper
088 * @param converterLookup
089 * @param converterRegistry
090 */
091 public IgnoreUnknownFieldXStream(ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver,
092 ClassLoader classLoader, Mapper mapper, ConverterLookup converterLookup, ConverterRegistry converterRegistry) {
093 super(reflectionProvider, driver, classLoader, mapper, converterLookup, converterRegistry);
094 }
095
096 /**
097 * @param reflectionProvider
098 * @param driver
099 * @param classLoader
100 * @param mapper
101 */
102 public IgnoreUnknownFieldXStream(ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver,
103 ClassLoader classLoader, Mapper mapper) {
104 super(reflectionProvider, driver, classLoader, mapper);
105 }
106
107 /**
108 * @param reflectionProvider
109 * @param driver
110 * @param classLoader
111 */
112 public IgnoreUnknownFieldXStream(ReflectionProvider reflectionProvider, HierarchicalStreamDriver driver,
113 ClassLoader classLoader) {
114 super(reflectionProvider, driver, classLoader);
115 }
116
117 /**
118 * @param reflectionProvider
119 * @param hierarchicalStreamDriver
120 */
121 public IgnoreUnknownFieldXStream(ReflectionProvider reflectionProvider,
122 HierarchicalStreamDriver hierarchicalStreamDriver) {
123 super(reflectionProvider, hierarchicalStreamDriver);
124 }
125
126 /**
127 * @param reflectionProvider
128 */
129 public IgnoreUnknownFieldXStream(ReflectionProvider reflectionProvider) {
130 super(reflectionProvider);
131 }
132
133 @Override
134 protected MapperWrapper wrapMapper(MapperWrapper next) {
135 return new MapperWrapper(next) {
136 @Override
137 public boolean shouldSerializeMember(Class definedIn, String fieldName) {
138 logger.debug("Field Name: " + fieldName + " Class defined in: " + definedIn.getName());
139 try {
140 if (definedIn != Object.class) {
141 if (!fieldPresentInClass(fieldName, definedIn)) {
142 return false;
143 }
144 }
145 return definedIn != Object.class || realClass(fieldName) != null;
146 } catch (CannotResolveClassException cnrce) {
147 logger.warn("Unexpected XML element encountered: " + fieldName
148 + ". Making use of this field will require code changes. ");
149 return false;
150 }
151 }
152
153 private boolean fieldPresentInClass(String fieldName, Class clazz) {
154 if (PrivateAccessor.getPrivateField(clazz, fieldName) != null) {
155 logger.debug("Field (" + fieldName + ") is literal member of class: " + clazz.getName());
156 return true;
157 }
158
159 /*
160 * The field name isn't present in the class, but it could still be present via an
161 *
162 * @XStreamAlias annotation
163 */
164 Set<Field> fields = new HashSet<Field>();
165 PrivateAccessor.getAllFields(clazz, fields);
166 for (Field field : fields) {
167 XStreamAlias aliasAnnot = field.getAnnotation(XStreamAlias.class);
168 if (aliasAnnot != null) {
169 if (aliasAnnot.value().equals(fieldName)) {
170 logger.debug("Field (" + fieldName + ") is member of class via XStreamAlias annotation: "
171 + clazz.getName());
172 return true;
173 }
174 }
175 XStreamImplicit implicitAnnot = field.getAnnotation(XStreamImplicit.class);
176 if (implicitAnnot != null) {
177 /* then if this is a collection of "fieldnames" then return true */
178 logger.debug("Generic type: " + field.getGenericType() + " field name: " + fieldName);
179 if (StringUtil.endsWithRegex(field.getGenericType().toString(), "\\.?\\$" + fieldName + ">")) {
180 logger.debug("Field (" + fieldName + ") is member of class via XStreamImplicit annotation: "
181 + clazz.getName());
182 return true;
183 }
184 }
185 }
186 logger.debug("Field (" + fieldName + ") is not a member of class: " + clazz.getName());
187 return false;
188 }
189 };
190 }
191
192 }