All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cds.adapter.odata.v2.utils.MessagesUtils Maven / Gradle / Ivy

There is a newer version: 3.6.0
Show newest version
/**************************************************************************
 * (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
 **************************************************************************/
package com.sap.cds.adapter.odata.v2.utils;

import static com.fasterxml.jackson.core.json.JsonWriteFeature.ESCAPE_NON_ASCII;
import static java.util.stream.Collectors.toList;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.apache.olingo.odata2.core.commons.ContentType;
import org.apache.olingo.odata2.core.commons.XmlHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonFactoryBuilder;
import com.fasterxml.jackson.core.JsonGenerator;
import com.sap.cds.ql.cqn.CqnComparisonPredicate;
import com.sap.cds.ql.cqn.CqnComparisonPredicate.Operator;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnLiteral;
import com.sap.cds.ql.cqn.CqnNullValue;
import com.sap.cds.ql.cqn.CqnPlain;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.cqn.CqnVisitor;
import com.sap.cds.services.messages.Message;
import com.sap.cds.services.messages.MessageTarget;
import com.sap.cds.services.messages.Messages;
import com.sap.cds.services.utils.StringUtils;

public class MessagesUtils {

	private static final Logger logger = LoggerFactory.getLogger(MessagesUtils.class);
	private static final String CODE = "code";
	private static final String DETAILS = "details";
	private static final String MESSAGE = "message";
	private static final String SEVERITY = "severity";
	private static final String TARGET = "target";
	private static final String ENTRY = "entry";
	private static final String FEED = "feed";

	public static String getSapMessagesHeader(Messages messages, String contentType) {
		ContentType ct = ContentType.parse(contentType);
		String actualContentType = ct.getType() + "/" + ct.getSubtype();
		if (actualContentType.equalsIgnoreCase(ContentType.APPLICATION_JSON.toContentTypeString())) {
			return getSapMessagesHeaderJson(messages);
		} else if (actualContentType.equalsIgnoreCase(ContentType.APPLICATION_ATOM_XML.toContentTypeString()) ||
				actualContentType.equalsIgnoreCase(ContentType.APPLICATION_XML.toContentTypeString())) {
			return getSapMessagesHeadersXML(messages);
		}
		return null;
	}

	private static String getSapMessagesHeadersXML(Messages messages) {
		if (messages.stream().count() > 0) {
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			try {
				XMLStreamWriter writer = XmlHelper.getXMLOutputFactory().createXMLStreamWriter(outputStream, "utf-8");
				Iterator iterator = messages.stream().iterator();
				boolean isFeed = messages.stream().count() > 1;
				if (isFeed) {
					writer.writeStartElement(FEED);
				}
				while (iterator.hasNext()) {
					Message message = iterator.next();
					writer.writeStartElement(ENTRY);
					// mandatory
					writer.writeStartElement(CODE);
					writer.writeCharacters(getMessageCode(message));
					writer.writeEndElement();
					writer.writeStartElement(MESSAGE);
					writer.writeCharacters(message.getMessage());
					writer.writeEndElement();
					writer.writeStartElement(SEVERITY);
					writer.writeCharacters(String.valueOf(message.getSeverity().toString().toLowerCase(Locale.US)));
					writer.writeEndElement();
					// optional
					String target = getTarget(message.getTarget());
					if(target != null) {
						writer.writeStartElement(TARGET);
						writer.writeCharacters(target);
						writer.writeEndElement();
					}
					writer.writeEndElement();
				}
				if (isFeed) {
					writer.writeEndElement();
				}
				writer.close();
				if (outputStream.size() != 0) {
					return outputStream.toString("UTF-8");
				}
			} catch (XMLStreamException | UnsupportedEncodingException e) {
				logger.error("Failed to create sap messages header", e);
			}
		}
		return null;
	}

	private static void writeJsonMessageBody(Message message, JsonGenerator json) throws IOException {
		// mandatory
		json.writeStringField(CODE, getMessageCode(message));
		json.writeStringField(MESSAGE, message.getMessage());
		json.writeStringField(SEVERITY, message.getSeverity().toString().toLowerCase(Locale.US));
		// optional
		String target = getTarget(message.getTarget());
		if (target != null) {
			json.writeStringField(TARGET, target);
		}
	}

