I am trying to code a OSGi bundle which can be initiated using multiple configurations. Purpose of my bundle is to rewrite static links in html and redirect it to a CDN URL. I am using org.apache.sling.rewriter.Transformer to achieve this.
#Component(metatype = true, label = "CDN Link Rewriter", configurationFactory = true, immediate = true)
#Service(value = TransformerFactory.class)
public class LinkTransformer implements Transformer,
TransformerFactory {
#Property(label = "Static URL Extensions", value = "js,jpg,png,css,gif")
private static final String STATIC_FILES_EXTNS = "static_file_extn";
#Property(label = "Domain Path", value = "")
private static final String DOMAIN_PATH = "domain_path";
#Property(label = "CDN Url prefix", value = "")
private static final String CDN_URL_PREFIX = "cdn_url_prefix";
#Property(label = "Tags to check", value = "a,img,link,script")
private static final String TAGS_TO_CHECK = "tags_to_check";
#Property(label = "Attributes to check", d value = "src,href")
private static final String ATTRS_TO_CHECK = "attrs_to_check";
#Property(value = "append-version", propertyPrivate = true)
private static final String PIPELINE_TYPE = "pipeline.type";
#Property(value = "global", propertyPrivate = true)
private static final String PIPELINE_MODE = "pipeline.mode";
#Activate
protected void activate(final Map<String, Object> props) {
this.update(props);
}
#Modified
protected void update(final Map<String, Object> props) {
}
public LinkTransformer() {
}
#Override
public void init(org.apache.sling.rewriter.ProcessingContext context,
org.apache.sling.rewriter.ProcessingComponentConfiguration config)
throws IOException {
}
#Override
public final Transformer createTransformer() {
return new LinkTransformer();
}
//some other methods
}
Problem: I am unable to access my configurations in my bundle. I am able to create multiple sets of configurations in Felix console. But #Activate method is called only at the time of bundle installation. During Link transformation activty only init() method is being called. Hence I am unable to get hold of configurations. Can anyone tell me how to get configurations ?
The problem with above approach is implementing to different interfaces in same class. Thanks to #Balazs Zsoldos you can check the answer here
Here, All I had to do was seperately implement Transformer and TransformerFactory.
#Component(configurationFactory = true, metatype = true, policy = ConfigurationPolicy.REQUIRE, label = "CDN Link Rewriter", description = "Rewrites links to all static files to use configurable CDN")
#Service(value = TransformerFactory.class)
public class StaticLinkTransformerFactory implements TransformerFactory {
//all property declarations as in question
private Map<String, Object> map;
#Activate
void activate(Map<String, Object> map) {
this.map = map;
}
#Override
public Transformer createTransformer() {
return new StaticLinkTransformer(map);
}
}
StaticLinkTransformer can be implemented as plain java class without any component or service annotations.
Related
I have the following classes, which have quite similar method implementations. Only the classes' method inputs and outputs seem to be of different types. When I put it like this, it sounds like a case for inheritance, however, the fact that the inputs and outputs are different and are related to two lambdas, make me wonder if they should remain without any relationship, as one lambda cannot be thought of in place of another (To be a case for inheritance).
My first class looks like the following.
public class JobPersistenceManager {
private String jobIndexName;
private JobLambda JobLambda;
private MyDataPersistence myDataPersistence;
private DataProcessorUtils dataProcessorUtils;
private static final String JOB_ID = "jobId";
private static final String JOB_NAME = "jobName";
#Inject
public JobPersistenceManager(#Named("jobIndexName") String jobIndexName,
JobLambda JobLambda,
MyDataPersistence myDataPersistence) {
this.jobIndexName = jobIndexName;
this.JobLambda = JobLambda;
this.myDataPersistence = myDataPersistence;
createIndexIfNotExists(this.jobIndexName);
}
public SearchDocumentResult searchJob(MyJobInput myJobInput) throws IOException {
return myDataPersistence
.searchDocument(this.jobIndexName,
dataProcessorUtils.transformObjectDataPayloadToMap(myJobInput));
}
public MyJobOutput invokeCreateJobLambdaAndIndexData(final MyJobInput myJobInput)
throws IOException {
String personRequestPayload = dataProcessorUtils.transformObjectDataInputJson(myJobInput);
Map<String, String> createdJobOutput = this.JobLambda.invokeLambda(personRequestPayload);
this.indexCreatedJob(myJobInput, createdPersonOutput);
return MyJobOutput.builder().withJobID(createdJobOutput.get(JOB_ID))
.withJobName(createdJobOutput.get(JOB_NAME)).build();
}
public int indexCreatedJob(final MyJobInput myJobInput,
final Map<String, String> createdJobOutput) throws IOException {
myJobInput = modifyJobInput(myJobInput);
String documentToIndex = dataProcessorUtils.transformObjectDataInputJson(myJobInput);
return myDataPersistence.indexDocument(this.jobIndexName, documentToIndex);
}
private void createIndexIfNotExists(final String indexName) {
if (!myDataPersistence.doesIndexExist(indexName)) {
myDataPersistence.createIndex(CreateIndexInput.builder().indexName(indexName).build());
}
}
}
My second class looks like the following.
public class EmployeePersistenceManager {
private EmployeeLambda employeeLambda;
private MyTestDataPersistence myTestDataPersistence;
private DataProcessorUtils dataProcessorUtils;
private String employeeIndexName;
private static final String PERSON_ID_KEY = "personId";
private static final String PERSON_NAME_KEY = "personName";
#Inject
public EmployeePersistenceManager(#Named("employeeIndexName") String employeeIndexName,
EmployeeLambda employeeLambda,
MyTestDataPersistence myTestDataPersistence,
DataProcessorUtils dataProcessorUtils) {
this.employeeIndexName = employeeIndexName;
this.employeeLambda = employeeLambda;
this.myTestDataPersistence = myTestDataPersistence;
this.dataProcessorUtils = dataProcessorUtils;
createIndexIfNotExists(employeeIndexName);
}
public SearchDocumentResult searchPerson(EmployeeInput employeeInput) throws IOException {
return myTestDataPersistence
.searchDocument(employeeIndexName,
dataProcessorUtils.transformObjectDataPayloadToMap(employeeInput));
}
public EmployeeOutput invokeCreatePersonLambdaAndIndexData(final EmployeeInput employeeInput)
throws IOException {
String personRequestPayload = dataProcessorUtils.transformObjectDataInputJson(employeeInput);
Map<String, String> createdPersonOutput = this.employeeLambda.invokeLambda(personRequestPayload);
this.indexCreatedEmployee(employeeInput, createdPersonOutput);
return EmployeeOutput.builder().withPersonId(createdPersonOutput.get(PERSON_ID_KEY))
.withPersonName(createdPersonOutput.get(PERSON_NAME_KEY)).build();
}
public int indexCreatedEmployee(final EmployeeInput employeeInput,
final Map<String, String> createdPersonOutput) throws IOException {
employeeInput = modifyEmployeeInput(employeeInput);
String documentToIndex = dataProcessorUtils.transformObjectDataInputJson(employeeInput);
return myTestDataPersistence.indexDocument(this.employeeIndexName, documentToIndex);
}
public Map.Entry<String, Map<String, String>> invokeLambda(final String payload) {
return new AbstractMap.SimpleEntry<>(payload, this.employeeLambda.invokeLambda(payload));
}
private void createIndexIfNotExists(final String indexName) {
if (!myTestDataPersistence.doesIndexExist(indexName)) {
myTestDataPersistence.createIndex(CreateIndexInput.builder().indexName(indexName).build());
}
}
}
As you can see, the methods perform almost the same actions. Only the indexCreatedEmployee and indexCreatedJob methods from the classes have an extra step of processing the input.
Should I keep these classes as they are now without any relationships between them, or should I create an abstract persistence manager class and perform the following.
Move createIndexIfNotExists to the abstract class
Create abstract methods search(), invokeLambda() and indexCreatedData() methods and implement them in each child class. The data types MyJobInput and MyEmployeeInput are POJO classes that don't have any relationship. So I guess these methods I mentioned would then take "Object" parameters?
EmployeeLambda and JobLambda are again classes with no relationship between them. Another concern I had towards creating some sort of inheritance was that, Employee Lambda and JobLambda cannot be used inter-changeably. So was wondering if they should inherit the same parent class just because they're both lambda classes.
OR is there another way to go about this? Any advice would be much appreciated. Thank you very much in advance.
As promised yesterday, here is what I would do.
Create a Lambda interface and make JobLambda and EmployeeLambda implement it
public interface Lambda {
Map<String, String> invokeLambda(String payload);
}
public class JobLambda implements Lambda {
//... your implementation
}
public class EmployeeLambda implements Lambda {
//... your implementation
}
Do the same for DataPersistence
public interface DataPersistence {
boolean doesIndexExist(String indexName);
void createIndex(CreateIndexInput createIndexInput);
int indexDocument(String indexName, String documentToIndex);
SearchDocumentResult searchDocument(String indexName, Map<String, String> payloadMap);
}
public class MyDataPersistence implements DataPersistence {
//... your implementation
}
public class MyTestDataPersistence implements DataPersistence {
//... your implementation
}
Then create a parent class PersistenceManager which contains all the duplicated methods, parametrized for the type of input/output:
(Note: I didn't complete everything, but I did something just to make you understand the concept)
public class PersistenceManager<I, O> {
protected static final String ID = "Id";
protected static final String NAME = "Name";
private String indexName;
private Lambda lambda;
private DataPersistence dataPersistence;
private DataProcessorUtils dataProcessorUtils;
public PersistenceManager(String indexName, Lambda lambda, DataPersistence dataPersistence, DataProcessorUtils dataProcessorUtils) {
this.indexName = indexName;
this.lambda = lambda;
this.dataPersistence = dataPersistence;
this.dataProcessorUtils = dataProcessorUtils;
createIndexIfNotExists(indexName);
}
public SearchDocumentResult search(I input) {
return dataPersistence.searchDocument(indexName, dataProcessorUtils.transformObjectDataPayloadToMap(input));
}
public O invokeCreateLambdaAndIndexData(final I input) {
String requestPayload = dataProcessorUtils.transformObjectDataInputJson(input);
Map<String, String> createdOutput = this.lambda.invokeLambda(requestPayload);
//continue generalizing following the same logic
}
public int indexCreated(I input, Map<String, String> createdOutput) {
//continue generalizing following the same logic
}
private void createIndexIfNotExists(final String indexName) {
if (!dataPersistence.doesIndexExist(indexName)) {
dataPersistence.createIndex(CreateIndexInput.builder().indexName(indexName).build());
}
}
}
At this point, you can specialize your classes by simply choosing the parameters
... all the rest of the code will be shared in the parent class.
public class JobPersistenceManager extends PersistenceManager<MyJobInput, MyJobOutput> {
private static final String JOB_ID = "Job" + ID;
private static final String JOB_NAME = "Job" + NAME;
public JobPersistenceManager(String indexName, Lambda lambda, DataPersistence dataPersistence, DataProcessorUtils dataProcessorUtils) {
super(indexName, lambda, dataPersistence, dataProcessorUtils);
}
}
public class EmployeePersistenceManager extends PersistenceManager<MyEmployeeInput, MyEmployeeOutput> {
private static final String EMPLOYEE_ID = "Employee" + ID;
private static final String EMPLOYEE_NAME = "Employee" + NAME;
public EmployeePersistenceManager(String indexName, Lambda lambda, DataPersistence dataPersistence, DataProcessorUtils dataProcessorUtils) {
super(indexName, lambda, dataPersistence, dataProcessorUtils);
}
}
... and use them like this:
PersistenceManager employeePersistenceManager = new EmployeePersistenceManager(...);
employeePersistenceManager.search(employeeInput); //<-- the code is in the base class
Here is my class:
public class DeduplicationErrorMetric extends AbstractErrorMetric {
public static final String DEDUPLICATIONS_METRIC = "deduplications";
public static final String KAFKA_MESSAGES_METRIC = "kafka.messages";
private static String DEDUPLICATION_TOPIC_NAME;
private static final List<Tag> DEDUPLICATION_ERROR_TAGS = List.of(Tag.of("type", "failed"));
private static final List<Tag> KAFKA_ERROR_TAGS = List.of(Tag.of("topic", DEDUPLICATION_TOPIC_NAME),
Tag.of("action", "send"), Tag.of("result", "failure"));
public DeduplicationErrorMetric() {
super(Map.of(
DEDUPLICATIONS_METRIC, DEDUPLICATION_ERROR_TAGS,
KAFKA_MESSAGES_METRIC, KAFKA_ERROR_TAGS
));
}
#Override
public void incrementMetric(String key) {
errorCounters.get(key).increment();
}
}
I have #Value("${kafka.topic.deduplication}") in my application.yml, and I need to insert the value into DEDUPLICATION_TOPIC_NAME before the bean will be created. How can I do it?
You can use the setter to do this but I'd advocate against this practice !
This means your field will be null before a first instance comes and invokes this injection point
Your static field is not final so can lead to modification, thus lead to hard to debug bugs
It will not solve your current problem as the null value will be used in this case for KAFKA_ERROR_TAGS
#Value("${kafka.topic.deduplication}")
private void setDeduplicationTopicName(String deduplicationTopicName) {
this.DEDUPLICATION_TOPIC_NAME = deducplicationTopicName;
}
Instead, maybe try to create a #Singleton bean and use #Value on its fields, then you're sure you have only one instance.
For your list, you can then use #PostConstruct to make sure it's instantiated once
What you could do here is to directly use injection from a properties file.
If it is a SpringBoot app, in you application properties set your kafka.topic.deduplication property (you can have different values for different environments).
This way, Spring will get the value while constructing the bean.
Your code could look something like this:
public class DeduplicationErrorMetric extends AbstractErrorMetric {
public static final String DEDUPLICATIONS_METRIC = "deduplications";
public static final String KAFKA_MESSAGES_METRIC = "kafka.messages";
private static final List<Tag> DEDUPLICATION_ERROR_TAGS = List.of(Tag.of("type", "failed"));
private static final List<Tag> KAFKA_ERROR_TAGS = List.of(Tag.of("topic", deduplicationTopicName),
Tag.of("action", "send"), Tag.of("result", "failure"));
#Value("${kafka.topic.deduplication}")
private String deduplicationTopicName;
public DeduplicationErrorMetric() {
super(Map.of(
DEDUPLICATIONS_METRIC, DEDUPLICATION_ERROR_TAGS,
KAFKA_MESSAGES_METRIC, KAFKA_ERROR_TAGS
));
}
#Override
public void incrementMetric(String key) {
errorCounters.get(key).increment();
}
}
Remove the keyword "static" and then you will be able to change it in the instance.
Static means that the field is locked to the class.
public class DeduplicationErrorMetric extends AbstractErrorMetric {
public static final String DEDUPLICATIONS_METRIC = "deduplications";
public static final String KAFKA_MESSAGES_METRIC = "kafka.messages";
private String DEDUPLICATION_TOPIC_NAME;
private static final List<Tag> DEDUPLICATION_ERROR_TAGS = List.of(Tag.of("type", "failed"));
private List<Tag> KAFKA_ERROR_TAGS = List.of(Tag.of("topic", DEDUPLICATION_TOPIC_NAME),
Tag.of("action", "send"), Tag.of("result", "failure"));
public DeduplicationErrorMetric() {
super(Map.of(
DEDUPLICATIONS_METRIC, DEDUPLICATION_ERROR_TAGS,
KAFKA_MESSAGES_METRIC, KAFKA_ERROR_TAGS
));
}
#Override
public void incrementMetric(String key) {
errorCounters.get(key).increment();
}
public void setTopic(String value){
DEDUPLICATION_TOPIC_NAME = value;
}
}
private void example(){
DeduplicationErrorMetric dem = new DeduplicationErrorMetric();
//Set the instance value directly
dem.DEDUPLICATION_TOPIC_NAME = "Test";
//Set via a function, potentially with other variables.
demo.setTopic("Test");
}
I would also recommend making the variable name lowercase now that it is not static, as good coding practice.
I am trying to add new custom Kafka Converter which is a modification of JsonConverterConfig in connect-json. I am trying to add some new custom property say "schemas.modifications.enable" in the converter which extends JsonConverterConfig. But Kafka connect is not able to find details about the converter.
My code Snippet :
public class ModifiedJsonConfig extends JsonConverterConfig {
public static final String SCHEMAS_MODIFY_CONFIG = "schemas.modifications.enable";
public static final boolean SCHEMAS_MODIFY_CONFIG_DEFAULT = true;
private static final String SCHEMAS_MODIFY_CONFIG_DOC = "The maximum number of schemas that can be cached in this converter instance.";
private static final String SCHEMAS_MODIFY_CONFIG_DISPLAY = "Schema Cache Size";
private final static ConfigDef CONFIG;
static {
String group = "Schemas-modification";
int orderInGroup = 0;
CONFIG = ConverterConfig.newConfigDef();
CONFIG.define(SCHEMAS_MODIFY_CONFIG, Type.BOOLEAN, SCHEMAS_MODIFY_CONFIG_DEFAULT, Importance.HIGH, SCHEMAS_MODIFY_CONFIG_DOC, group,
orderInGroup++, Width.MEDIUM, SCHEMAS_MODIFY_CONFIG_DISPLAY);
}
public static ConfigDef configDef() {
return CONFIG;
}
public ModifiedJsonConfig(Map<String, ?> props) {
super(props);
}
public boolean schemasModified() {
return getBoolean(SCHEMAS_MODIFY_CONFIG);
}
}
But I am getting the error here :
ERROR Stopping due to error (org.apache.kafka.connect.cli.ConnectDistributed:83)
org.apache.kafka.common.config.ConfigException: Unknown configuration 'schemas.modifications.enable'
But I have defined this configuration. It would be really helpful if you can help me set a custom converter property here.
Thanks in advance.
i'm using OWL-S API (source and javadoc here: http://on.cs.unibas.ch/owls-api/apidocs/ )
If i do this i have the correct result:
public class Example {
private static ISWRLFactory factory; //is a interface
private string url = "http://...";
private Service aService;
private OWLOntology ont;
void method_A(){
URI aURI = URI.create(url);
OWLKnowledgeBase aKB = OWLFactory.createKB();
aService = aKB.readService(aURI);
ont = aKB.createOntology(aURI);
ont.createService(aService.getURI());
}
void method_B(){
factory = SWRLFactory.createFactory(ont);
Atom builtAtom = factory.createNotEqual(x, variab); //x and variab are two variable
}
}
But if i do this i don't have the correct result:
public class Example {
private static ISWRLFactory factory; //is a interface
private string url = "http://...";
private Service aService;
private OWLOntology ont;
void method_A(){
URI aURI = URI.create(url);
OWLKnowledgeBase aKB = OWLFactory.createKB();
aService = aKB.readService(aURI);
ont = aKB.createOntology(aURI);
ont.createService(aService.getURI());
factory = SWRLFactory.createFactory(ont);
}
void method_B(){
Atom builtAtom = factory.createNotEqual(x, variab); //x and variab are two variable
}
}
Why?
Difference exist in executing " factory = SWRLFactory.createFactory(ont);" line in method_A() and method_B().
Are you sure 'factory' object is not modified between method_A() and method_B() calls? Does 'x' and 'variable' have some dependency on 'factory' ?
Javadoc is great for scanning all of source files and creating HTML pages to view it. I was wondering if there is a similar tool that would go through all of your Spring controllers and collect all of the methods that have been annotated with #RequestMapping and produce a single HTML page listing them. Sort of like a pseudo site map for developers to ensure uniqueness and standardization across controllers.
I apologize if this question has been asked elsewhere already. I could not come up with an appropriate set of search terms that would provide a useful result.
This is a very good question, I often miss (and implement) functionality like this.
Use a Build Tool
What I'd do is run Maven (or ant) and execute a task after compilation that
reads all classes (perhaps with a configurable list of packages)
iterates over all methods of these classes
reads the annotations
and writes the output to HTML
Use Annotation Processing
But I guess this is a scenario, where annotation processing might also be a way to do it. Usually, you have to use some internal APIs to get stuff done in API, but using Filer.createResource(...) it should actually possible to do it out of the box.
Here's a rudimentary implementation:
public class RequestMappingProcessor extends AbstractProcessor{
private final Map<String, String> map =
new TreeMap<String, String>();
private Filer filer;
#Override
public Set<String> getSupportedAnnotationTypes(){
return Collections.singleton(RequestMapping.class.getName());
}
#Override
public synchronized void init(
final ProcessingEnvironment processingEnv){
super.init(processingEnv);
filer = processingEnv.getFiler();
}
#Override
public boolean process(
final Set<? extends TypeElement> annotations,
final RoundEnvironment roundEnv){
for(final TypeElement annotatedElement : annotations){
final RequestMapping mapping =
annotatedElement.getAnnotation(
RequestMapping.class
);
if(mapping != null){
addMapping(mapping, annotatedElement, roundEnv);
}
}
assembleSiteMap();
return false;
}
private void assembleSiteMap(){
Writer writer = null;
boolean threw = false;
try{
final FileObject fileObject =
filer.createResource(
StandardLocation.CLASS_OUTPUT,
"html", "siteMap.html"
);
writer = fileObject.openWriter();
writer.append("<body>\n");
for(final Entry<String, String> entry : map.entrySet()){
writer
.append("<a href=\"")
.append(entry.getKey())
.append("\">")
.append("Path: ")
.append(entry.getKey())
.append(", method: ")
.append(entry.getValue())
.append("</a>\n");
}
writer.append("</body>\n");
} catch(final IOException e){
threw = true;
throw new IllegalStateException(e);
} finally{
// with commons/io: IOUtils.closeQuietly(writer)
// with Guava: Closeables.close(writer, rethrow)
// with plain Java this monstrosity:
try{
if(writer != null){
writer.close();
}
} catch(final IOException e){
if(!threw){
throw new IllegalStateException(e);
}
} finally{
}
}
}
private void addMapping(final RequestMapping mapping,
final TypeElement annotatedElement,
final RoundEnvironment roundEnv){
final String[] values = mapping.value();
for(final String value : values){
map.put(
value,
annotatedElement.getQualifiedName().toString()
);
}
}
}
There's nothing that I know of that would do that. I've retrieved controllers and mappings via the app context before to create navigation, but it was a lot of work for little gain IMO:
#Component
public class SiteMap implements ApplicationContextAware, InitializingBean {
private ApplicationContext context;
private List<Page> pages = new ArrayList<Page>();
public List<Page> getPages() {
return pages;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.context = applicationContext;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(context, "applicationContext not set");
Map<String, Object> controllers = ctx.getBeansWithAnnotation(Controller.class);
for(Map.Entry<String, Object> entry : controllers.entrySet()) {
Page page = new Page();
Class<?> controllerClass = entry.getValue();
String controllerRoot = null;
RequestMapping classMapping = controllerClass.getAnnotation(RequestMapping.class);
if(classMapping != null)
controllerRoot = classMapping.value();
if(controllerRoot = null)
controllerRoot = // get and parse controller name
page.setPath(controllerRoot);
for(Method m : controllerClass.getDeclaredMethods()) {
RequestMapping rm = m.getAnnotation(RequestMapping.class);
if(rm == null)
continue;
Page child = new Page();
child.setPath(rm.value());
page.getChildren().add(child);
}
pages.add(page);
}
}
public static class Page {
private String path;
private List<Page> children = new ArrayList<Page>();
// other junk
}
}
Then access ${pages} in your site map page JSP. Might need to play with that code some if you do something similar, I freehanded it in this editor.