fiftyone.pipeline.javascriptbuilder.flowelements.JavaScriptBuilderElement Maven / Gradle / Ivy
The newest version!
/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2023 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* This Original Work is licensed under the European Union Public Licence
* (EUPL) v.1.2 and is subject to its terms as set out below.
*
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
*
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
*
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* */
package fiftyone.pipeline.javascriptbuilder.flowelements;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import fiftyone.pipeline.core.data.*;
import fiftyone.pipeline.core.data.factories.ElementDataFactory;
import fiftyone.pipeline.core.exceptions.PipelineConfigurationException;
import fiftyone.pipeline.core.exceptions.PipelineDataException;
import fiftyone.pipeline.core.flowelements.FlowElementBase;
import fiftyone.pipeline.engines.data.AspectPropertyValue;
import fiftyone.pipeline.engines.exceptions.PropertyMissingException;
import fiftyone.pipeline.javascriptbuilder.Constants;
import fiftyone.pipeline.javascriptbuilder.data.JavaScriptBuilderData;
import fiftyone.pipeline.javascriptbuilder.templates.JavaScriptResource;
import fiftyone.pipeline.jsonbuilder.data.JsonBuilderData;
import org.json.JSONObject;
import org.slf4j.Logger;
import java.io.*;
import java.net.URLEncoder;
import java.util.*;
import static fiftyone.pipeline.core.Constants.*;
import static fiftyone.pipeline.engines.fiftyone.flowelements.Constants.EVIDENCE_SEQUENCE;
import static fiftyone.pipeline.engines.fiftyone.flowelements.Constants.EVIDENCE_SESSIONID;
import static fiftyone.pipeline.javascriptbuilder.Constants.EVIDENCE_ENABLE_COOKIES;
import static fiftyone.pipeline.javascriptbuilder.Constants.EVIDENCE_OBJECT_NAME;
//! [class]
/**
* JavaScript Builder Element generates a JavaScript include to be run on the
* client device.
* @see Specification
*/
public class JavaScriptBuilderElement
extends FlowElementBase {
protected String host;
protected String endpoint;
protected String protocol;
protected String contextRoot;
protected final String objName;
protected final boolean enableCookies;
private final Mustache mustache;
//! [constructor]
/**
* Default constructor.
* @param logger The logger.
* @param elementDataFactory The element data factory.
* @param endpoint Set the endpoint which will be queried on the host.
* e.g /api/v4/json
* @param objName The default name of the object instantiated by the client
* JavaScript.
* @param enableCookies Set whether the client JavaScript stored results of
* client side processing in cookies.
* @param host The host that the client JavaScript should query for updates.
* If null or blank then the host from the request will be used
* @param protocol The protocol (HTTP or HTTPS) that the client JavaScript
* will use when querying for updates. If null or blank
* then the protocol from the request will be used
*/
public JavaScriptBuilderElement(
Logger logger,
ElementDataFactory elementDataFactory,
String endpoint,
String objName,
boolean enableCookies,
String host,
String protocol) {
this(logger, elementDataFactory, endpoint, objName, enableCookies, host, protocol, null);
}
//! [constructor]
//! [constructor]
/**
* Default constructor.
* @param logger The logger.
* @param elementDataFactory The element data factory.
* @param endpoint Set the endpoint which will be queried on the host.
* e.g /api/v4/json
* @param objName The default name of the object instantiated by the client
* JavaScript.
* @param enableCookies Set whether the client JavaScript stored results of
* client side processing in cookies.
* @param host The host that the client JavaScript should query for updates.
* If null or blank then the host from the request will be used
* @param protocol The protocol (HTTP or HTTPS) that the client JavaScript
* will use when querying for updates. If null or blank
* then the protocol from the request will be used
* @param contextRoot The <context-root> setting from the web.xml.
* This is needed when creating the callback URL.
*/
public JavaScriptBuilderElement(
Logger logger,
ElementDataFactory elementDataFactory,
String endpoint,
String objName,
boolean enableCookies,
String host,
String protocol,
String contextRoot) {
super(logger, elementDataFactory);
MustacheFactory mf = new DefaultMustacheFactory();
InputStream in = getClass().getResourceAsStream(Constants.TEMPLATE);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
mustache = mf.compile(reader, "template");
this.host = host;
this.endpoint = endpoint;
this.protocol = protocol;
this.objName = objName.isEmpty() ? Constants.DEFAULT_OBJECT_NAME : objName;
this.enableCookies = enableCookies;
this.contextRoot = contextRoot;
}
//! [constructor]
@Override
protected void processInternal(FlowData data) throws Exception {
String reqHost = getHost(data);
String reqProtocol = getProtocol(data);
boolean supportsPromises = getSupportsPromises(data);
// Try and get the web server context root evidence so it can be
// used to construct the correct path for the Json refresh.
if (contextRoot == null || contextRoot.isEmpty()) {
contextRoot = getContextRoot(data);
}
// Get the JSON include to embed into the JavaScript include.
String jsonObject = getJsonObject(data);
// Generate any required parameters for the JSON request.
Map parameters = getParameters(data);
String queryParams = getQueryParams(parameters);
String url = getUrl(reqProtocol, reqHost, queryParams);
String sessionId = getSessionId(data);
Integer sequence = getSequence(data);
String serializedParameters = serializeParameters(parameters);
// With the gathered resources, build a new JavaScriptResource.
buildJavaScript(data, jsonObject, supportsPromises, url, serializedParameters, sessionId, sequence);
}
@Override
public String getElementDataKey() {
return "javascript-builder";
}
@Override
public EvidenceKeyFilter getEvidenceKeyFilter() {
return new EvidenceKeyFilterWhitelist(Arrays.asList(
Constants.EVIDENCE_HOST_KEY,
fiftyone.pipeline.core.Constants.EVIDENCE_PROTOCOL,
EVIDENCE_OBJECT_NAME,
EVIDENCE_ENABLE_COOKIES,
EVIDENCE_WEB_CONTEXT_ROOT),
String.CASE_INSENSITIVE_ORDER);
}
@Override
public List getProperties() {
return Collections.singletonList(
new ElementPropertyMetaDataDefault(
"javascript",
this,
"javascript",
String.class,
true));
}
@Override
protected void managedResourcesCleanup() {
// Nothing to clean up here.
}
@Override
protected void unmanagedResourcesCleanup() {
// Nothing to clean up here.
}
private String getHost(FlowData data) {
String reqHost = this.host;
// Try and get the request host name so it can be used to request
// the Json refresh in the JavaScript code.
if (reqHost == null || reqHost.isEmpty()) {
TryGetResult hostEvidence = data.tryGetEvidence(
Constants.EVIDENCE_HOST_KEY,
String.class);
if (hostEvidence.hasValue()) {
reqHost = hostEvidence.getValue();
}
}
return reqHost;
}
private String getProtocol(FlowData data) {
String reqProtocol = this.protocol;
// Try and get the request protocol so it can be used to request
// the JSON refresh in the JavaScript code.
if (reqProtocol == null || reqProtocol.isEmpty()) {
TryGetResult protocolEvidence = data.tryGetEvidence(
fiftyone.pipeline.core.Constants.EVIDENCE_PROTOCOL,
String.class);
if (protocolEvidence.hasValue()) {
reqProtocol = protocolEvidence.getValue();
} else {
// Couldn't get protocol from anywhere
reqProtocol = Constants.DEFAULT_PROTOCOL;
}
}
return reqProtocol;
}
private boolean getSupportsPromises(FlowData data) {
boolean supportsPromises;
// If device detection is enabled then try and get whether the
// requesting browser supports promises. If not then default to false.
try {
AspectPropertyValue> supportsPromisesValue =
data.getAs("Promise", AspectPropertyValue.class);
supportsPromises = supportsPromisesValue.hasValue() &&
supportsPromisesValue.getValue() == "Full";
} catch (PipelineDataException | PropertyMissingException e) {
supportsPromises = false;
}
return supportsPromises;
}
private String getContextRoot(FlowData data) {
String root = null;
// Try and get the web server context root evidence so it can be
// used to construct the correct path for the Json refresh.
TryGetResult contextRoot = data.tryGetEvidence(
EVIDENCE_WEB_CONTEXT_ROOT,
String.class);
if (contextRoot.hasValue()) {
root = contextRoot.getValue();
}
return root;
}
private String getJsonObject(FlowData data) {
String jsonObject;
try {
jsonObject = data.get(JsonBuilderData.class).getJson();
} catch (PipelineDataException e){
throw new PipelineConfigurationException("Json data is missing,"
+ " make sure there is a JsonBuilder element before this"
+ " JavaScriptBuilderElement in the pipeline", e);
}
return jsonObject;
}
private Map getParameters(FlowData data) throws UnsupportedEncodingException {
HashMap parameters = new HashMap<>();
Map queryEvidence = data
.getEvidence()
.asKeyMap();
for(Map.Entry entry : queryEvidence.entrySet()){
if(entry.getKey().startsWith(EVIDENCE_QUERY_PREFIX)){
String key = entry.getKey().substring(entry.getKey().indexOf(EVIDENCE_SEPERATOR) + 1);
key = URLEncoder.encode(key, "UTF-8");
String value = URLEncoder.encode(entry.getValue().toString(), "UTF-8");
parameters.put(key, value);
}
}
return parameters;
}
private String getQueryParams(Map parameters) throws UnsupportedEncodingException {
StringBuilder sb = new StringBuilder();
Set keys = parameters.keySet();
for (String key : keys) {
sb.append(key);
sb.append("=");
sb.append(parameters.get(key));
sb.append("&");
}
sb.deleteCharAt(sb.lastIndexOf("&"));
return sb.toString();
}
private String serializeParameters(Map parameters) {
JSONObject jsonObject = new JSONObject(parameters);
return jsonObject.toString(0);
}
private String getUrl(String protocol, String host, String queryParams) {
String url = null;
if (protocol != null && !protocol.isEmpty() &&
host != null && !host.isEmpty() &&
endpoint != null && !endpoint.isEmpty()) {
boolean contextRootPopulated = contextRoot != null &&
!contextRoot.isEmpty() && !contextRoot.equals("/");
// Make sure that each part of the URL except host starts with a '/'
// and each part except endpoint does NOT end with one.
if (endpoint.charAt(0) != '/') {
endpoint = "/" + endpoint;
}
if (contextRootPopulated && contextRoot.charAt(0) != '/') {
contextRoot = "/" + contextRoot;
}
if (host.charAt(host.length() - 1) == '/') {
host = host.substring(0, host.length() - 1);
}
if (contextRootPopulated && contextRoot.charAt(contextRoot.length() - 1) == '/') {
contextRoot = contextRoot.substring(0, contextRoot.length() - 1);
}
url = protocol + "://" + host +
(contextRootPopulated ? contextRoot : "") +
endpoint +
(queryParams.isEmpty() ? "" : "?" + queryParams);
}
return url;
}
private String getSessionId(FlowData data) {
String sessionId = "";
TryGetResult trySessionId = data.tryGetEvidence(EVIDENCE_SESSIONID, String.class);
if (trySessionId.hasValue()) {
sessionId = trySessionId.getValue();
}
return sessionId;
}
private Integer getSequence(FlowData data) {
Integer sequence = 1;
TryGetResult trySequence = data.tryGetEvidence(EVIDENCE_SEQUENCE, Integer.class);
if (trySequence.hasValue()) {
sequence = trySequence.getValue();
}
return sequence;
}
private void buildJavaScript(
FlowData data,
String jsonObject,
boolean supportsPromises,
String url,
String parameters,
String sessionId,
int sequence) {
JavaScriptBuilderDataInternal elementData =
(JavaScriptBuilderDataInternal)data.getOrAdd(
getElementDataKey(),
getDataFactory());
String objectName;
// Try and get the requested object name from evidence.
TryGetResult res = data.tryGetEvidence(
EVIDENCE_OBJECT_NAME,
String.class );
if (res.hasValue() == false ||
res.getValue().isEmpty()) {
objectName = objName;
} else {
objectName = res.getValue();
}
boolean cookies;
// Try and get the requested enable cookies from evidence.
TryGetResult cookieVal = data.tryGetEvidence(
EVIDENCE_ENABLE_COOKIES,
String.class);
if (cookieVal.hasValue() == false ||
cookieVal.getValue().isEmpty()) {
cookies = enableCookies;
} else {
cookies = Boolean.parseBoolean(cookieVal.getValue());
}
boolean updateEnabled = url != null && url.isEmpty() == false;
// This check won't be 100% fool-proof but it only needs to be
// reasonably accurate and not take too long.
boolean hasDelayedProperties = jsonObject != null &&
jsonObject.contains("delayexecution");
JavaScriptResource javaScriptObj = new JavaScriptResource(
objectName,
jsonObject,
sessionId,
sequence,
supportsPromises,
url,
parameters,
cookies,
updateEnabled,
hasDelayedProperties);
StringWriter stringWriter = new StringWriter();
mustache.execute(stringWriter, javaScriptObj.asMap());
String content = stringWriter.toString();
elementData.setJavaScript(content);
}
}
//! [class]
© 2015 - 2025 Weber Informatics LLC | Privacy Policy