blob: 78f2a4133818e513be0a57a2e00b69aceee58a57 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jmeter.protocol.bolt.sampler;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.jmeter.config.ConfigTestElement;
import org.apache.jmeter.engine.util.ConfigMergabilityIndicator;
import org.apache.jmeter.gui.TestElementMetadata;
import org.apache.jmeter.protocol.bolt.config.BoltConnectionElement;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testbeans.TestBean;
import org.apache.jmeter.testelement.TestElement;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Record;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.SessionConfig;
import org.neo4j.driver.TransactionConfig;
import org.neo4j.driver.exceptions.Neo4jException;
import org.neo4j.driver.summary.ResultSummary;
import com.fasterxml.jackson.core.StreamReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.json.JsonMapper;
@TestElementMetadata(labelResource = "displayName")
public class BoltSampler extends AbstractBoltTestElement implements Sampler, TestBean, ConfigMergabilityIndicator {
private static final Set<String> APPLICABLE_CONFIG_CLASSES = new HashSet<>(
Collections.singletonList("org.apache.jmeter.config.gui.SimpleConfigGui")); // $NON-NLS-1$
// Enables to initialize object mapper on demand
private static class Holder {
private static final ObjectReader OBJECT_READER = JsonMapper.builder()
// See https://github.com/FasterXML/jackson-core/issues/991
.enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
.build()
.readerFor(new TypeReference<HashMap<String, Object>>() {
});
}
@Override
public SampleResult sample(Entry e) {
SampleResult res = new SampleResult();
res.setSampleLabel(getName());
res.setSamplerData(request());
res.setDataType(SampleResult.TEXT);
res.setContentType("text/plain"); // $NON-NLS-1$
res.setDataEncoding(StandardCharsets.UTF_8.name());
Map<String, Object> params;
try {
params = getParamsAsMap();
} catch (IOException ex) {
return handleException(res, ex);
}
// Assume we will be successful
res.setSuccessful(true);
res.setResponseMessageOK();
res.setResponseCodeOK();
res.sampleStart();
try {
res.setResponseHeaders("Cypher request: " + getCypher());
res.setResponseData(
execute(
BoltConnectionElement.getDriver(),
getCypher(),
params,
getSessionConfig(),
getTransactionConfig()),
StandardCharsets.UTF_8.name());
} catch (Exception ex) {
res = handleException(res, ex);
} finally {
res.sampleEnd();
}
return res;
}
/**
* @see org.apache.jmeter.samplers.AbstractSampler#applies(org.apache.jmeter.config.ConfigTestElement)
*/
@Override
public boolean applies(ConfigTestElement configElement) {
String guiClass = configElement.getProperty(TestElement.GUI_CLASS).getStringValue();
return APPLICABLE_CONFIG_CLASSES.contains(guiClass);
}
private String execute(Driver driver, String cypher, Map<String, Object> params,
SessionConfig sessionConfig, TransactionConfig txConfig) {
try (Session session = driver.session(sessionConfig)) {
Result statementResult = session.run(cypher, params, txConfig);
return response(statementResult);
}
}
private static SampleResult handleException(SampleResult res, Exception ex) {
res.setResponseMessage(ex.toString());
if (ex instanceof Neo4jException) {
res.setResponseCode(((Neo4jException)ex).code());
} else {
res.setResponseCode("500");
}
res.setResponseData(
ObjectUtils.defaultIfNull(ex.getMessage(), "NO MESSAGE"),
res.getDataEncodingNoDefault());
res.setSuccessful(false);
return res;
}
private Map<String, Object> getParamsAsMap() throws IOException {
if (getParams() != null && getParams().length() > 0) {
return Holder.OBJECT_READER.readValue(getParams());
} else {
return Collections.emptyMap();
}
}
private String request() {
StringBuilder request = new StringBuilder();
request.append("Query: \n")
.append(getCypher())
.append("\n")
.append("Parameters: \n")
.append(getParams())
.append("\n")
.append("Database: \n")
.append(getDatabase())
.append("\n")
.append("Access Mode: \n")
.append(getAccessMode());
return request.toString();
}
private String response(Result result) {
StringBuilder response = new StringBuilder();
List<Record> records;
if (isRecordQueryResults()) {
//get records already as consume() will exhaust the stream
records = result.list();
} else {
records = Collections.emptyList();
}
response.append("\nSummary:");
ResultSummary summary = result.consume();
response.append("\nConstraints Added: ")
.append(summary.counters().constraintsAdded())
.append("\nConstraints Removed: ")
.append(summary.counters().constraintsRemoved())
.append("\nContains Updates: ")
.append(summary.counters().containsUpdates())
.append("\nIndexes Added: ")
.append(summary.counters().indexesAdded())
.append("\nIndexes Removed: ")
.append(summary.counters().indexesRemoved())
.append("\nLabels Added: ")
.append(summary.counters().labelsAdded())
.append("\nLabels Removed: ")
.append(summary.counters().labelsRemoved())
.append("\nNodes Created: ")
.append(summary.counters().nodesCreated())
.append("\nNodes Deleted: ")
.append(summary.counters().nodesDeleted())
.append("\nRelationships Created: ")
.append(summary.counters().relationshipsCreated())
.append("\nRelationships Deleted: ")
.append(summary.counters().relationshipsDeleted());
response.append("\n\nRecords: ");
if (isRecordQueryResults()) {
for (Record record : records) {
response.append("\n").append(record);
}
} else {
response.append("Skipped");
result.consume();
}
return response.toString();
}
}