001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.solr.util;
017:
018: import org.w3c.dom.Element;
019: import org.w3c.dom.NamedNodeMap;
020: import org.w3c.dom.Node;
021: import org.w3c.dom.NodeList;
022: import org.apache.solr.core.SolrException;
023:
024: import java.util.Map;
025: import java.util.HashMap;
026: import java.util.List;
027: import java.util.ArrayList;
028: import java.util.Enumeration;
029: import java.util.Iterator;
030:
031: /**
032: * @author yonik
033: * @version $Id: DOMUtil.java 542679 2007-05-29 22:28:21Z ryan $
034: */
035: public class DOMUtil {
036:
037: public static Map<String, String> toMap(NamedNodeMap attrs) {
038: return toMapExcept(attrs);
039: }
040:
041: public static Map<String, String> toMapExcept(NamedNodeMap attrs,
042: String... exclusions) {
043: Map<String, String> args = new HashMap<String, String>();
044: outer: for (int j = 0; j < attrs.getLength(); j++) {
045: Node attr = attrs.item(j);
046: String attrName = attr.getNodeName();
047: for (String ex : exclusions)
048: if (ex.equals(attrName))
049: continue outer;
050: String val = attr.getNodeValue();
051: args.put(attrName, val);
052: }
053: return args;
054: }
055:
056: public static Node getChild(Node node, String name) {
057: if (!node.hasChildNodes())
058: return null;
059: NodeList lst = node.getChildNodes();
060: if (lst == null)
061: return null;
062: for (int i = 0; i < lst.getLength(); i++) {
063: Node child = lst.item(i);
064: if (name.equals(child.getNodeName()))
065: return child;
066: }
067: return null;
068: }
069:
070: public static String getAttr(NamedNodeMap attrs, String name) {
071: return getAttr(attrs, name, null);
072: }
073:
074: public static String getAttr(Node nd, String name) {
075: return getAttr(nd.getAttributes(), name);
076: }
077:
078: public static String getAttr(NamedNodeMap attrs, String name,
079: String missing_err) {
080: Node attr = attrs == null ? null : attrs.getNamedItem(name);
081: if (attr == null) {
082: if (missing_err == null)
083: return null;
084: throw new RuntimeException(missing_err
085: + ": missing mandatory attribute '" + name + "'");
086: }
087: String val = attr.getNodeValue();
088: return val;
089: }
090:
091: public static String getAttr(Node node, String name,
092: String missing_err) {
093: return getAttr(node.getAttributes(), name, missing_err);
094: }
095:
096: //////////////////////////////////////////////////////////
097: // Routines to parse XML in the syntax of the Solr query
098: // response schema.
099: // Should these be moved to Config? Should all of these things?
100: //////////////////////////////////////////////////////////
101: public static NamedList childNodesToNamedList(Node nd) {
102: return nodesToNamedList(nd.getChildNodes());
103: }
104:
105: public static List childNodesToList(Node nd) {
106: return nodesToList(nd.getChildNodes());
107: }
108:
109: public static NamedList nodesToNamedList(NodeList nlst) {
110: NamedList clst = new NamedList();
111: for (int i = 0; i < nlst.getLength(); i++) {
112: addToNamedList(nlst.item(i), clst, null);
113: }
114: return clst;
115: }
116:
117: public static List nodesToList(NodeList nlst) {
118: List lst = new ArrayList();
119: for (int i = 0; i < nlst.getLength(); i++) {
120: addToNamedList(nlst.item(i), null, lst);
121: }
122: return lst;
123: }
124:
125: public static void addToNamedList(Node nd, NamedList nlst, List arr) {
126: // Nodes often include whitespace, etc... so just return if this
127: // is not an Element.
128: if (nd.getNodeType() != Node.ELEMENT_NODE)
129: return;
130:
131: String type = nd.getNodeName();
132:
133: String name = null;
134: if (nd.hasAttributes()) {
135: NamedNodeMap attrs = nd.getAttributes();
136: Node nameNd = attrs.getNamedItem("name");
137: if (nameNd != null)
138: name = nameNd.getNodeValue();
139: }
140:
141: Object val = null;
142:
143: if ("str".equals(type)) {
144: val = getText(nd);
145: } else if ("int".equals(type)) {
146: val = Integer.valueOf(getText(nd));
147: } else if ("long".equals(type)) {
148: val = Long.valueOf(getText(nd));
149: } else if ("float".equals(type)) {
150: val = Float.valueOf(getText(nd));
151: } else if ("double".equals(type)) {
152: val = Double.valueOf(getText(nd));
153: } else if ("bool".equals(type)) {
154: val = Boolean.valueOf(getText(nd));
155: } else if ("lst".equals(type)) {
156: val = childNodesToNamedList(nd);
157: } else if ("arr".equals(type)) {
158: val = childNodesToList(nd);
159: }
160:
161: if (nlst != null)
162: nlst.add(name, val);
163: if (arr != null)
164: arr.add(val);
165: }
166:
167: /**
168: * Drop in replacement for Node.getTextContent().
169: *
170: * <p>
171: * This method is provided to support the same functionality as
172: * Node.getTextContent() but in a way that is DOM Level 2 compatible.
173: * </p>
174: *
175: * @see <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-textContent">DOM Object Model Core</a>
176: */
177: public static String getText(Node nd) {
178:
179: short type = nd.getNodeType();
180:
181: // for most node types, we can defer to the recursive helper method,
182: // but when asked for the text of these types, we must return null
183: // (Not the empty string)
184: switch (type) {
185:
186: case Node.DOCUMENT_NODE: /* fall through */
187: case Node.DOCUMENT_TYPE_NODE: /* fall through */
188: case Node.NOTATION_NODE: /* fall through */
189: return null;
190: }
191:
192: StringBuilder sb = new StringBuilder();
193: getText(nd, sb);
194: return sb.toString();
195: }
196:
197: /** @see #getText(Node) */
198: private static void getText(Node nd, StringBuilder buf) {
199:
200: short type = nd.getNodeType();
201:
202: switch (type) {
203:
204: case Node.ELEMENT_NODE: /* fall through */
205: case Node.ENTITY_NODE: /* fall through */
206: case Node.ENTITY_REFERENCE_NODE: /* fall through */
207: case Node.DOCUMENT_FRAGMENT_NODE:
208: NodeList childs = nd.getChildNodes();
209: for (int i = 0; i < childs.getLength(); i++) {
210: Node child = childs.item(i);
211: short childType = child.getNodeType();
212: if (childType != Node.COMMENT_NODE
213: && childType != Node.PROCESSING_INSTRUCTION_NODE) {
214: getText(child, buf);
215: }
216: }
217: break;
218:
219: case Node.ATTRIBUTE_NODE: /* fall through */
220: /* Putting Attribute nodes in this section does not exactly
221: match the definition of how textContent should behave
222: according to the DOM Level-3 Core documentation - which
223: specifies that the Attr's children should have their
224: textContent concated (Attr's can have a single child which
225: is either Text node or an EntityRefrence). In practice,
226: DOM implementations do not seem to use child nodes of
227: Attributes, storing the "text" directly as the nodeValue.
228: Fortunately, the DOM Spec indicates that when Attr.nodeValue
229: is read, it should return the nodeValue from the child Node,
230: so this approach should work both for strict implementations,
231: and implementations actually encountered.
232: */
233: case Node.TEXT_NODE: /* fall through */
234: case Node.CDATA_SECTION_NODE: /* fall through */
235: case Node.COMMENT_NODE: /* fall through */
236: case Node.PROCESSING_INSTRUCTION_NODE: /* fall through */
237: buf.append(nd.getNodeValue());
238: break;
239:
240: case Node.DOCUMENT_NODE: /* fall through */
241: case Node.DOCUMENT_TYPE_NODE: /* fall through */
242: case Node.NOTATION_NODE: /* fall through */
243: default:
244: /* :NOOP: */
245:
246: }
247: }
248:
249: /**
250: * Replaces ${system.property[:default value]} references in all attributes
251: * and text nodes of supplied node. If the system property is not defined and no
252: * default value is provided, a runtime exception is thrown.
253: *
254: * @param node DOM node to walk for substitutions
255: */
256: public static void substituteSystemProperties(Node node) {
257: // loop through child nodes
258: Node child;
259: Node next = node.getFirstChild();
260: while ((child = next) != null) {
261:
262: // set next before we change anything
263: next = child.getNextSibling();
264:
265: // handle child by node type
266: if (child.getNodeType() == Node.TEXT_NODE) {
267: child.setNodeValue(substituteSystemProperty(child
268: .getNodeValue()));
269: } else if (child.getNodeType() == Node.ELEMENT_NODE) {
270: // handle child elements with recursive call
271: NamedNodeMap attributes = child.getAttributes();
272: for (int i = 0; i < attributes.getLength(); i++) {
273: Node attribute = attributes.item(i);
274: attribute
275: .setNodeValue(substituteSystemProperty(attribute
276: .getNodeValue()));
277: }
278: substituteSystemProperties(child);
279: }
280: }
281: }
282:
283: /*
284: * This method borrowed from Ant's PropertyHelper.replaceProperties:
285: * http://svn.apache.org/repos/asf/ant/core/trunk/src/main/org/apache/tools/ant/PropertyHelper.java
286: */
287: private static String substituteSystemProperty(String value) {
288: if (value == null || value.indexOf('$') == -1) {
289: return value;
290: }
291:
292: List<String> fragments = new ArrayList<String>();
293: List<String> propertyRefs = new ArrayList<String>();
294: parsePropertyString(value, fragments, propertyRefs);
295:
296: StringBuffer sb = new StringBuffer();
297: Iterator<String> i = fragments.iterator();
298: Iterator<String> j = propertyRefs.iterator();
299:
300: while (i.hasNext()) {
301: String fragment = i.next();
302: if (fragment == null) {
303: String propertyName = j.next();
304: String defaultValue = null;
305: int colon_index = propertyName.indexOf(':');
306: if (colon_index > -1) {
307: defaultValue = propertyName
308: .substring(colon_index + 1);
309: propertyName = propertyName.substring(0,
310: colon_index);
311: }
312: fragment = System.getProperty(propertyName,
313: defaultValue);
314: if (fragment == null) {
315: throw new SolrException(
316: SolrException.ErrorCode.SERVER_ERROR,
317: "No system property or default value specified for "
318: + propertyName);
319: }
320: }
321: sb.append(fragment);
322: }
323: return sb.toString();
324: }
325:
326: /*
327: * This method borrowed from Ant's PropertyHelper.parsePropertyStringDefault:
328: * http://svn.apache.org/repos/asf/ant/core/trunk/src/main/org/apache/tools/ant/PropertyHelper.java
329: */
330: private static void parsePropertyString(String value,
331: List<String> fragments, List<String> propertyRefs) {
332: int prev = 0;
333: int pos;
334: //search for the next instance of $ from the 'prev' position
335: while ((pos = value.indexOf("$", prev)) >= 0) {
336:
337: //if there was any text before this, add it as a fragment
338: //TODO, this check could be modified to go if pos>prev;
339: //seems like this current version could stick empty strings
340: //into the list
341: if (pos > 0) {
342: fragments.add(value.substring(prev, pos));
343: }
344: //if we are at the end of the string, we tack on a $
345: //then move past it
346: if (pos == (value.length() - 1)) {
347: fragments.add("$");
348: prev = pos + 1;
349: } else if (value.charAt(pos + 1) != '{') {
350: //peek ahead to see if the next char is a property or not
351: //not a property: insert the char as a literal
352: /*
353: fragments.addElement(value.substring(pos + 1, pos + 2));
354: prev = pos + 2;
355: */
356: if (value.charAt(pos + 1) == '$') {
357: //backwards compatibility two $ map to one mode
358: fragments.add("$");
359: prev = pos + 2;
360: } else {
361: //new behaviour: $X maps to $X for all values of X!='$'
362: fragments.add(value.substring(pos, pos + 2));
363: prev = pos + 2;
364: }
365:
366: } else {
367: //property found, extract its name or bail on a typo
368: int endName = value.indexOf('}', pos);
369: if (endName < 0) {
370: throw new RuntimeException(
371: "Syntax error in property: " + value);
372: }
373: String propertyName = value.substring(pos + 2, endName);
374: fragments.add(null);
375: propertyRefs.add(propertyName);
376: prev = endName + 1;
377: }
378: }
379: //no more $ signs found
380: //if there is any tail to the string, append it
381: if (prev < value.length()) {
382: fragments.add(value.substring(prev));
383: }
384: }
385:
386: }
|