import java.util.HashMap;
import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
import java.util.Queue;
import javax.persistence.EntityManager;
import org.onap.cps.spi.entities.AnchorEntity;
import org.onap.cps.spi.entities.DataspaceEntity;
import org.onap.cps.spi.entities.FragmentEntity;
-import org.onap.cps.utils.JsonObjectMapper;
+import org.onap.cps.spi.exceptions.CpsPathException;
+import org.onap.cps.spi.utils.EscapeUtils;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@Slf4j
@Component
public class FragmentQueryBuilder {
- private static final String REGEX_ABSOLUTE_PATH_PREFIX = "^";
- private static final String REGEX_DESCENDANT_PATH_PREFIX = "^.*\\/";
- private static final String REGEX_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[@(?!.*\\[).*?])?$";
private static final AnchorEntity ACROSS_ALL_ANCHORS = null;
@PersistenceContext
private EntityManager entityManager;
- private final JsonObjectMapper jsonObjectMapper;
-
/**
* Create a sql query to retrieve by anchor(id) and cps path.
*
return getQueryForDataspaceOrAnchorAndCpsPath(dataspaceEntity, ACROSS_ALL_ANCHORS, cpsPathQuery);
}
- private static String getXpathSqlRegex(final CpsPathQuery cpsPathQuery) {
- final StringBuilder xpathRegexBuilder = getRegexStringBuilderWithPrefix(cpsPathQuery);
- xpathRegexBuilder.append(REGEX_OPTIONAL_LIST_INDEX_POSTFIX);
- return xpathRegexBuilder.toString();
- }
-
private Query getQueryForDataspaceOrAnchorAndCpsPath(final DataspaceEntity dataspaceEntity,
final AnchorEntity anchorEntity,
final CpsPathQuery cpsPathQuery) {
private static void addXpathSearch(final CpsPathQuery cpsPathQuery,
final StringBuilder sqlStringBuilder,
final Map<String, Object> queryParameters) {
- sqlStringBuilder.append(" AND xpath ~ :xpathRegex");
- final String xpathRegex = getXpathSqlRegex(cpsPathQuery);
- queryParameters.put("xpathRegex", xpathRegex);
- }
-
- private static StringBuilder getRegexStringBuilderWithPrefix(final CpsPathQuery cpsPathQuery) {
- final StringBuilder xpathRegexBuilder = new StringBuilder();
+ sqlStringBuilder.append(" AND (xpath LIKE :escapedXpath OR "
+ + "(xpath LIKE :escapedXpath||'[@%]' AND xpath NOT LIKE :escapedXpath||'[@%]/%[@%]'))");
if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) {
- xpathRegexBuilder.append(REGEX_ABSOLUTE_PATH_PREFIX);
- xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getXpathPrefix()));
- return xpathRegexBuilder;
+ queryParameters.put("escapedXpath", EscapeUtils.escapeForSqlLike(cpsPathQuery.getXpathPrefix()));
+ } else {
+ queryParameters.put("escapedXpath", "%/" + EscapeUtils.escapeForSqlLike(cpsPathQuery.getDescendantName()));
}
- xpathRegexBuilder.append(REGEX_DESCENDANT_PATH_PREFIX);
- xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getDescendantName()));
- return xpathRegexBuilder;
- }
-
- private static String escapeXpath(final String xpath) {
- // See https://jira.onap.org/browse/CPS-500 for limitations of this basic escape mechanism
- return xpath.replace("[@", "\\[@");
}
private static Integer getTextValueAsInt(final CpsPathQuery cpsPathQuery) {
private void addLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) {
if (cpsPathQuery.hasLeafConditions()) {
- sqlStringBuilder.append(" AND (");
- final List<String> queryBooleanOperatorsType = cpsPathQuery.getBooleanOperatorsType();
- final Queue<String> booleanOperatorsQueue = (queryBooleanOperatorsType == null) ? null : new LinkedList<>(
- queryBooleanOperatorsType);
- cpsPathQuery.getLeavesData().entrySet().forEach(entry -> {
- sqlStringBuilder.append(" attributes @> ");
- sqlStringBuilder.append("'");
- sqlStringBuilder.append(jsonObjectMapper.asJsonString(entry));
- sqlStringBuilder.append("'");
- if (!(booleanOperatorsQueue == null || booleanOperatorsQueue.isEmpty())) {
- sqlStringBuilder.append(" ");
- sqlStringBuilder.append(booleanOperatorsQueue.poll());
- sqlStringBuilder.append(" ");
- }
- });
- sqlStringBuilder.append(")");
+ queryLeafConditions(cpsPathQuery, sqlStringBuilder);
}
}
+ private void queryLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) {
+ sqlStringBuilder.append(" AND (");
+ final Queue<String> booleanOperatorsQueue = new LinkedList<>(cpsPathQuery.getBooleanOperators());
+ final Queue<String> comparativeOperatorQueue = new LinkedList<>(cpsPathQuery.getComparativeOperators());
+ cpsPathQuery.getLeavesData().forEach(leaf -> {
+ final String nextComparativeOperator = comparativeOperatorQueue.poll();
+ if (leaf.getValue() instanceof Integer) {
+ sqlStringBuilder.append("(attributes ->> '").append(leaf.getName()).append("')\\:\\:int");
+ sqlStringBuilder.append(nextComparativeOperator);
+ sqlStringBuilder.append(leaf.getValue());
+ } else {
+ if ("=".equals(nextComparativeOperator)) {
+ final String leafValueAsText = leaf.getValue().toString();
+ sqlStringBuilder.append("attributes ->> '").append(leaf.getName()).append("'");
+ sqlStringBuilder.append(" = '");
+ sqlStringBuilder.append(EscapeUtils.escapeForSqlStringLiteral(leafValueAsText));
+ sqlStringBuilder.append("'");
+ } else {
+ throw new CpsPathException(" can use only " + nextComparativeOperator + " with integer ");
+ }
+ }
+ if (!booleanOperatorsQueue.isEmpty()) {
+ sqlStringBuilder.append(" ");
+ sqlStringBuilder.append(booleanOperatorsQueue.poll());
+ sqlStringBuilder.append(" ");
+ }
+ });
+ sqlStringBuilder.append(")");
+ }
+
private static void addTextFunctionCondition(final CpsPathQuery cpsPathQuery,
final StringBuilder sqlStringBuilder,
final Map<String, Object> queryParameters) {
if (cpsPathQuery.hasContainsFunctionCondition()) {
sqlStringBuilder.append(" AND attributes ->> :containsLeafName LIKE CONCAT('%',:containsValue,'%') ");
queryParameters.put("containsLeafName", cpsPathQuery.getContainsFunctionConditionLeafName());
- queryParameters.put("containsValue", cpsPathQuery.getContainsFunctionConditionValue());
+ queryParameters.put("containsValue",
+ EscapeUtils.escapeForSqlLike(cpsPathQuery.getContainsFunctionConditionValue()));
}
}