io.telicent.core.FMod_CQRS Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scg-system Show documentation
Show all versions of scg-system Show documentation
System code - plugins, extensions, entrypoints etc. - for Smart Cache Graph
/*
* Copyright (c) Telicent Ltd.
*
* Licensed 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 io.telicent.core;
import static io.telicent.core.CQRS.symKafkaTopic;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.apache.jena.atlas.lib.Version;
import org.apache.jena.atlas.logging.FmtLog;
import org.apache.jena.fuseki.Fuseki;
import org.apache.jena.fuseki.FusekiConfigException;
import org.apache.jena.fuseki.kafka.FKRegistry;
import org.apache.jena.fuseki.kafka.FKS;
import org.apache.jena.fuseki.main.FusekiServer;
import org.apache.jena.fuseki.main.sys.FusekiModule;
import org.apache.jena.fuseki.server.*;
import org.apache.jena.fuseki.servlets.ActionService;
import org.apache.jena.fuseki.servlets.HttpAction;
import org.apache.jena.kafka.KConnectorDesc;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.riot.WebContent;
import org.apache.jena.sparql.util.Context;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.slf4j.Logger;
/** Add CQRS update (writes patches to Kafka). */
public class FMod_CQRS implements FusekiModule {
public static Logger LOG = CQRS.LOG;
/** Software version taken from the jar file. */
private static final String VERSION = Version.versionForClass(FMod_CQRS.class).orElse("");
private static ActionService placeholder = new ActionService() {
@Override
public void validate(HttpAction action) {}
@Override
public void execute(HttpAction action) {
FmtLog.info(CQRS.LOG, "CQRS execute called but ActionService not configured");
}
};
public FMod_CQRS() {
// Register a placeholder ActionService
// This is replaced by the specific ActionService during prepare()
OperationRegistry.get()
.register(CQRS.Vocab.operationUpdateCQRS, WebContent.contentTypeSPARQLUpdate, placeholder);
}
@Override
public String name() {
return "CQRS";
}
@Override
public void prepare(FusekiServer.Builder builder, Set names, Model configModel) {
FmtLog.info(Fuseki.configLog, "CQRS Fuseki Module (%s)", VERSION);
// // Register "http://telicent.io/cqrs#update".
// The configured ActionService is set during configDataAccessPoint.
builder.registerOperation(CQRS.Vocab.operationUpdateCQRS,
WebContent.contentTypeSPARQLUpdate,
placeholder);
}
@Override
public void configured(FusekiServer.Builder serverBuilder, DataAccessPointRegistry dapRegistry, Model configModel) {
FusekiModule.super.configured(serverBuilder, dapRegistry, configModel);
}
@Override
public void configDataAccessPoint(DataAccessPoint dap, Model configModel) {
dap.getDataService().forEachEndpoint(endpoint->{
Operation op = endpoint.getOperation();
// Upgrade all update operations ...
// if ( Operation.Update.equals(op) ) {
// if ( topicName != null )
// op = CQRS.Vocab.operationUpdateCQRS;
// }
// Bind a processor to any CQRS Update operation.
if ( CQRS.Vocab.operationUpdateCQRS.equals(op) ) {
String topicName = getTopicFromContext(endpoint.getContext());
if ( topicName == null ) {
List topics = FKS.findTopics(dap.getName());
if ( topics.isEmpty()) {
FmtLog.error(LOG, "No topic name in context nor a registered connector for dataset %s.");
throw new FusekiConfigException("No topic name found");
}
if ( topics.size() > 1 ) {
FmtLog.error(LOG, "Multiple registered connectors for dataset %s. Set topic name in context to select one.");
throw new FusekiConfigException("Mutliple topic names found");
}
topicName = topics.get(0);
}
FmtLog.info(LOG, "Endpoint %s (operation %s) to topic %s", endpointName(dap, endpoint), op.getName(), topicName);
if ( topicName.isEmpty() )
throw new FusekiConfigException("Empty string for topic name for "+symKafkaTopic+" on CQRS update operation");
KConnectorDesc conn = FKRegistry.get().getConnectorDescriptor(topicName);
ActionService cqrsUpdate = CQRS.updateAction(topicName, producerProperties(conn.getBootstrapServers()));
endpoint.setProcessor(cqrsUpdate);
}
});
}
private String endpointName(DataAccessPoint dap, Endpoint endpoint) {
if ( endpoint.isUnnamed() )
return dap.getName();
return dap.getName()+"/"+endpoint.getName();
}
private static String getTopicFromContext(Context context) {
if ( context == null )
return null;
return context.get(symKafkaTopic);
}
// @Override
// public void serverBeforeStarting(FusekiServer server) {
// // -- Update configuration
// server.getDataAccessPointRegistry().accessPoints().forEach(dap->{
// dap.getDataService().forEachEndpoint(endpoint->{
// ...
// });
// });
// }
private static Properties producerProperties(String server) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, server);
return props;
}
}