1 /* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to you under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /** 18 * @class 19 * @name Impl 20 * @memberOf myfaces._impl.core 21 * @description Implementation singleton which implements all interface method 22 * defined by our jsf.js API 23 * */ 24 myfaces._impl.core._Runtime.singletonExtendClass("myfaces._impl.core.Impl", Object, 25 /** 26 * @lends myfaces._impl.core.Impl.prototype 27 */ 28 { 29 30 //third option myfaces._impl.xhrCoreAjax which will be the new core impl for now 31 _transport : new (myfaces._impl.core._Runtime.getGlobalConfig("transport", myfaces._impl.xhrCore._Transports))(), 32 33 /** 34 * external event listener queue! 35 */ 36 _evtListeners : new (myfaces._impl.core._Runtime.getGlobalConfig("eventListenerQueue", myfaces._impl._util._ListenerQueue))(), 37 38 /** 39 * external error listener queue! 40 */ 41 _errListeners : new (myfaces._impl.core._Runtime.getGlobalConfig("errorListenerQueue", myfaces._impl._util._ListenerQueue))(), 42 43 /*CONSTANTS*/ 44 45 /*internal identifiers for options*/ 46 IDENT_ALL: "@all", 47 IDENT_NONE: "@none", 48 IDENT_THIS: "@this", 49 IDENT_FORM: "@form", 50 51 /* 52 * [STATIC] constants 53 */ 54 55 P_PARTIAL_SOURCE: "javax.faces.source", 56 P_VIEWSTATE: "javax.faces.ViewState", 57 P_AJAX: "javax.faces.partial.ajax", 58 P_EXECUTE: "javax.faces.partial.execute", 59 P_RENDER: "javax.faces.partial.render", 60 P_EVT: "javax.faces.partial.event", 61 62 /* message types */ 63 ERROR: "error", 64 EVENT: "event", 65 66 /* event emitting stages */ 67 BEGIN: "begin", 68 COMPLETE: "complete", 69 SUCCESS: "success", 70 71 /*ajax errors spec 14.4.2*/ 72 HTTPERROR: "httpError", 73 EMPTY_RESPONSE: "emptyResponse", 74 MALFORMEDXML: "malformedXML", 75 SERVER_ERROR: "serverError", 76 CLIENT_ERROR: "clientError", 77 TIMEOUT_EVENT: "timeout", 78 79 _Lang: myfaces._impl._util._Lang, 80 _Dom: myfaces._impl._util._Dom, 81 82 /*error reporting threshold*/ 83 _threshold: "ERROR", 84 85 /*blockfilter for the passthrough filtering, the attributes given here 86 * will not be transmitted from the options into the passthrough*/ 87 _BLOCKFILTER: {onerror: true, onevent: true, render: true, execute: true, myfaces: true}, 88 89 /** 90 * collect and encode data for a given form element (must be of type form) 91 * find the javax.faces.ViewState element and encode its value as well! 92 * return a concatenated string of the encoded values! 93 * 94 * @throws error in case of the given element not being of type form! 95 * https://issues.apache.org/jira/browse/MYFACES-2110 96 */ 97 getViewState : function(form) { 98 /** 99 * typecheck assert!, we opt for strong typing here 100 * because it makes it easier to detect bugs 101 */ 102 if (form) { 103 form = this._Lang.byId(form); 104 } 105 106 if (!form 107 || !form.nodeName 108 || form.nodeName.toLowerCase() != "form") { 109 throw new Error(this._Lang.getMessage("ERR_VIEWSTATE")); 110 } 111 112 var ajaxUtils = new myfaces._impl.xhrCore._AjaxUtils(0); 113 114 var ret = this._Lang.createFormDataDecorator([]); 115 ajaxUtils.encodeSubmittableFields(ret, null, null, null, form, null); 116 return ret.makeFinal(); 117 }, 118 119 /** 120 * this function has to send the ajax requests 121 * 122 * following request conditions must be met: 123 * <ul> 124 * <li> the request must be sent asynchronously! </li> 125 * <li> the request must be a POST!!! request </li> 126 * <li> the request url must be the form action attribute </li> 127 * <li> all requests must be queued with a client side request queue to ensure the request ordering!</li> 128 * </ul> 129 * 130 * @param {String|Node} elem any dom element no matter being it html or jsf, from which the event is emitted 131 * @param {|Event|} event any javascript event supported by that object 132 * @param {|Object|} options map of options being pushed into the ajax cycle 133 * 134 * 135 * TODO refactoring, the passthrgh handling is only for dragging in request parameters 136 * we should rewrite this part 137 * 138 * 139 * a) transformArguments out of the function 140 * b) passThrough handling with a map copy with a filter map block map 141 */ 142 request : function(elem, event, options) { 143 144 /*namespace remap for our local function context we mix the entire function namespace into 145 *a local function variable so that we do not have to write the entire namespace 146 *all the time 147 **/ 148 var _Lang = this._Lang; 149 var _Dom = this._Dom; 150 var getConfig = myfaces._impl.core._Runtime.getLocalOrGlobalConfig; 151 152 /*assert if the onerror is set and once if it is set it must be of type function*/ 153 _Lang.assertType(options.onerror, "function"); 154 /*assert if the onevent is set and once if it is set it must be of type function*/ 155 _Lang.assertType(options.onevent, "function"); 156 157 //options not set we define a default one with nothing 158 options = options || {}; 159 160 /*preparations for jsf 2.2 windowid handling*/ 161 //pass the window id into the options if not set already 162 if(!options.windowId) { 163 var windowId = _Dom.getWindowId(); 164 (windowId) ? options["javax.faces.windowId"] = windowId: null; 165 } else { 166 options["javax.faces.windowId"] = options.windowId; 167 delete options.windowId; 168 } 169 170 /** 171 * we cross reference statically hence the mapping here 172 * the entire mapping between the functions is stateless 173 */ 174 //null definitely means no event passed down so we skip the ie specific checks 175 if ('undefined' == typeof event) { 176 event = window.event || null; 177 } 178 179 elem = _Dom.byIdOrName(elem); 180 var elementId = _Dom.nodeIdOrName(elem); 181 182 /* 183 * We make a copy of our options because 184 * we should not touch the incoming params! 185 */ 186 var passThrgh = _Lang.mixMaps({}, options, true, this._BLOCKFILTER); 187 188 if (event) { 189 passThrgh[this.P_EVT] = event.type; 190 } 191 192 /** 193 * ajax pass through context with the source 194 * onevent and onerror 195 */ 196 var context = { 197 source: elem, 198 onevent: options.onevent, 199 onerror: options.onerror, 200 201 //TODO move the myfaces part into the _mfInternal part 202 myfaces: options.myfaces 203 }; 204 205 /** 206 * fetch the parent form 207 * 208 * note we also add an override possibility here 209 * so that people can use dummy forms and work 210 * with detached objects 211 */ 212 var form = (options.myfaces && options.myfaces.form)? 213 _Lang.byId(options.myfaces.form): 214 this._getForm(elem, event); 215 216 /** 217 * binding contract the javax.faces.source must be set 218 */ 219 passThrgh[this.P_PARTIAL_SOURCE] = elementId; 220 221 /** 222 * javax.faces.partial.ajax must be set to true 223 */ 224 passThrgh[this.P_AJAX] = true; 225 226 if (options.execute) { 227 /*the options must be a blank delimited list of strings*/ 228 /*compliance with Mojarra which automatically adds @this to an execute 229 * the spec rev 2.0a however states, if none is issued nothing at all should be sent down 230 */ 231 this._transformList(passThrgh, this.P_EXECUTE, options.execute + " @this", form, elementId); 232 } else { 233 passThrgh[this.P_EXECUTE] = elementId; 234 } 235 236 if (options.render) { 237 this._transformList(passThrgh, this.P_RENDER, options.render, form, elementId); 238 } 239 240 /** 241 * multiple transports upcoming jsf 2.1 feature currently allowed 242 * default (no value) xhrQueuedPost 243 * 244 * xhrQueuedPost 245 * xhrPost 246 * xhrGet 247 * xhrQueuedGet 248 * iframePost 249 * iframeQueuedPost 250 * 251 */ 252 var transportType = this._getTransportType(context, passThrgh, form); 253 254 //additional meta information to speed things up, note internal non jsf 255 //pass through options are stored under _mfInternal in the context 256 context._mfInternal = {}; 257 var mfInternal = context._mfInternal; 258 259 mfInternal["_mfSourceFormId"] = form.id; 260 mfInternal["_mfSourceControlId"] = elementId; 261 mfInternal["_mfTransportType"] = transportType; 262 263 //mojarra compatibility, mojarra is sending the form id as well 264 //this is not documented behavior but can be determined by running 265 //mojarra under blackbox conditions 266 //i assume it does the same as our formId_submit=1 so leaving it out 267 //wont hurt but for the sake of compatibility we are going to add it 268 passThrgh[form.id] = form.id; 269 270 this._transport[transportType](elem, form, context, passThrgh); 271 272 }, 273 274 /** 275 * fetches the form in an unprecise manner depending 276 * on an element or event target 277 * 278 * @param elem 279 * @param event 280 */ 281 _getForm: function(elem, event) { 282 var _Dom = this._Dom; 283 var _Lang = this._Lang; 284 var form = _Dom.fuzzyFormDetection(elem); 285 286 if (!form && event) { 287 //in case of no form is given we retry over the issuing event 288 form = _Dom.fuzzyFormDetection(_Lang.getEventTarget(event)); 289 if (!form) { 290 throw Error(_Lang.getMessage("ERR_FORM")); 291 } 292 } else if (!form) { 293 throw Error(_Lang.getMessage("ERR_FORM")); 294 } 295 return form; 296 }, 297 298 /** 299 * determines the transport type to be called 300 * for the ajax call 301 * 302 * @param context the context 303 * @param passThrgh pass through values 304 * @param form the form which issues the request 305 */ 306 _getTransportType: function(context, passThrgh, form) { 307 /** 308 * if execute or render exist 309 * we have to pass them down as a blank delimited string representation 310 * of an array of ids! 311 */ 312 //for now we turn off the transport auto selection, to enable 2.0 backwards compatibility 313 //on protocol level, the file upload only can be turned on if the auto selection is set to true 314 var getConfig = myfaces._impl.core._Runtime.getLocalOrGlobalConfig; 315 var _Lang = this._Lang; 316 var _Dom = this._Dom; 317 318 var transportAutoSelection = getConfig(context, "transportAutoSelection", false); 319 var isMultipart = (transportAutoSelection && _Dom.getAttribute(form, "enctype") == "multipart/form-data") ? 320 _Dom.isMultipartCandidate(passThrgh[this.P_EXECUTE]) : 321 false; 322 323 /** 324 * multiple transports upcoming jsf 2.1 feature currently allowed 325 * default (no value) xhrQueuedPost 326 * 327 * xhrQueuedPost 328 * xhrPost 329 * xhrGet 330 * xhrQueuedGet 331 * iframePost 332 * iframeQueuedPost 333 * 334 */ 335 var transportType = (!isMultipart) ? 336 getConfig(context, "transportType", "xhrQueuedPost") : 337 getConfig(context, "transportType", "multipartQueuedPost"); 338 if (!this._transport[transportType]) { 339 //throw new Error("Transport type " + transportType + " does not exist"); 340 throw new Error(_Lang.getMessage("ERR_TRANSPORT",null, transportType)); 341 } 342 return transportType; 343 344 }, 345 346 /** 347 * transforms the list to the expected one 348 * with the proper none all form and this handling 349 * (note we also could use a simple string replace but then 350 * we would have had double entries under some circumstances) 351 * 352 * @param passThrgh 353 * @param target 354 * @param srcStr 355 * @param form 356 * @param elementId 357 */ 358 _transformList: function(passThrgh, target, srcStr, form, elementId) { 359 var _Lang = this._Lang; 360 //this is probably the fastest transformation method 361 //it uses an array and an index to position all elements correctly 362 //the offset variable is there to prevent 0 which results in a javascript 363 //false 364 var offset = 1, 365 vals = (srcStr) ? srcStr.split(/\s+/) : [], 366 idIdx = (vals.length) ? _Lang.arrToMap(vals, offset) : {}, 367 368 //helpers to improve speed and compression 369 none = idIdx[this.IDENT_NONE], 370 all = idIdx[this.IDENT_ALL], 371 theThis = idIdx[this.IDENT_THIS], 372 theForm = idIdx[this.IDENT_FORM]; 373 374 if (none) { 375 //in case of none only one value is returned 376 passThrgh[target] = this.IDENT_NONE; 377 return passThrgh; 378 } 379 if (all) { 380 //in case of all only one value is returned 381 passThrgh[target] = this.IDENT_ALL; 382 return passThrgh; 383 } 384 385 if (theForm) { 386 //the form is replaced with the proper id but the other 387 //values are not touched 388 vals[theForm - offset] = form.id; 389 } 390 if (theThis && !idIdx[elementId]) { 391 //in case of this, the element id is set 392 vals[theThis - offset] = elementId; 393 } 394 395 //the final list must be blank separated 396 passThrgh[target] = vals.join(" "); 397 return passThrgh; 398 }, 399 400 addOnError : function(/*function*/errorListener) { 401 /*error handling already done in the assert of the queue*/ 402 this._errListeners.enqueue(errorListener); 403 }, 404 405 addOnEvent : function(/*function*/eventListener) { 406 /*error handling already done in the assert of the queue*/ 407 this._evtListeners.enqueue(eventListener); 408 }, 409 410 411 412 /** 413 * implementation triggering the error chain 414 * 415 * @param {Object} request the request object which comes from the xhr cycle 416 * @param {Object} context (Map) the context object being pushed over the xhr cycle keeping additional metadata 417 * @param {String} name the error name 418 * @param {String} serverErrorName the server error name in case of a server error 419 * @param {String} serverErrorMessage the server error message in case of a server error 420 * 421 * handles the errors, in case of an onError exists within the context the onError is called as local error handler 422 * the registered error handlers in the queue receiv an error message to be dealt with 423 * and if the projectStage is at development an alert box is displayed 424 * 425 * note: we have additional functionality here, via the global config myfaces.config.defaultErrorOutput a function can be provided 426 * which changes the default output behavior from alert to something else 427 * 428 * 429 */ 430 sendError : function sendError(/*Object*/request, /*Object*/ context, /*String*/ name, /*String*/ serverErrorName, /*String*/ serverErrorMessage) { 431 var _Lang = myfaces._impl._util._Lang; 432 var UNKNOWN = _Lang.getMessage("UNKNOWN"); 433 434 var eventData = {}; 435 //we keep this in a closure because we might reuse it for our serverErrorMessage 436 var malFormedMessage = function() { 437 return (name && name === myfaces._impl.core.Impl.MALFORMEDXML) ? _Lang.getMessage("ERR_MALFORMEDXML") : ""; 438 }; 439 440 441 442 //by setting unknown values to unknown we can handle cases 443 //better where a simulated context is pushed into the system 444 eventData.type = this.ERROR; 445 446 eventData.status = name || UNKNOWN; 447 eventData.serverErrorName = serverErrorName || UNKNOWN; 448 eventData.serverErrorMessage = serverErrorMessage || UNKNOWN; 449 450 try { 451 eventData.source = context.source || UNKNOWN; 452 eventData.responseCode = request.status || UNKNOWN; 453 eventData.responseText = request.responseText || UNKNOWN; 454 eventData.responseXML = request.responseXML || UNKNOWN; 455 } catch (e) { 456 // silently ignore: user can find out by examining the event data 457 } 458 459 /**/ 460 if (context["onerror"]) { 461 context.onerror(eventData); 462 } 463 464 /*now we serve the queue as well*/ 465 this._errListeners.broadcastEvent(eventData); 466 467 if (jsf.getProjectStage() === "Development" && this._errListeners.length() == 0) { 468 var defaultErrorOutput = myfaces._impl.core._Runtime.getGlobalConfig("defaultErrorOutput", alert); 469 var finalMessage = []; 470 471 finalMessage.push((name) ? name : ""); 472 finalMessage.push((serverErrorName) ? serverErrorName : ""); 473 finalMessage.push((serverErrorMessage) ? serverErrorMessage : ""); 474 finalMessage.push(malFormedMessage()); 475 finalMessage.push("\n\n"); 476 finalMessage.push( _Lang.getMessage("MSG_DEV_MODE")); 477 defaultErrorOutput(finalMessage.join("")); 478 } 479 }, 480 481 /** 482 * sends an event 483 */ 484 sendEvent : function sendEvent(/*Object*/request, /*Object*/ context, /*event name*/ name) { 485 var _Lang = myfaces._impl._util._Lang; 486 var eventData = {}; 487 var UNKNOWN = _Lang.getMessage("UNKNOWN"); 488 489 eventData.type = this.EVENT; 490 491 eventData.status = name; 492 eventData.source = context.source; 493 494 495 if (name !== this.BEGIN) { 496 497 try { 498 //we bypass a problem with ie here, ie throws an exception if no status is given on the xhr object instead of just passing a value 499 var getValue = function(value, key) { 500 try { 501 return value[key] 502 } catch (e) { 503 return UNKNOWN; 504 } 505 }; 506 507 eventData.responseCode = getValue(request, "status"); 508 eventData.responseText = getValue(request, "responseText"); 509 eventData.responseXML = getValue(request, "responseXML"); 510 511 } catch (e) { 512 var impl = myfaces._impl.core._Runtime.getGlobalConfig("jsfAjaxImpl", myfaces._impl.core.Impl); 513 impl.sendError(request, context, this.CLIENT_ERROR, "ErrorRetrievingResponse", 514 _Lang.getMessage("ERR_CONSTRUCT", e.toString())); 515 516 //client errors are not swallowed 517 throw e; 518 } 519 520 } 521 522 /**/ 523 if (context.onevent) { 524 /*calling null to preserve the original scope*/ 525 context.onevent.call(null, eventData); 526 } 527 528 /*now we serve the queue as well*/ 529 this._evtListeners.broadcastEvent(eventData); 530 }, 531 532 /** 533 * processes the ajax response if the ajax request completes successfully 534 * this is the case for non queued outside calls which are triggered by calling response 535 * themselves and hence the case according to the spec 536 * 537 * @param {Object} request (xhrRequest) the ajax request! 538 * @param {Object} context (Map) context map keeping context data not being passed down over 539 * the request boundary but kept on the client 540 */ 541 response : function(request, context) { 542 this._transport.response(request, context); 543 }, 544 545 /** 546 * @return the project stage also emitted by the server: 547 * it cannot be cached and must be delivered over the server 548 * The value for it comes from the request parameter of the jsf.js script called "stage". 549 */ 550 getProjectStage : function() { 551 /* run through all script tags and try to find the one that includes jsf.js */ 552 var scriptTags = document.getElementsByTagName("script"); 553 var getConfig = myfaces._impl.core._Runtime.getGlobalConfig; 554 555 for (var i = 0; i < scriptTags.length; i++) { 556 if (scriptTags[i].src.search(/\/javax\.faces\.resource\/jsf\.js.*ln=javax\.faces/) != -1) { 557 var result = scriptTags[i].src.match(/stage=([^&;]*)/); 558 if (result) { 559 // we found stage=XXX 560 // return only valid values of ProjectStage 561 if ( result[1] == "Production" 562 || result[1] == "Development" 563 || result[1] == "SystemTest" 564 || result[1] == "UnitTest") { 565 return result[1]; 566 } 567 } 568 else { 569 //we found the script, but there was no stage parameter -- Production 570 //(we also add an override here for testing purposes, the default, however is Production) 571 return getConfig("projectStage", "Production"); 572 //return "Production"; 573 } 574 } 575 } 576 /* we could not find anything valid --> return the default value */ 577 return getConfig("projectStage", "Production"); 578 }, 579 580 /** 581 * implementation of the external chain function 582 * moved into the impl 583 * 584 * @param {Object} source the source which also becomes 585 * the scope for the calling function (unspecified side behavior) 586 * the spec states here that the source can be any arbitrary code block. 587 * Which means it either is a javascript function directly passed or a code block 588 * which has to be evaluated separately. 589 * 590 * After revisiting the code additional testing against components showed that 591 * the this parameter is only targeted at the component triggering the eval 592 * (event) if a string code block is passed. This is behavior we have to resemble 593 * in our function here as well, I guess. 594 * 595 * @param {Event} event the event object being passed down into the the chain as event origin 596 * the spec is contradicting here, it on one hand defines event, and on the other 597 * it says it is optional, after asking, it meant that event must be passed down 598 * but can be undefined 599 */ 600 chain : function(source, event) { 601 var len = arguments.length; 602 var _Lang = this._Lang; 603 604 //the spec is contradicting here, it on one hand defines event, and on the other 605 //it says it is optional, I have cleared this up now 606 //the spec meant the param must be passed down, but can be 'undefined' 607 if (len < 2) { 608 throw new Error(_Lang.getMessage("ERR_EV_OR_UNKNOWN")); 609 } else if (len < 3) { 610 if ('function' == typeof event || this._Lang.isString(event)) { 611 612 throw new Error(_Lang.getMessage("ERR_EVT_PASS")); 613 } 614 //nothing to be done here, move along 615 return true; 616 } 617 //now we fetch from what is given from the parameter list 618 //we cannot work with splice here in any performant way so we do it the hard way 619 //arguments only are give if not set to undefined even null values! 620 621 //assertions source either null or set as dom element: 622 623 if ('undefined' == typeof source) { 624 throw new Error(_Lang.getMessage("ERR_SOURCE_DEF_NULL")); 625 //allowed chain datatypes 626 } else if ('function' == typeof source) { 627 throw new Error(_Lang.getMessage("ERR_SOURCE_FUNC")); 628 } 629 if (this._Lang.isString(source)) { 630 throw new Error(_Lang.getMessage("ERR_SOURCE_NOSTR")); 631 } 632 633 //assertion if event is a function or a string we already are in our function elements 634 //since event either is undefined, null or a valid event object 635 636 if ('function' == typeof event || this._Lang.isString(event)) { 637 throw new Error(_Lang.getMessage("ERR_EV_OR_UNKNOWN")); 638 } 639 640 for (var cnt = 2; cnt < len; cnt++) { 641 //we do not change the scope of the incoming functions 642 //but we reuse the argument array capabilities of apply 643 var ret; 644 645 if ('function' == typeof arguments[cnt]) { 646 ret = arguments[cnt].call(source, event); 647 } else { 648 //either a function or a string can be passed in case of a string we have to wrap it into another function 649 ret = new Function("event", arguments[cnt]).call(source, event); 650 } 651 //now if one function returns false in between we stop the execution of the cycle 652 //here, note we do a strong comparison here to avoid constructs like 'false' or null triggering 653 if (ret === false /*undefined check implicitly done here by using a strong compare*/) { 654 return false; 655 } 656 } 657 return true; 658 659 }, 660 661 /** 662 * error handler behavior called internally 663 * and only into the impl it takes care of the 664 * internal message transformation to a myfaces internal error 665 * and then uses the standard send error mechanisms 666 * also a double error logging prevention is done as well 667 * 668 * @param request the request currently being processed 669 * @param context the context affected by this error 670 * @param sourceClass the sourceclass throwing the error 671 * @param func the function throwing the error 672 * @param exception the exception being thrown 673 */ 674 stdErrorHandler: function(request, context, sourceClass, func, exception) { 675 676 var _Lang = myfaces._impl._util._Lang; 677 var exProcessed = _Lang.isExceptionProcessed(exception); 678 try { 679 //newer browsers do not allow to hold additional values on native objects like exceptions 680 //we hence capsule it into the request, which is gced automatically 681 //on ie as well, since the stdErrorHandler usually is called between requests 682 //this is a valid approach 683 684 if (this._threshold == "ERROR" && !exProcessed) { 685 this.sendError(request, context, this.CLIENT_ERROR, exception.name, 686 "MyFaces ERROR:" + this._Lang.createErrorMsg(sourceClass, func, exception)); 687 } 688 } finally { 689 690 //we forward the exception, just in case so that the client 691 //will receive it in any way 692 try { 693 if (!exProcessed) { 694 _Lang.setExceptionProcessed(exception); 695 } 696 } catch(e) { 697 698 } 699 throw exception; 700 } 701 } 702 }); 703