001: /*
002: * ====================================================================
003: * JAFFA - Java Application Framework For All
004: *
005: * Copyright (C) 2002 JAFFA Development Group
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: *
021: * Redistribution and use of this software and associated documentation ("Software"),
022: * with or without modification, are permitted provided that the following conditions are met:
023: * 1. Redistributions of source code must retain copyright statements and notices.
024: * Redistributions must also contain a copy of this document.
025: * 2. Redistributions in binary form must reproduce the above copyright notice,
026: * this list of conditions and the following disclaimer in the documentation
027: * and/or other materials provided with the distribution.
028: * 3. The name "JAFFA" must not be used to endorse or promote products derived from
029: * this Software without prior written permission. For written permission,
030: * please contact mail to: jaffagroup@yahoo.com.
031: * 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
032: * appear in their names without prior written permission.
033: * 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
034: *
035: * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: */
049:
050: /*
051: * MappingFilter.java
052: *
053: * Created on February 17, 2004, 4:35 PM
054: */
055:
056: package org.jaffa.beans.moulding.mapping;
057:
058: import java.beans.PropertyDescriptor;
059: import java.util.ArrayList;
060: import java.util.Iterator;
061: import java.util.List;
062: import java.util.regex.Pattern;
063: import org.apache.log4j.Logger;
064: import org.jaffa.beans.moulding.data.domain.DomainDAO;
065: import org.jaffa.util.SplitString;
066: import org.jaffa.util.StringHelper;
067:
068: /** This class creates an instance of a filter for a given
069: * domain object graph, based on a supplied set of mapping rules.
070: *
071: * @author PaulE
072: */
073: public class MappingFilter {
074:
075: private static Logger log = Logger.getLogger(MappingFilter.class);
076:
077: /** Holds value of property rules. */
078: private String[] rules;
079: private GraphMapping graph;
080: private List allFields;
081: private List filteredFields;
082: private boolean[] filterTypes;
083: private Pattern[] filterPatterns;
084:
085: /** Creates a new instance of MappingFilter */
086: public MappingFilter(GraphMapping graph) {
087: this (graph, null);
088: }
089:
090: /** Creates a new instance of MappingFilter */
091: public MappingFilter(GraphMapping graph, String[] rules) {
092: this .graph = graph;
093: allFields = getFieldList(graph.getDataClass());
094: setRules(rules);
095: }
096:
097: /** Getter for property rules.
098: * @return Value of property rules.
099: *
100: */
101: public String[] getRules() {
102: return this .rules;
103: }
104:
105: /** Setter for property rules.
106: * @param rules New value of property rules.
107: *
108: */
109: public void setRules(String[] rules) {
110: this .rules = rules;
111: if (rules == null)
112: this .rules = new String[] { "*" };
113: calculateFilter();
114: }
115:
116: /** Get a list of all the possible objects that can be
117: * addressed in this domain object graph
118: */
119: public static List getFieldList(Class dao) {
120: List out = new ArrayList();
121: getFieldList(dao, out, null, true);
122: return out;
123: }
124:
125: /** return -1 if field is excluded, 1 if included, 0 if no matched
126: **/
127: public boolean isFieldIncluded(String field) {
128: return filteredFields.contains(field);
129: }
130:
131: public String[] getIncludedFields() {
132: return (String[]) filteredFields.toArray(new String[0]);
133: }
134:
135: /**
136: public boolean isIncluded(String fieldRef) {
137: return filteredFields.contains(fieldRef);
138: }
139: */
140:
141: public boolean areSubFieldsIncluded(String fieldPrefix) {
142: for (int j = 0; j < filteredFields.size(); j++) {
143: String field = (String) filteredFields.get(j);
144: if (field.startsWith(fieldPrefix)
145: && field.length() > fieldPrefix.length())
146: return true;
147: }
148: return false;
149: }
150:
151: private static void getFieldList(Class dao, List out,
152: String prefix, boolean includeKeys) {
153: String lastField = null;
154: if (prefix != null) {
155: SplitString ss = new SplitString(prefix, ".", false);
156: if (ss.isValid())
157: lastField = ss.getSuffix();
158: else
159: lastField = ss.getPrefix();
160: }
161:
162: GraphMapping mapper = MappingFactory.getInstance(dao);
163: if (mapper == null) {
164: log.error("Can't find mapping for class " + dao.getName());
165: return;
166: }
167: String[] fields = mapper.getDataFieldNames();
168: if (fields != null) {
169: for (int i = 0; i < fields.length; i++) {
170: String name = fields[i];
171: if (includeKeys || !mapper.isKeyField(name)) {
172: String fullName = name;
173: if (prefix != null)
174: fullName = prefix + name;
175: PropertyDescriptor pd = mapper
176: .getDataFieldDescriptor(name);
177: if (pd != null && pd.getReadMethod() != null) {
178: Class c = pd.getReadMethod().getReturnType();
179: boolean array = c.isArray();
180: if (array)
181: c = c.getComponentType();
182: if (DomainDAO.class.isAssignableFrom(c)) {
183: if (lastField == null
184: || !lastField.equals(name)) {
185: getFieldList(c, out, fullName + ".",
186: array);
187: if (array)
188: fullName = "*" + fullName;
189: else
190: fullName = "+" + fullName;
191: } else {
192: log.debug("Stopped Recursion @ Path="
193: + fullName);
194: fullName = null;
195: }
196: }
197: } else
198: log.debug("Can't introspect field " + name);
199:
200: if (fullName != null) // Don't add if nulled out to prevent recursion
201: out.add(fullName);
202: }
203: }
204: }
205: }
206:
207: private void calculateFilter() {
208: filteredFields = new ArrayList();
209: List relToCheck = new ArrayList();
210: List foToCheck = new ArrayList();
211:
212: // Convert rules to RegEx and cache...
213: filterTypes = new boolean[rules.length + 1];
214: filterPatterns = new Pattern[rules.length + 1];
215: for (int i = 0; i < rules.length; i++) {
216: String filter = rules[i];
217: Pattern p = null;
218: boolean exclude = false;
219: if (filter != null && filter.length() > 0) {
220: exclude = filter.charAt(0) == '-';
221: if (exclude || filter.charAt(0) == '+')
222: filter = filter.substring(1);
223: /* Convert filter to a regex. Rules are...
224: * . => \.
225: * ** => [\w\.]+
226: * * => [\w]+
227: */
228: filter = StringHelper.replace(filter, ".", "\\.");
229: filter = StringHelper
230: .replace(filter, "**", "[\\w\\.]+");
231: filter = StringHelper.replace(filter, "*", "[\\w]+");
232: log.debug("Converted filter '" + rules[i]
233: + "' to pattern '" + filter + "'");
234: p = Pattern.compile(filter);
235: }
236: filterTypes[i] = exclude;
237: filterPatterns[i] = p;
238: }
239:
240: // Now build list of acceptable fields
241: if (allFields != null)
242: for (int i = 0; i < allFields.size(); i++) {
243: String field = (String) allFields.get(i);
244: boolean foreign = field.startsWith("+");
245: boolean related = field.startsWith("*");
246: if (foreign || related)
247: field = field.substring(1);
248: if (includeField(field)) {
249: filteredFields.add(field);
250: if (foreign)
251: foToCheck.add(field);
252: if (related)
253: relToCheck.add(field);
254: }
255: }
256:
257: // Add foreign object, and remove PK references
258: /*
259: if(foToCheck.size() > 0)
260: for(int i = 0; i<foToCheck.size(); i++) {
261: String field = (String)foToCheck.get(i);
262: }
263:
264: // Add foreign objects, and all its PK references
265: if(foToCheck.size() > 0)
266: for(int i = 0; i<foToCheck.size(); i++) {
267: String field = (String)foToCheck.get(i);
268: //@todo not sure if these should be forced in at this point?
269: // how do i get a reference on the foreign object mapping at this
270: // point to find out what the PK's arfe for this object>???
271: ?
272: }
273: */
274:
275: // Removed related reference if there are no relatd fields
276: if (relToCheck.size() > 0)
277: for (Iterator i = relToCheck.iterator(); i.hasNext();) {
278: String field = (String) i.next();
279: log.debug("Check related object " + field);
280: boolean found = false;
281: if (filteredFields != null)
282: for (int j = 0; j < filteredFields.size() && !found; j++) {
283: String field2 = (String) filteredFields.get(j);
284: if (field2.startsWith(field)
285: && field2.length() > field.length()) {
286: found = true;
287: log.debug("Found field on related object "
288: + field2);
289: }
290: }
291: if (!found) {
292: if (filteredFields.remove(field))
293: log.debug("Removed related object " + field);
294: }
295: }
296: // Now make sure based on al the filtering, that all parent object referenced
297: // are in place. You can't have x.y.z without first having x.y and therefore x
298: for (int j = 0; j < filteredFields.size(); j++) {
299: String field = (String) filteredFields.get(j);
300: int pos = field.lastIndexOf('.');
301: if (pos > 0) {
302: field = field.substring(0, pos);
303: if (!filteredFields.contains(field)) {
304: log.debug("Added missing parent object " + field);
305: filteredFields.add(field);
306: }
307: }
308: }
309:
310: }
311:
312: /** return -1 if field is excluded, 1 if included, 0 if no matched
313: **/
314: private boolean includeField(String field) {
315: for (int i = 0; i < rules.length; i++) {
316: Pattern p = filterPatterns[i];
317: boolean exclude = filterTypes[i];
318: if (p != null && p.matcher(field).matches()) {
319: log.debug("field '" + field + "' has been "
320: + (exclude ? "excluded" : "included")
321: + " by pattern '" + p.pattern() + "'");
322: return !exclude;
323: }
324: }
325: return false;
326: }
327:
328: public static void main(String[] args) {
329: String a = "hello";
330: String r = "[\\w]+";
331: System.out.println(a + " matches pattern " + r + " = "
332: + Pattern.matches(r, a));
333: }
334: }
|