1 package net.sf.sapjcosupport;
2
3 import org.apache.log4j.Logger;
4 import org.springframework.core.io.ClassPathResource;
5 import org.springframework.util.xml.DomUtils;
6 import org.w3c.dom.Document;
7 import org.w3c.dom.Element;
8
9 import javax.xml.parsers.DocumentBuilder;
10 import javax.xml.parsers.DocumentBuilderFactory;
11 import java.util.*;
12
13 /**
14 * This class parses a VO SAP mapping and caches it internally.
15 *
16 * @author Jo Vandermeeren
17 * @since Jan 18, 2006 - 5:34:08 PM
18 */
19 public class SapMappingParser {
20 private static final Logger logger = Logger.getLogger(SapMappingParser.class);
21
22 /**
23 * Internal caching class for <code>SapMapping</code> instances.
24 */
25 private static class MappingCache {
26 private static Map cache;
27
28 static {
29 cache = Collections.synchronizedMap(new HashMap());
30 }
31
32 public static synchronized SapMapping get(Class persistentClass) {
33 return (SapMapping) cache.get(persistentClass);
34 }
35
36 public static synchronized void register(Class persistentClass, SapMapping mapping) {
37 if (logger.isInfoEnabled()) {
38 logger.info("Caching mapping for entity " + persistentClass.getName());
39 }
40 cache.put(persistentClass, mapping);
41 }
42 }
43
44 /**
45 * This method retrieves a cached mapping for a given value object class (VO).
46 * If it doesn.t exist in cache, it will be looked up, parsed, cached and returned.
47 *
48 * @param clazz VO class
49 * @return SAP mapping for the given class
50 * @throws SapJcoMappingException if something goes wrong when creating the SAP mapping
51 */
52 public synchronized static SapMapping getInfo(Class clazz) throws SapJcoMappingException {
53 SapMapping mapping = MappingCache.get(clazz);
54 if (mapping == null) {
55 if (logger.isInfoEnabled()) {
56 logger.info("Mapping for entity '" + clazz.getName() + "' not cached yet, parsing mapping definition");
57 }
58 mapping = parseMapping(clazz);
59 MappingCache.register(clazz, mapping);
60 }
61 return mapping;
62 }
63
64 /**
65 * This helper method tries to find the SAP JCO mapping file in the same classpath location as the
66 * <code>clazz</code> class definition. The mapping file has the same name as the class and ends with ".sap.xml".
67 * This file is read and parsed to a <code>SapMapping</code> object.
68 *
69 * @param clazz the VO class
70 * @return SAP mapping
71 * @throws SapJcoMappingException if something goes wrong when creating the SAP mapping
72 */
73 private static SapMapping parseMapping(Class clazz) throws SapJcoMappingException {
74 try {
75 String fqName = clazz.getName();
76 String resource = fqName.replace('.', '/');
77 String shortName = fqName.substring(fqName.lastIndexOf(".") + 1);
78 ClassPathResource classPathResource = new ClassPathResource(resource + ".sap.xml");
79
80 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
81 Document mappingXml = builder.parse(classPathResource.getInputStream());
82
83 Element root = mappingXml.getDocumentElement();
84
85 List classDefs = DomUtils.getChildElementsByTagName(root, "class");
86 if (classDefs == null || classDefs.size() == 0) {
87 throw new SapJcoMappingException(
88 "SAP Mapping xml '" + shortName + ".sap.xml' should contain 1 <class/> definition");
89 }
90 Element classDef = (Element) classDefs.get(0);
91 String className = classDef.getAttribute("name");
92 String bapiName = classDef.getAttribute("bapi");
93 if (className == null || className.trim().length() == 0) {
94 throw new SapJcoMappingException("'name' attribute of 'class' element is required");
95 }
96
97 SapMapping sapMapping = new SapMapping(className);
98
99 boolean fieldSelectionExists = false;
100
101 List lists = DomUtils.getChildElementsByTagName(root, "list");
102 for (int i = 0; i < lists.size(); i++) {
103 Element listsElement = (Element) lists.get(i);
104 List inputLists = DomUtils.getChildElementsByTagName(listsElement, "inputList");
105 for (Iterator inputListIterator = inputLists.iterator(); inputListIterator.hasNext();) {
106 Element inputListElement = (Element) inputListIterator.next();
107 String[] listAttributes = extractListElementAttributes(inputListElement);
108 if (listAttributes != null) {
109 sapMapping.addInputList(new SapListMapping(listAttributes[0], listAttributes[1]));
110 }
111 }
112 String[] listAttributes = selectAndExtractListElementAttributes(listsElement, "outputList");
113 if (listAttributes != null) {
114 sapMapping.setOutputList(new SapListMapping(listAttributes[0], listAttributes[1]));
115 }
116 listAttributes = selectAndExtractListElementAttributes(listsElement, "fieldSelection");
117 if (listAttributes != null) {
118 fieldSelectionExists = true;
119 sapMapping.setFieldSelection(new SapListMapping(listAttributes[0], listAttributes[1]));
120 }
121 }
122
123 Map fieldSelectionMap = new HashMap();
124 if (bapiName == null || bapiName.length() == 0) {
125 throw new SapJcoMappingException("'bapi' attribute of 'class' element is required");
126 }
127 SapBapiMapping bapi = new SapBapiMapping(bapiName);
128
129
130 List structureList = DomUtils.getChildElementsByTagName(classDef, "structure");
131 for (int j = 0; j < structureList.size(); j++) {
132 Element structureElement = (Element) structureList.get(j);
133 String structureName = structureElement.getAttribute("name");
134 if (structureName == null || structureName.length() == 0) {
135 throw new SapJcoMappingException("'name' attribute of 'structure' element is required");
136 }
137 String mappingType = structureElement.getAttribute("type");
138 SapStructureMapping structure;
139
140 if ("list".equalsIgnoreCase(mappingType)) {
141 String keyField = structureElement.getAttribute("key-field");
142 String primaryKey = structureElement.getAttribute("pk");
143 if (keyField != null && keyField.trim().length() > 0 && primaryKey != null && primaryKey.trim()
144 .length() > 0) {
145
146 structure = new SapStructureMapping(structureName, SapStructureMapping.LIST_OF_MAPS);
147 structure.setKeyField(keyField);
148 structure.setPrimaryKey(primaryKey);
149 } else {
150
151 structure = new SapStructureMapping(structureName, SapStructureMapping.LIST);
152 }
153 } else if ("map".equalsIgnoreCase(mappingType)) {
154 String keyField = structureElement.getAttribute("key-field");
155 if (keyField == null || keyField.length() == 0) {
156 throw new SapJcoMappingException(
157 "'key-field' attribute of 'structure' element is required when 'type' is 'map'");
158 }
159
160 structure = new SapStructureMapping(structureName, SapStructureMapping.MAP);
161 structure.setKeyField(keyField);
162 } else if ("entity".equalsIgnoreCase(mappingType)) {
163 String entityClass = structureElement.getAttribute("class");
164 String collection = structureElement.getAttribute("collection");
165 structure = new SapStructureMapping(structureName, SapStructureMapping.ENTITY);
166 structure.setCollectionName(collection);
167 structure.setEntityClass(entityClass);
168 } else {
169 structure = new SapStructureMapping(structureName);
170 }
171
172
173
174 String excluded = structureElement.getAttribute("excluded");
175 if ("true".equalsIgnoreCase(excluded)) {
176 structure.setExcluded(true);
177 }
178
179
180 List propertyList = DomUtils.getChildElementsByTagName(structureElement, "property");
181 for (int k = 0; k < propertyList.size(); k++) {
182 Element propertyElement = (Element) propertyList.get(k);
183 String propertyName = propertyElement.getAttribute("name");
184 String fieldName = propertyElement.getAttribute("field");
185 String key = propertyElement.getAttribute("key");
186 String type = propertyElement.getAttribute("type");
187 String not_null = propertyElement.getAttribute("not-null");
188 if (fieldSelectionExists) {
189 String profiles = propertyElement.getAttribute("profiles");
190 if (profiles != null) {
191 StringTokenizer st = new StringTokenizer(profiles, ",", false);
192 while (st.hasMoreTokens()) {
193 String profile = st.nextToken();
194 List selection = (List) fieldSelectionMap.get(profile);
195 if (selection == null) {
196 selection = new ArrayList();
197 }
198 selection.add(fieldName);
199 fieldSelectionMap.put(profile, selection);
200 }
201 }
202 }
203 if (not_null == null || not_null.length() == 0) {
204 not_null = "false";
205 }
206 if (!("true".equalsIgnoreCase(not_null) || "false".equalsIgnoreCase(not_null))) {
207 throw new SapJcoMappingException(
208 "'not-null' attribute of 'property' element should be 'false' or 'true'");
209 }
210 if (propertyName == null || propertyName.length() == 0) {
211 throw new SapJcoMappingException("'name' attribute of 'property' element is required");
212 }
213 if (fieldName == null || fieldName.length() == 0) {
214 throw new SapJcoMappingException("'field' attribute of 'property' element is required");
215 }
216 if (type == null || type.length() == 0) {
217 type = "string";
218 }
219 boolean required = Boolean.valueOf(not_null).booleanValue();
220 if (structure.isMap() || structure.isListOfMaps()) {
221 if (key == null || key.length() == 0) {
222 throw new SapJcoMappingException(
223 "'key' attribute of 'property' element is required when the structure is of type 'map'");
224 }
225 structure.addField(new SapFieldMapping(propertyName, fieldName, type, key, className,
226 required));
227 } else {
228 structure.addField(new SapFieldMapping(propertyName, fieldName, type, className, required));
229 }
230 }
231 bapi.addStructure(structure);
232 }
233
234 List inputList = DomUtils.getChildElementsByTagName(classDef, "input");
235 for (int j = 0; j < inputList.size(); j++) {
236 Element inputElement = (Element) inputList.get(j);
237 String inputName = inputElement.getAttribute("name");
238 String inputDefault = inputElement.getAttribute("default");
239 String notNull = inputElement.getAttribute("not-null");
240 String pk = inputElement.getAttribute("pk");
241 if (inputName == null || inputName.length() == 0) {
242 throw new SapJcoMappingException("'name' attribute of 'input' element is required");
243 }
244 boolean required = toBoolean(notNull, "false");
245 boolean isPk = toBoolean(pk, "false");
246 bapi.addInput(new SapInput(inputName, inputDefault, required, isPk));
247 }
248 bapi.setFieldSelection(fieldSelectionMap);
249 sapMapping.setBapi(bapi);
250
251 return sapMapping;
252 } catch (Exception e) {
253 String msg = new StringBuffer("Error loading mapping info for entity '").append(clazz.getName()).append("'")
254 .toString();
255 logger.error(msg, e);
256 throw new SapJcoMappingException(msg);
257 }
258 }
259
260 /**
261 * This helper method extracts the first child element with name <code>childElement</code> from
262 * the XML element <code>parentListsElement</code> and extracts the String values that are needed to construct
263 * the input resp. output list.
264 *
265 * @param parentListsElement parent XML element
266 * @param childElement child XML element name
267 * @return String array containing <code>name</code> attribute (at position 0) and <code>pk</code> attribute (at position 1)
268 * @throws SapJcoMappingException if the 'name' or 'pk' attribute is not available in the XML element
269 */
270 private static String[] selectAndExtractListElementAttributes(Element parentListsElement,
271 String childElement) throws SapJcoMappingException {
272 List list = DomUtils.getChildElementsByTagName(parentListsElement, childElement);
273 if (list != null && list.size() > 0) {
274 return extractListElementAttributes((Element) list.get(0));
275 }
276 return null;
277 }
278
279 private static String[] extractListElementAttributes(Element element) {
280 if (element == null) {
281 return null;
282 }
283 String[] attributes = new String[2];
284 attributes[0] = element.getAttribute("name");
285 if (attributes[0] == null || attributes[0].trim().length() == 0) {
286 throw new SapJcoMappingException("'name' attribute of '" + element + "' element in 'list' is required");
287 }
288 attributes[1] = element.getAttribute("pk");
289 if (attributes[1] == null || attributes[1].trim().length() == 0) {
290 throw new SapJcoMappingException("'pk' attribute of '" + element + "' element in 'list' is required");
291 }
292 return attributes;
293 }
294
295 /**
296 * Converts String "true" to boolean <code>true</code> and String "false" to boolean <code>false</code>.
297 *
298 * @param booleanValue String value that should be parsed to boolean
299 * @param defaultValue default value to use in case booleanValue is null or zero in length
300 * @return boolean that corresponds with the String parameter
301 */
302 private static boolean toBoolean(String booleanValue, String defaultValue) {
303 if (booleanValue == null || booleanValue.length() == 0) {
304 booleanValue = defaultValue;
305 }
306 if (!("true".equalsIgnoreCase(booleanValue) || "false".equalsIgnoreCase(booleanValue))) {
307 throw new SapJcoMappingException("boolean attributes should be 'false' or 'true'");
308 }
309 return Boolean.valueOf(booleanValue).booleanValue();
310 }
311 }