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    }