883206f9aa3e38b21c88e4bea5f2242ba0173c0e
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / aa / attrresolv / provider / FormattedAttributeDefinition.java
1 /*
2  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3  * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
4  * provided that the following conditions are met: Redistributions of source code must retain the above copyright
5  * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
6  * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
7  * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
8  * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
9  * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
10  * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
11  * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
12  * products derived from this software without specific prior written permission. For written permission, please contact
13  * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
14  * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
15  * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
16  * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
18  * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
19  * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 /*
27  * Contributed by SunGard SCT.
28  */
29
30 package edu.internet2.middleware.shibboleth.aa.attrresolv.provider;
31
32 import java.lang.reflect.Constructor;
33 import java.security.Principal;
34 import java.text.Format;
35 import java.util.Collection;
36 import java.util.Iterator;
37
38 import org.apache.log4j.Logger;
39 import org.w3c.dom.Element;
40 import org.w3c.dom.NodeList;
41
42 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn;
43 import edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies;
44 import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolutionPlugInException;
45 import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
46
47 /**
48  * The FormattedAttributeDefinition allows attribute values to be formatted using any custom formatter, such as the
49  * java.text.MessageFormat, java.text.DateFormat or java.text.NumberFormat etc. It allows the source attribute to be
50  * parsed using a specified formatter and the target to formatted using another formatter. Since the formatters for
51  * source and target are both passed in as arguments along with the pattern string, a high degree of customization is
52  * allowed. A value handler may also be specified, if needed. The value handler will see the 'target' value after the
53  * target formatter has completed the value transformation. The 'format' attribute of the Source and Target elements
54  * specify the class name of the formatter and MUST be a subclass of java.text.Format. The
55  * <code>sourceFormat.parseObject(sourceValue)</code> method is used to convert the source attribute to an Object
56  * (say, obj), which is then converted back to a String using the <code>targetFormat.format( obj )</code> method.
57  * 
58  * @author <a href="mailto:vgoenka@sungardsct.com">Vishal Goenka </a>
59  */
60
61 public class FormattedAttributeDefinition extends SimpleBaseAttributeDefinition implements AttributeDefinitionPlugIn {
62
63         private static Logger log = Logger.getLogger(FormattedAttributeDefinition.class.getName());
64
65         // The format of Source Attribute
66         private Format sourceFormat;
67
68         // The format of Target Attribute
69         private Format targetFormat;
70
71         // if source and target formatters are same should we skip formatting? In a few cases, the formatter may still
72         // produce
73         // a different result so one may still want the formatting, even though the source and target formatters are
74         // identical.
75         private boolean skipFormatting = false;
76
77         public FormattedAttributeDefinition(Element e) throws ResolutionPlugInException {
78
79                 super(e);
80                 NodeList sources = e.getElementsByTagName("Source");
81                 NodeList targets = e.getElementsByTagName("Target");
82
83                 if ((sources == null) || (sources.getLength() != 1) || (targets == null) || (targets.getLength() != 1)) {
84                         log
85                                         .error("There MUST be exactly 1 'Source' and 1 'Target' definition for a FormattedAttributeDefinition for ("
86                                                         + getId() + ")");
87                         throw new ResolutionPlugInException(
88                                         "There MUST be exactly 1 'Source' and 1 'Target' definition for a FormattedAttributeDefinition for ("
89                                                         + getId() + ")");
90                 }
91                 Element source = (Element) sources.item(0);
92                 Element target = (Element) targets.item(0);
93
94                 sourceFormat = createFormat(source);
95                 targetFormat = createFormat(target);
96
97                 if (sourceFormat.equals(targetFormat)) {
98                         String skipIfSameFormat = e.getAttribute("skipIfSameFormat");
99                         skipFormatting = Boolean.valueOf(skipIfSameFormat).booleanValue();
100                         if (!skipFormatting) {
101                                 log.warn("Source and Target formats are identical for (" + getId()
102                                                 + "). Set 'skipIfSameFormat=true' on the CustomAttributeDefinition element to skip.");
103                         } else {
104                                 log.debug("Source and Target formats are identical for (" + getId()
105                                                 + "). Formatting will be skipped since 'skipIfSameFormat=true'");
106                         }
107                 }
108         }
109
110         /**
111          * @see edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn#resolve(edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute,
112          *      java.security.Principal, java.lang.String, java.lang.String,
113          *      edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies)
114          */
115         public void resolve(ResolverAttribute attribute, Principal principal, String requester, String responder,
116                         Dependencies depends) throws ResolutionPlugInException {
117
118                 // Resolve all dependencies to arrive at the source values (unformatted)
119                 Collection results = resolveDependencies(attribute, principal, requester, depends);
120
121                 Iterator resultsIt = results.iterator();
122
123                 while (resultsIt.hasNext()) {
124                         String value = getString(resultsIt.next());
125                         if (skipFormatting) attribute.addValue(value);
126                         else {
127                                 try {
128                                         Object parsed = sourceFormat.parseObject(value);
129                                         value = targetFormat.format(parsed);
130                                         attribute.addValue(value);
131                                 } catch (Exception e) {
132                                         // We simply log an error for values that give errors during formatting rather than abandoning the
133                                         // whole process
134                                         log.error("Attribute value for (" + getId() + ") --> (" + value
135                                                         + ") failed during format conversion with exception: " + e.getMessage());
136                                 }
137                         }
138                 }
139                 attribute.setResolved();
140         }
141
142         /**
143          * Construct the specified formatter, which must be an instance of java.text.Format. It reads the attributes
144          * 'format' and 'pattern' from the specified element, asserts that both are non-null and instantiates the class
145          * specified by 'format' by passing in the 'pattern' to the single String argument constructor.
146          * 
147          * @param element
148          *            the XML element describing the FormatType as defined in the FormattedAttributeDefinition.xsd
149          * @return the initialized formatter, which must be a sub-class of java.text.Format
150          */
151         private Format createFormat(Element element) throws ResolutionPlugInException {
152
153                 String elementName = element.getTagName();
154                 String format = element.getAttribute("format");
155                 String pattern = element.getAttribute("pattern");
156
157                 log.debug(getId() + " <" + elementName + " format=\"" + format + "\" pattern=\"" + pattern + "\"/>");
158
159                 if ((format == null) || ("".equals(format)) || (pattern == null) || ("".equals(pattern))) {
160                         String error = elementName + " must have 'format' and 'pattern' attributes specified for (" + getId() + ")";
161                         log.error(error);
162                         throw new ResolutionPlugInException(error);
163                 }
164                 try {
165                         Class formatClass = Class.forName(format);
166                         if (!Format.class.isAssignableFrom(formatClass)) { throw new ResolutionPlugInException("Specified format ("
167                                         + format + ") MUST be a subclass of java.text.Format"); }
168                         Constructor formatCons = formatClass.getConstructor(new Class[]{String.class});
169                         return (Format) formatCons.newInstance(new String[]{pattern});
170                 } catch (ClassNotFoundException e) {
171                         String error = "Specified format class (" + format + ") could not be found for (" + getId() + ")";
172                         log.error(error);
173                         throw new ResolutionPlugInException(error);
174                 } catch (Exception e) {
175                         String error = "Error creating " + elementName + " formatter for (" + getId() + "). Cause: "
176                                         + e.getMessage();
177                         log.error(error, e);
178                         throw new ResolutionPlugInException(error);
179                 }
180         }
181
182 }