001: /**
002: * $Id: MAPClientDetector.java,v 1.4 2005/08/08 06:14:03 bs126381 Exp $
003: * Allrights reserved. Use of this product is subject to license terms.
004: * Federal Acquisitions: Commercial Software -- Government Users Subject
005: * to Standard License Terms and Conditions.
006: *
007: * Sun, Sun Microsystems, the Sun logo, and Sun ONE are trademarks or
008: * registered trademarks of Sun Microsystems,Inc. in the United States
009: * and other countries.
010: */
011:
012: /**
013: * <p>The <CODE>ClientDetector</CODE> class implements the client detection
014: * algorithm for the wireless portal server.
015: */package com.sun.mobile.cdm;
016:
017: import com.aligo.util.HeaderHash;
018: import com.aligo.profile.manager.HeaderMap;
019: import com.aligo.profile.manager.SearchRule;
020: import com.aligo.profile.manager.config.ProfileManagerConfig;
021:
022: import com.iplanet.services.cdm.DefaultClientTypesManager;
023: import com.iplanet.services.cdm.clientschema.AMClientCapData;
024: import com.iplanet.services.cdm.clientschema.AMClientCapException;
025:
026: import javax.servlet.http.HttpServletRequest;
027:
028: import java.util.Map;
029: import java.util.Set;
030: import java.util.HashMap;
031: import java.util.HashSet;
032: import java.util.Iterator;
033: import java.util.StringTokenizer;
034:
035: import java.io.InputStreamReader;
036: import java.io.ByteArrayInputStream;
037: import java.security.AccessController;
038:
039: import com.sun.identity.security.AdminTokenAction;
040: import com.iplanet.services.cdm.Client;
041: import com.iplanet.services.cdm.ClientsManager;
042: import com.iplanet.services.cdm.ClientDetectionInterface;
043:
044: import com.iplanet.services.cdm.ClientException;
045: import com.iplanet.services.cdm.ClientDetectionException;
046:
047: import com.iplanet.sso.SSOToken;
048: import com.iplanet.sso.SSOTokenManager;
049:
050: import com.iplanet.am.util.Debug;
051: import com.iplanet.am.util.AMClientDetector;
052:
053: import com.sun.mobile.util.ContentUtils;
054:
055: public class MAPClientDetector implements ClientDetectionInterface {
056:
057: protected static Debug debug = Debug
058: .getInstance("MAPClientDetector");
059:
060: private static SearchRule[] searchRuleSet_ = null;
061: private static HeaderMap[] headerMapSet_ = null;
062:
063: private static String dimensionDelim_ = ",";
064: private static String stringListDelim_ = ",";
065:
066: //
067: // rendering engine's assumption of dimension delimiter
068: //
069: public static final String DIMENSION_DELIMITER = "x";
070:
071: //
072: // dimension delimiter in the incoming headers (sent by devices).
073: //
074: public static final String DIM_DELIM_INCOMING = ProfileManagerConfig.DIMENSION_DELIMITER;
075:
076: public static final String HTTP_USER_AGENT = "user-agent";
077: public static final String CLIENT_TYPE = "clientType";
078: public static final String PARENT_ID = "parentId";
079: public static final String USER_AGENT = "userAgent"; // client property
080:
081: private static SSOToken adminToken = null;
082:
083: private static final String GENERIC_HTML = "genericHTML";
084:
085: /**
086: * Create an instance of MAPClientTypesManager, because
087: * we need a way of adding new clientdata for the merge
088: * with internal/external dbs. Also the Clientdetectioninterface
089: * does not have all the methods we need. It also shouldn't be
090: * a problem if we store the data in static members. (Justification:
091: * IS6.0 already does this - it createsatleast 3 instances of
092: * ClientTypesManager).
093: */
094:
095: protected static DefaultClientTypesManager mngr = (DefaultClientTypesManager) AMClientDetector
096: .getClientTypesManagerInstance();
097:
098: static {
099: adminToken = (SSOToken) AccessController
100: .doPrivileged(AdminTokenAction.getInstance());
101:
102: try {
103: //
104: //get the profileManager.xml
105: //
106: String profileManagerXml = AMClientCapData
107: .getInternalInstance().getProfileManagerXML();
108:
109: InputStreamReader inputStreamReader = new InputStreamReader(
110: new ByteArrayInputStream(profileManagerXml
111: .getBytes()));
112:
113: ProfileManagerConfig.init(inputStreamReader);
114:
115: // Get the HeaderMap set
116: headerMapSet_ = ProfileManagerConfig.getHeaderMaps();
117:
118: // Get Search Rules
119: searchRuleSet_ = ProfileManagerConfig.getSearchRules();
120:
121: // Get the Dimension delimiter for the header attributes
122: String tempChar = ProfileManagerConfig
123: .resolve(DIM_DELIM_INCOMING);
124:
125: if (tempChar != null && !tempChar.equals("")) {
126: dimensionDelim_ = tempChar;
127: }
128:
129: // Get the StringList delimiter for the header attributes
130: tempChar = ProfileManagerConfig
131: .resolve(ProfileManagerConfig.STRINGLIST_DELIMITER);
132:
133: if (tempChar != null && !tempChar.equals("")) {
134: stringListDelim_ = tempChar;
135: }
136: } catch (Exception ex) {
137: String msg = "Error loading XML configuration file: ";
138: debug.error(msg + ex.getMessage(), ex);
139: }
140: }
141:
142: public MAPClientDetector() {
143: }
144:
145: protected Client getClient(HttpServletRequest request) {
146: String httpUA = request.getHeader(HTTP_USER_AGENT);
147:
148: if (debug.messageEnabled()) {
149: debug.message("======: Http user-agent Header = " + httpUA);
150: }
151:
152: if ((httpUA == null) || httpUA.equals("")) {
153: /**
154: * We can detect a client without the user-agent, since we look
155: * at the accept headers etc. But we are not able to store it
156: * usefully, because we use the user-agent as the key into the
157: * userAgentMap. Till Aligo supports a HeaderRule, that allows
158: * re-using a client, we return null and the caller checks for
159: * null & returns the defauiltClient (genericHTML).
160: */
161: return null;
162: }
163:
164: HeaderHash headerHash = new HeaderHash(request);
165: HashMap header = headerHash.getHashMapHandle();
166:
167: Client client = null;
168: boolean foundKeyWordMatch = false;
169: boolean allowed = mngr.canCreateClients();
170: for (int i = 0; i < searchRuleSet_.length; i++) {
171: SearchRule rule = searchRuleSet_[i];
172: String headerAttr = rule.getHeaderAttribute();
173: String headerVal = (String) header.get(headerAttr);
174:
175: if (rule.getSearchType() == SearchRule.SEARCH_HEADER_ATTRIBUTE_VALUE) {
176: //
177: // Search the libraries using the entire header attribute value
178: // Does it matter if there is more than one rule of this
179: // type that matches. Probably
180: // not, because the one with the highest precedence would
181: // win, but this is like saying a record has two primary keys.
182: // If a partial match rule exists, perform a partial match
183: // between the keys in the libraries and the header attribute.
184: //
185:
186: String partialMatchRule = rule.getPartialMatchRule();
187: client = searchLibraries(headerAttr, headerVal,
188: partialMatchRule);
189: } else {
190: //
191: // Search the header attribute value for the given search
192: // string. If found, and its not GENERIC_HTML AND we have
193: // permission to create new Clients, set foundKeyWordMatch
194: // to create a new client. (We return genericHTML as a perf.
195: // improvement to detect html clients. Look at the first
196: // 3 rules of profileManager.xml)
197: //
198:
199: if ((headerVal != null)
200: && ((headerVal.indexOf(rule.getSearchString()) != -1))) {
201: String keyWordMap = rule.getKeywordMap();
202: try {
203: client = ClientsManager.getInstance(keyWordMap);
204: if (keyWordMap.equals(GENERIC_HTML)) {
205: //
206: // Add it to the PartialMatchMap so that this client
207: // will not have to loop thro all the userAgents
208: // before trying a keyword search, next time.
209: //
210: String ct = client.getClientType();
211: mngr.addToPartialMatchMap(headerVal, ct);
212: } else if (allowed) {
213: //
214: // not genericHTML && allowed to create clients
215: //
216: foundKeyWordMatch = true;
217: }
218: } catch (ClientException ce) {
219: debug
220: .warning("Unknown clientType in KeywordMap: "
221: + keyWordMap);
222: }
223: }
224: }
225:
226: if (client != null) {
227: if (foundKeyWordMatch) {
228: if (debug.messageEnabled()) {
229: debug
230: .message("foundKeyWordMatch: Creating new client:");
231: }
232:
233: try {
234: Map newCD = createClientData(client, header);
235: String newCT = getFirstAndOnlyString((Set) newCD
236: .get(CLIENT_TYPE));
237:
238: client = addClientInternal(newCT, newCD);
239:
240: } catch (Exception ex) {
241: debug.warning("Error creating client: "
242: + "Returning defaultClientType: ", ex);
243: client = null;
244: }
245: }
246:
247: break;
248: }
249: }
250:
251: return client;
252: }
253:
254: /**
255: * For now we will have attr to only be HTTP_USER_AGENT.
256: * If we do allow other attribute values, then we need to have the
257: * search algorithm very compute intensive as we need to look in all
258: * clients.
259: *
260: * @param attr Name of the header attribute, the search is required on.
261: * @param value The value (from the HttpRequest) of this "attr".
262: * @param rule The PartialMatchRule to apply for the search.
263: */
264: private Client searchLibraries(String attr, String value,
265: String rule) {
266: Client client = null;
267: if (value == null || value.equals("")) {
268: return client;
269: }
270:
271: if ((attr != null) && (!attr.equals(HTTP_USER_AGENT))) {
272: debug.error("Search with Http-header not supported: "
273: + attr);
274: return client;
275: }
276:
277: if (rule == null || rule.equals("")) {
278: client = mngr.getFromUserAgentMap(value);
279: return client;
280: }
281:
282: String cType = mngr.getPartiallyMatchedClient(value);
283: if (cType != null) {
284: try {
285: client = ClientsManager.getInstance(cType);
286: } catch (ClientException ce) {
287: client = null;
288: debug.warning("ClientInstance = null" + cType + " :",
289: ce);
290: }
291:
292: return client;
293: }
294:
295: Iterator libKeys = mngr.userAgentSet().iterator();
296: String matchedKey = null;
297: int matchLen = 0;
298: boolean matchFound = false;
299:
300: while (libKeys.hasNext()) {
301: String libKey = (String) libKeys.next();
302:
303: if (rule.equals(SearchRule.STARTS_WITH)) {
304: if (value.startsWith(libKey)) {
305: matchFound = true;
306: }
307: } else if (rule.equals(SearchRule.CONTAINS)) {
308: if (value.indexOf(libKey) > -1) {
309: matchFound = true;
310: }
311: } else if (rule.equals(SearchRule.ENDS_WITH)) {
312: if (value.endsWith(libKey)) {
313: matchFound = true;
314: }
315: }
316:
317: if (matchFound) {
318: int len = libKey.length();
319: if (len > matchLen) {
320: matchLen = len;
321: matchedKey = libKey;
322: }
323:
324: matchFound = false; // reset
325: }
326: }
327:
328: if (matchedKey != null) { // (and) its a partial Match
329: //
330: // Perf: Add this client to the partialMatchMap (indexed by
331: // the full user-agent String).This way subsequent requests
332: // by this client wont have to go thro' this Iterator.
333: //
334: client = mngr.getFromUserAgentMap(matchedKey);
335: String ct = client.getClientType();
336: mngr.addToPartialMatchMap(value, ct);
337: }
338:
339: return client;
340: }
341:
342: private Map createClientData(Client client, HashMap header) {
343: if (header == null) {
344: if (debug.messageEnabled()) {
345: debug.message("header sent in was null");
346: }
347:
348: return null;
349: }
350:
351: Map newClientData = new HashMap();
352: if (client == null) {
353: if (debug.messageEnabled()) {
354: debug
355: .message("createClientData(): No Parent to inherit from");
356: }
357: } else {
358: //
359: // Object needs to be cloned..
360: //
361: String parentCT = client.getProperty(CLIENT_TYPE);
362: if (debug.messageEnabled()) {
363: debug
364: .message("createClientData(): Parent: "
365: + parentCT);
366: }
367:
368: Map parentData = mngr.getClientTypeData(parentCT);
369:
370: Set parents = new HashSet(1);
371: parents.add(parentCT);
372: newClientData.put(PARENT_ID, parents);
373: }
374:
375: //
376: // Use HeaderMap to set Client attributes from the header attributes
377: //
378: HeaderMap map = null;
379: for (int i = 0; i < headerMapSet_.length; i++) {
380: if ((map = headerMapSet_[i]) == null) {
381: continue;
382: }
383:
384: String hdrAttrName = map.getHeaderAttribute();
385: String hdrAttrVal = (String) header.get(hdrAttrName);
386:
387: if (hdrAttrVal == null || hdrAttrVal.equals("")) {
388: continue;
389: }
390:
391: //
392: // Determine datatype in order to create argument
393: //
394: String datatype = map.getUADatatype();
395: String clientProperty = map.getClientProperty();
396: String searchString = map.getSearchString();
397:
398: String value = hdrAttrVal;
399: if (searchString != null && !searchString.equals("")) {
400: if (hdrAttrVal.indexOf(searchString) != -1) {
401: value = map.getUADataValue();
402: }
403:
404: if (value == null || value.equals("")) {
405: continue;
406: }
407: }
408:
409: Object[] args = new Object[1];
410: if (datatype.equals(HeaderMap.UA_DATATYPE_STRING)) {
411: args[0] = value.trim();
412: } else if (datatype.equals(HeaderMap.UA_DATATYPE_DIMENSION)) {
413: //
414: // Replace header delimiter character with the
415: // our delimiter character('x')
416: //
417: args[0] = value.replace(dimensionDelim_.charAt(0),
418: DIMENSION_DELIMITER.charAt(0));
419: } else if (datatype.equals(HeaderMap.UA_DATATYPE_NUMBER)) {
420: args[0] = convertToInt(value);
421: } else if (datatype
422: .equals(HeaderMap.UA_DATATYPE_STRINGLIST)) {
423: args = getAttributeTokens(value, stringListDelim_);
424: } else if (datatype.equals(HeaderMap.UA_DATATYPE_BOOLEAN)) {
425: args[0] = convertToBoolean(value);
426: } else {
427: debug.warning("Unknown UADataType (prop) = "
428: + clientProperty);
429: continue;
430: }
431:
432: Set props = createSet(args);
433: newClientData.put(clientProperty, props);
434: }
435:
436: Set uaSet = (Set) newClientData.get(USER_AGENT);
437: String userAgent = getFirstAndOnlyString(uaSet);
438: Set ctSet = makeClientType(userAgent);
439:
440: newClientData.put(CLIENT_TYPE, ctSet);
441:
442: if (debug.messageEnabled()) {
443: debug.message("Created ClientMap = " + newClientData);
444: }
445:
446: return newClientData;
447: }
448:
449: private Boolean convertToBoolean(String value) {
450: Boolean bool = null;
451:
452: if ((value != null)
453: && (value.toLowerCase().equals("1")
454: || value.toLowerCase().equals("true")
455: || value.toLowerCase().equals("on") || value
456: .toLowerCase().equals("yes"))) {
457: bool = new Boolean(true);
458: } else {
459: bool = new Boolean(false);
460: }
461:
462: return bool;
463: }
464:
465: private Integer convertToInt(String value) {
466: Integer num = null;
467: try {
468: num = new Integer(value);
469: } catch (Exception ex1) {
470: try { // try to decode hex number
471: num = Integer.decode(value);
472: } catch (Exception ex2) {
473: try { // try once more to decode as hex number
474: num = Integer.decode("0x" + value);
475: } catch (Exception ex3) {
476: debug.warning(".createProfile - "
477: + "Error converting String to number "
478: + value, ex3);
479: }
480: }
481: }
482:
483: return num;
484: }
485:
486: /**
487: * This method gets the attribute tokens given a string of tokens
488: * separated by a delimiter.
489: *
490: * @param String the attribute list.
491: * @param String the delimiter.
492: */
493: private String[] getAttributeTokens(String attribute,
494: String delimiter) {
495: StringTokenizer st = new StringTokenizer(attribute, delimiter);
496: int numTokens = st.countTokens();
497: String[] tokens = new String[numTokens];
498:
499: for (int i = 0; i < numTokens && st.hasMoreTokens(); i++) {
500: tokens[i] = (st.nextToken()).trim();
501: }
502:
503: return tokens;
504: }
505:
506: private Set createSet(Object[] args) {
507: if ((args == null) || (args.length == 0)) {
508: return null;
509: }
510:
511: Set retVal = new HashSet(args.length);
512: for (int i = 0; i < args.length; i++) {
513: retVal.add(args[i].toString());
514: }
515:
516: return retVal;
517: }
518:
519: protected Set createSet(String val) {
520: Set retVal = new HashSet(1);
521: retVal.add(val);
522:
523: return retVal;
524: }
525:
526: protected String getFirstAndOnlyString(Set set) {
527: String retVal = null;
528: if ((set != null) && (set.size() == 1)) {
529: retVal = (String) set.iterator().next();
530: }
531:
532: return retVal;
533: }
534:
535: protected Set makeClientType(String userAgent) {
536: String escapedUA = ContentUtils.escapeClientType(userAgent);
537: Set ctSet = createSet(escapedUA);
538:
539: return ctSet;
540: }
541:
542: /**
543: * The implementation of the Interface method.
544: */
545: public String getClientType(HttpServletRequest request)
546: throws ClientDetectionException {
547: String clientType = null;
548: Client ct = null;
549:
550: try {
551: ct = getClient(request);
552: } catch (Throwable t) {
553: debug.warning("getClientType() failed: ", t);
554: ct = null;
555: }
556:
557: if (ct == null) {
558: if (debug.messageEnabled()) {
559: debug
560: .message("getClientType() Return DefaultInstance: ");
561: }
562: ct = ClientsManager.getDefaultInstance();
563: }
564:
565: clientType = ct.getClientType();
566:
567: if (debug.messageEnabled()) {
568: debug.message("======: getClientType() Returning: "
569: + clientType);
570: }
571:
572: return clientType;
573: }
574:
575: private Client addClientInternal(String ct, Map cMap)
576: throws AMClientCapException {
577: boolean addToInternal = true;
578: return addClient(ct, cMap, addToInternal);
579: }
580:
581: /**
582: * @param internal true indicates save in persistent store.
583: */
584: protected Client addClient(String ct, Map cMap, boolean internal)
585: throws AMClientCapException {
586: Client c = mngr.addClient(adminToken, ct, cMap, internal);
587: return c;
588: }
589: }
|