	private static String getSapMessagesHeaderJson(Messages messages) {
		List msgList = messages.stream().collect(toList());
		if (msgList.size() > 0) {
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			JsonFactoryBuilder builder = new JsonFactoryBuilder().configure(ESCAPE_NON_ASCII, true);
			try (JsonGenerator json = new JsonFactory(builder).createGenerator(outputStream)) {
				json.writeStartObject();
				Message leadingMessage = msgList.remove(0);
				writeJsonMessageBody(leadingMessage, json);

				boolean isFirst = true;
				Iterator iterator = msgList.iterator();

				if (iterator.hasNext()) {
					json.writeFieldName(DETAILS);
					while (iterator.hasNext()) {
						if (isFirst) {
							json.writeStartArray();
							isFirst = false;
						}
						Message detailMessage = iterator.next();
						json.writeStartObject();
						writeJsonMessageBody(detailMessage, json);
						json.writeEndObject();
					}
					if (!isFirst) {
						json.writeEndArray();
					}
				}

				json.writeEndObject();
				json.close();
				if (outputStream.size() != 0) {
					return outputStream.toString(StandardCharsets.UTF_8);
				}

			} catch (IOException e) {
				logger.error("Failed to create sap messages header", e);
			}
		}
		return null;
	}

	public static String getMessageCode(Message message) {
		return StringUtils.isEmpty(message.getCode()) ? "" : message.getCode();
	}

	// TODO: this method does not handle all conversions of target to OData V2 notation correctly
	// (for example: entity with date keys)
	public static String getTarget(MessageTarget messageTarget) {
		if(messageTarget == null) {
			return null;
		}

		List segments = messageTarget.getRef() == null ? Collections.emptyList()
				: messageTarget.getRef().segments();

		String prefix = messageTarget.getParameter();
		if(prefix == null && segments.isEmpty()) {
			return null;
		}
		if (MessageTarget.PARAMETER_CQN.equals(prefix)) {
			prefix = null;
		}

		StringBuilder target = new StringBuilder(StringUtils.isEmpty(prefix) ? "" : prefix);

		if(!segments.isEmpty() && target.length() > 0 && target.charAt(target.length() - 1) != '/') {
			target.append("/");
		}

		for(Segment segment : segments) {
			target.append(segment.id());
			if(segment.filter().isPresent()) {
				target.append("(");
				AtomicInteger beforeIsActiveEntityPosition = new AtomicInteger(-1);
				segment.filter().get().accept(new CqnVisitor() {

					@Override
					public void visit(CqnComparisonPredicate predicate) {
						if (predicate.operator() == Operator.EQ || predicate.operator() == Operator.IS) {
							CqnValue left = predicate.left();
							CqnValue right = predicate.right();
							CqnValue value = null;
							String key = null;
							if (left instanceof CqnElementRef ref) {
								key = ref.firstSegment();
								value = right;
							} else if (right instanceof CqnElementRef ref) {
								key = ref.firstSegment();
								value = left;
							}
							if(key != null) {
								Object theValue = null;
								if(value instanceof CqnLiteral literal) {
									theValue = literal.value();
									if(theValue instanceof String string) {
										if (isUUID(string)) {
											theValue = "guid" + "'" + theValue + "'";
										} else {
											theValue = "'" + theValue + "'";
										}
									}
								} else if(value instanceof CqnNullValue) {
									theValue = "null";
								} else if(value instanceof CqnPlain plain) {
									theValue = plain.plain();
								}
								if(theValue != null) {
									String toAppend = key + "=" + theValue + ",";
									// TODO dirty workaround for Fiori requiring certain key order
									// will only work for single key or single key draft enabled entities
									int insertAt = beforeIsActiveEntityPosition.get();
									if(insertAt > -1) {
										target.insert(insertAt, toAppend);
										beforeIsActiveEntityPosition.set(insertAt + toAppend.length());
									} else {
										if(key.equals("IsActiveEntity")) {
											beforeIsActiveEntityPosition.set(target.length());
										}
										target.append(toAppend);
									}
								}
							}
						}
					}

				});
				target.deleteCharAt(target.length() - 1);
				target.append(")");
			}
			target.append("/");
		}

		if(!segments.isEmpty()) {
			target.deleteCharAt(target.length() - 1);
		}

		return target.toString();
	}

	// TODO this check should actually be done against the type in the model
	private static boolean isUUID(String s) {
		try {
			UUID.fromString(s);
			return true;
		} catch (IllegalArgumentException e) { // NOSONAR
			return false;
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy