blob: e1a05c2f47c1f27b2f20faff0bfc28ef70130794 [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.stanbol.enhancer.servicesapi.helper;
import static java.util.Collections.singleton;
import static org.apache.stanbol.enhancer.servicesapi.rdf.Properties.*;
import static org.apache.stanbol.enhancer.servicesapi.rdf.TechnicalClasses.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.UUID;
import org.apache.clerezza.commons.rdf.BlankNode;
import org.apache.clerezza.rdf.core.InvalidLiteralTypeException;
import org.apache.clerezza.commons.rdf.Language;
import org.apache.clerezza.commons.rdf.Literal;
import org.apache.clerezza.commons.rdf.Graph;
import org.apache.clerezza.commons.rdf.BlankNodeOrIRI;
import org.apache.clerezza.commons.rdf.RDFTerm;
import org.apache.clerezza.commons.rdf.Triple;
import org.apache.clerezza.commons.rdf.Graph;
import org.apache.clerezza.commons.rdf.IRI;
import org.apache.clerezza.commons.rdf.impl.utils.PlainLiteralImpl;
import org.apache.clerezza.commons.rdf.impl.utils.TripleImpl;
import org.apache.clerezza.rdf.core.LiteralFactory;
import org.apache.stanbol.enhancer.servicesapi.Chain;
import org.apache.stanbol.enhancer.servicesapi.ContentItem;
import org.apache.stanbol.enhancer.servicesapi.EnhancementEngine;
import org.apache.stanbol.enhancer.servicesapi.EnhancementPropertyException;
import org.apache.stanbol.enhancer.servicesapi.NoSuchPartException;
import org.apache.stanbol.enhancer.servicesapi.ServiceProperties;
import org.apache.stanbol.enhancer.servicesapi.rdf.ExecutionPlan;
import org.apache.stanbol.enhancer.servicesapi.rdf.NamespaceEnum;
import org.osgi.service.cm.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class EnhancementEngineHelper {
/**
* Restrict instantiation
*/
private EnhancementEngineHelper() {}
/**
* The maximum size of the prefix/suffix for the selection context
* @since 0.11.0
*/
public static final int DEFAULT_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE = 50;
/**
* The minimum size of the prefix/suffix for the selection context
* @since 0.11.0
*/
public static final int MIN_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE = 15;
/**
* The minimum length of the selected text so that fise:selection-head and
* fise:selection.tail are being used instead of fise:selected-text. The
* actual size is calculated by using <code>prefixSuffixLength*5</code>.
* So if a user does not chage the {@link #DEFAULT_PREFIX_SUFFIX_LENGTH} the
* default value us <code>10 * 5 = 50</code> chars.
*/
public static final int MIN_SELECTEN_HEAD_TAIL_USAGE_LENGTH = 30;
/**
* The default length of fise:selection-prefix and fise:selection-suffix
* literals (value = 10).
*/
public static final int DEFAULT_PREFIX_SUFFIX_LENGTH = 10;
/**
* The minimum size for fise:selection-prefix and fise:selection-suffix
* literals (value = 3).
*/
public static final int MIN_PREFIX_SUFFIX_SIZE = 3;
protected final static Random rng = new Random();
private final static Logger log = LoggerFactory.getLogger(EnhancementEngineHelper.class);
private final static LiteralFactory lf = LiteralFactory.getInstance();
public static void setSeed(long seed) {
rng.setSeed(seed);
}
/**
* Create a new instance with the types enhancer:Enhancement and
* enhancer:TextAnnotation in the metadata-graph of the content
* item along with default properties (dc:creator and dc:created) and return
* the IRI of the extraction so that engines can further add.
*
* @param ci the ContentItem being under analysis
* @param engine the Engine performing the analysis
*
* @return the URI of the new enhancement instance
*/
public static IRI createTextEnhancement(ContentItem ci,
EnhancementEngine engine){
return createTextEnhancement(ci.getMetadata(), engine, new IRI(ci.getUri().getUnicodeString()));
}
/**
* Create a new instance with the types enhancer:Enhancement and
* enhancer:TextAnnotation in the parsed graph along with default properties
* (dc:creator, dc:created and enhancer:extracted-form) and return
* the IRI of the extraction so that engines can further add.
*
* @param metadata the graph
* @param engine the engine
* @param contentItemId the id
*
* @return the URI of the new enhancement instance
*/
public static IRI createTextEnhancement(Graph metadata,
EnhancementEngine engine, IRI contentItemId){
IRI enhancement = createEnhancement(metadata, engine,contentItemId);
//add the Text Annotation Type
metadata.add(new TripleImpl(enhancement, RDF_TYPE,
ENHANCER_TEXTANNOTATION));
return enhancement;
}
/**
* This method sets the fise:start, fise:end, fise:selection-prefix,
* fise:selected-text and fise:selection-suffix properties for the
* parsed fise:TextAnnotation instance according to the parsed parameters.<p>
* While it is intended to be used for TextAnnotations this method can also
* be used to add the mentioned properties to {@link IRI}s with different
* type.<p>
* <b>NOTE</b> the <code>allowSelectionHeadTail</code>: This parameter allows
* to deactivate the usage of fise:selection-head and fise:selection-tail.
* Typically users should parse <code>false</code> in case of 'named entities'
* and <code>true</code> in case sections of the text (e.g. phrases, sentences,
* chapters ...) are selected.
* @param metadata The RDF graph to add the information
* @param textAnnotation the IRI of the fise:TextAnnotation
* @param content the plain text content as String
* @param start the start index of the occurrence
* @param end the end index of the occurrence
* @param lang the lanugage of the content or <code>null</code> if not known
* @param prefixSuffixSize the size of the prefix, suffix. If the parsed
* value &lt; 3 than the default 10 is used.
* @param allowSelectionHeadTail if <code>true</code> the fise:selection-head
* and fise:selection-tail properties are used instead of fise:selected-text
* if the selected text is longer as <code>Math.max(30, prefixSuffixSize*5);</code>.
* If <code>false</code> the fise:selected-text is added regardless of the
* size of the selected area.
* @since 0.11.0
*/
public static void setOccurrence(Graph metadata, IRI textAnnotation,
String content, Integer start, Integer end, Language lang, int prefixSuffixSize,
boolean allowSelectionHeadTail){
//set start, end
metadata.add(new TripleImpl(textAnnotation, ENHANCER_START,
lf.createTypedLiteral(start)));
metadata.add(new TripleImpl(textAnnotation, ENHANCER_END,
lf.createTypedLiteral(end)));
//set selection prefix and suffix (TextAnnotation new model)
prefixSuffixSize = prefixSuffixSize < MIN_PREFIX_SUFFIX_SIZE ?
DEFAULT_PREFIX_SUFFIX_LENGTH : prefixSuffixSize;
metadata.add(new TripleImpl(textAnnotation, ENHANCER_SELECTION_PREFIX,
new PlainLiteralImpl(content.substring(
Math.max(0,start-prefixSuffixSize), start), lang)));
metadata.add(new TripleImpl(textAnnotation, ENHANCER_SELECTION_SUFFIX,
new PlainLiteralImpl(content.substring(
end,Math.min(content.length(), end+prefixSuffixSize)),lang)));
//set the selected text (or alternatively head and tail)
int maxSelectedTextSize = Math.max(MIN_SELECTEN_HEAD_TAIL_USAGE_LENGTH,
prefixSuffixSize*5);
if(!allowSelectionHeadTail || end-start <= maxSelectedTextSize){
metadata.add(new TripleImpl(textAnnotation, ENHANCER_SELECTED_TEXT,
new PlainLiteralImpl(content.substring(start, end),lang)));
} else { //selected area to long for fise:selected-text
//use fise:selection-head and fise:selection-tail instead
metadata.add(new TripleImpl(textAnnotation, ENHANCER_SELECTION_HEAD,
new PlainLiteralImpl(content.substring(
start,start+prefixSuffixSize),lang)));
metadata.add(new TripleImpl(textAnnotation, ENHANCER_SELECTION_TAIL,
new PlainLiteralImpl(content.substring(
end-prefixSuffixSize,end),lang)));
}
}
/**
* Extracts the selection context based on the content, selection and
* the start char offset of the selection. Tries to cut of the context
* on whole words. The size of the prefix/suffix is set to
* {@link #DEFAULT_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE}.
* @param content the content
* @param selection the selected text
* @param selectionStartPos the start char position of the selection
* @return the context
* @since 0.11.0
*/
public static String getSelectionContext(String content, String selection, int selectionStartPos){
return getSelectionContext(content, selection, selectionStartPos,
DEFAULT_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE);
}
/**
* Extracts the selection context based on the content, selection and
* the start char offset of the selection. Tries to cut of the context
* on whole words.
* @param content the content
* @param selection the selected text
* @param selectionStartPos the start char position of the selection
* @param contextSize the size of the prefix/suffix. If less than zero the
* {@link #DEFAULT_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE} is used. If in the
* range [0..{@link #MIN_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE}] than the
* size is set to {@link #MIN_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE}
* @return the context
* @since 0.11.0
*/
public static String getSelectionContext(String content, String selection, int selectionStartPos, int contextSize){
if(contextSize < 0){
contextSize = DEFAULT_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE;
}
if(contextSize < MIN_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE){
contextSize = MIN_PREFIX_SUFFIX_SIZE;
}
//extract the selection context
int beginPos;
if(selectionStartPos <= DEFAULT_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE){
beginPos = 0;
} else {
int start = selectionStartPos-DEFAULT_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE;
beginPos = content.indexOf(' ',start);
if(beginPos < 0 || beginPos >= selectionStartPos){ //no words
beginPos = start; //begin within a word
}
}
int endPos;
if(selectionStartPos+selection.length()+DEFAULT_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE >= content.length()){
endPos = content.length();
} else {
int start = selectionStartPos+selection.length()+DEFAULT_SELECTION_CONTEXT_PREFIX_SUFFIX_SIZE;
endPos = content.lastIndexOf(' ', start);
if(endPos <= selectionStartPos+selection.length()){
endPos = start; //end within a word;
}
}
return content.substring(beginPos, endPos);
}
/**
* Create a new instance with the types enhancer:Enhancement and
* enhancer:EntityAnnotation in the metadata-graph of the content
* item along with default properties (dc:creator and dc:created) and return
* the IRI of the extraction so that engines can further add
*
* @param ci the ContentItem being under analysis
* @param engine the Engine performing the analysis
* @return the URI of the new enhancement instance
*/
public static IRI createEntityEnhancement(ContentItem ci,
EnhancementEngine engine){
return createEntityEnhancement(ci.getMetadata(), engine, new IRI(ci.getUri().getUnicodeString()));
}
/**
* Create a new instance with the types enhancer:Enhancement and
* enhancer:EntityAnnotation in the parsed graph along with default properties
* (dc:creator, dc:created and enhancer:extracted-form) and return
* the IRI of the extraction so that engines can further add.
*
* @param metadata the graph
* @param engine the engine
* @param contentItemId the id
*
* @return the URI of the new enhancement instance
*/
public static IRI createEntityEnhancement(Graph metadata,
EnhancementEngine engine, IRI contentItemId){
IRI enhancement = createEnhancement(metadata, engine, contentItemId);
metadata.add(new TripleImpl(enhancement, RDF_TYPE, ENHANCER_ENTITYANNOTATION));
return enhancement;
}
/**
* Create a new instance with the types enhancer:Enhancement and
* enhancer:TopicAnnotation in the parsed graph along with default properties
* (dc:creator, dc:created and enhancer:extracted-form) and return
* the IRI of the extraction so that engines can further add.
*
* @param metadata the graph
* @param engine the engine
* @param contentItemId the id
*
* @return the URI of the new enhancement instance
*/
public static IRI createTopicEnhancement(Graph metadata,
EnhancementEngine engine, IRI contentItemId){
IRI enhancement = createEnhancement(metadata, engine, contentItemId);
metadata.add(new TripleImpl(enhancement, RDF_TYPE, ENHANCER_TOPICANNOTATION));
return enhancement;
}
/**
* Create a new instance with the types enhancer:Enhancement and
* enhancer:TopicAnnotation in the metadata-graph of the content
* item along with default properties (dc:creator and dc:created) and return
* the IRI of the extraction so that engines can further add
*
* @param ci the ContentItem being under analysis
* @param engine the Engine performing the analysis
* @return the URI of the new enhancement instance
*/
public static IRI createTopicEnhancement(ContentItem ci,
EnhancementEngine engine){
return createTopicEnhancement(ci.getMetadata(), engine, new IRI(ci.getUri().getUnicodeString()));
}
/**
* Create a new enhancement instance in the metadata-graph of the content
* item along with default properties (dc:creator and dc:created) and return
* the IRI of the extraction so that engines can further add. <p>
* <i>NOTE:</i> This method was protected prior to <code>0.12.1</code> (see
* <a href="https://issues.apache.org/jira/browse/STANBOL-1321">STANBOL-1321</a>)
*
* @param ci the ContentItem being under analysis
* @param engine the Engine performing the analysis
*
* @return the URI of the new enhancement instance
* @since 0.12.1
*/
public static IRI createEnhancement(Graph metadata,
EnhancementEngine engine, IRI contentItemId){
LiteralFactory literalFactory = LiteralFactory.getInstance();
IRI enhancement = new IRI("urn:enhancement-"
+ EnhancementEngineHelper.randomUUID());
//add the Enhancement Type
metadata.add(new TripleImpl(enhancement, RDF_TYPE,
ENHANCER_ENHANCEMENT));
//add the extracted from content item
metadata.add(new TripleImpl(enhancement,
ENHANCER_EXTRACTED_FROM, contentItemId));
// creation date
metadata.add(new TripleImpl(enhancement, DC_CREATED,
literalFactory.createTypedLiteral(new Date())));
// the engines that extracted the data
// TODO: add some kind of versioning info for the extractor?
// TODO: use a public dereferencing URI instead? that would allow for
// explicit versioning too
/* NOTE (Rupert Westenthaler 2010-05-26):
* The Idea is to use the ComponentContext in the activate() method of
* an Enhancer to get the bundle name/version and use that as an
* URI for the creator.
* We would need to add getEnhancerID() method to the enhancer interface
* to access this information
*/
metadata.add(new TripleImpl(enhancement, DC_CREATOR,
literalFactory.createTypedLiteral(engine.getClass().getName())));
return enhancement;
}
/**
* Adds the parsed {@link EnhancementEngine} as dc:contributer to the
* enhancement and also sets the dc:modified property accordingly
* @param metadata the {@link ContentItem#getMetadata()}
* @param enhancement the enhancement
* @param engine the engine
*/
public static void addContributingEngine(Graph metadata, IRI enhancement,
EnhancementEngine engine){
LiteralFactory literalFactory = LiteralFactory.getInstance();
// TODO: use a public dereferencing URI instead?
metadata.add(new TripleImpl(enhancement, DC_CONTRIBUTOR,
literalFactory.createTypedLiteral(engine.getClass().getName())));
//set the modification date to the current date.
set(metadata,enhancement,DC_MODIFIED,new Date(),literalFactory);
}
/**
* Create a new extraction instance in the metadata-graph of the content
* item along with default properties (dc:creator and dc:created) and return
* the IRI of the extraction so that engines can further add
*
* @param ci the ContentItem being under analysis
* @param engine the Engine performing the analysis
* @return the URI of the new extraction instance
* @deprecated will be remove with 1.0
* @see EnhancementEngineHelper#createEntityEnhancement(ContentItem, EnhancementEngine)
* @see EnhancementEngineHelper#createTextEnhancement(ContentItem, EnhancementEngine)
*/
@Deprecated
public static IRI createNewExtraction(ContentItem ci,
EnhancementEngine engine) {
LiteralFactory literalFactory = LiteralFactory.getInstance();
Graph metadata = ci.getMetadata();
IRI extraction = new IRI("urn:extraction-"
+ EnhancementEngineHelper.randomUUID());
metadata.add(new TripleImpl(extraction, RDF_TYPE,
ENHANCER_EXTRACTION));
// relate the extraction to the content item
metadata.add(new TripleImpl(extraction,
ENHANCER_RELATED_CONTENT_ITEM, new IRI(ci.getUri().getUnicodeString())));
// creation date
metadata.add(new TripleImpl(extraction, DC_CREATED,
literalFactory.createTypedLiteral(new Date())));
// the engines that extracted the data
// TODO: add some kind of versioning info for the extractor?
// TODO: use a public dereferencing URI instead? that would allow for
// explicit versioning too
metadata.add(new TripleImpl(extraction, DC_CREATOR,
literalFactory.createTypedLiteral(engine.getClass().getName())));
return extraction;
}
/**
* Random UUID generator with re-seedable RNG for the tests.
*
* @return a new Random UUID
*/
public static UUID randomUUID() {
return new UUID(rng.nextLong(), rng.nextLong());
}
/**
* Getter for the first typed literal value of the property for a resource.
*
* @param <T> the java class the literal value needs to be converted to.
* Note that the parsed LiteralFactory needs to support this conversion
* @param graph the graph used to query for the property value
* @param resource the resource
* @param property the property
* @param type the type the literal needs to be converted to
* @param literalFactory the literalFactory
* @return the value
*/
public static <T> T get(Graph graph, BlankNodeOrIRI resource, IRI property, Class<T> type,
LiteralFactory literalFactory){
Iterator<Triple> results = graph.filter(resource, property, null);
if(results.hasNext()){
while(results.hasNext()){
Triple result = results.next();
if(result.getObject() instanceof Literal){
return literalFactory.createObject(type, (Literal)result.getObject());
} else {
log.debug("Triple {} does not have a Literal as object! -> ignore",result);
}
}
log.info("No value for {} and property {} had the requested Type {} -> return null",
new Object[]{resource,property,type});
return null;
} else {
log.debug("No Triple found for {} and property {}! -> return null",resource,property);
return null;
}
}
/**
* Replaces all current values of the property for the resource
* with the parsed value
* @param graph the graph
* @param resource the resource
* @param property the property
* @param value the value
*/
public static void set(Graph graph, BlankNodeOrIRI resource, IRI property, RDFTerm value){
set(graph,resource,property,value == null ? null : singleton(value),null);
}
/**
* Replaces all current values of the property for the resource
* with the parsed values
* @param graph the graph
* @param resource the resource
* @param property the property
* @param value the value
*/
public static void set(Graph graph, BlankNodeOrIRI resource, IRI property, Collection<RDFTerm> values){
set(graph,resource,property,values,null);
}
/**
* Replaces all current values of the property for the resource
* with the parsed value
* @param graph the graph
* @param resource the resource
* @param property the property
* @param value the value. In case it is an instance of {@link RDFTerm} it
* is directly added to the graph. Otherwise the parsed {@link LiteralFactory}
* is used to create a {@link TypedLiteral} for the parsed value.
* @param literalFactory the {@link LiteralFactory} used in case the parsed
* value is not an {@link RDFTerm}
*/
public static void set(Graph graph, BlankNodeOrIRI resource, IRI property,
Object value, LiteralFactory literalFactory){
set(graph,resource,property,value == null ? null : singleton(value),literalFactory);
}
/**
* Replaces all current values of the property for the resource
* with the parsed values
* @param graph the graph
* @param resource the resource
* @param property the property
* @param value the value. In case it is an instance of {@link RDFTerm} it
* is directly added to the graph. Otherwise the parsed {@link LiteralFactory}
* is used to create a {@link TypedLiteral} for the parsed value.
* @param literalFactory the {@link LiteralFactory} used in case the parsed
* value is not an {@link RDFTerm}
*/
public static void set(Graph graph, BlankNodeOrIRI resource, IRI property,
Collection<?> values, LiteralFactory literalFactory){
Iterator<Triple> currentValues = graph.filter(resource, property, null);
while(currentValues.hasNext()){
currentValues.next();
currentValues.remove();
}
if(values != null){
for(Object value : values){
if(value instanceof RDFTerm){
graph.add(new TripleImpl(resource, property, (RDFTerm) value));
} else if (value != null){
graph.add(new TripleImpl(resource, property,
literalFactory.createTypedLiteral(value)));
}
}
}
}
/**
* Getter for the typed literal values of the property for a resource
* @param <T> the java class the literal value needs to be converted to.
* Note that the parsed LiteralFactory needs to support this conversion
* @param graph the graph used to query for the property value
* @param resource the resource
* @param property the property
* @param type the type the literal needs to be converted to
* @param literalFactory the literalFactory
* @return the value
*/
public static <T> Iterator<T> getValues(Graph graph, BlankNodeOrIRI resource,
IRI property, final Class<T> type, final LiteralFactory literalFactory){
final Iterator<Triple> results = graph.filter(resource, property, null);
return new Iterator<T>() {
//TODO: dose not check if the object of the triple is of type IRI
@Override
public boolean hasNext() { return results.hasNext(); }
@Override
public T next() {
return literalFactory.createObject(type, (Literal)results.next().getObject());
}
@Override
public void remove() { results.remove(); }
};
}
/**
* Getter for the first String literal value the property for a resource
* @param graph the graph used to query for the property value
* @param resource the resource
* @param property the property
* @return the value
*/
public static String getString(Graph graph, BlankNodeOrIRI resource, IRI property){
Iterator<Triple> results = graph.filter(resource, property, null);
if(results.hasNext()){
while (results.hasNext()){
Triple result = results.next();
if(result.getObject() instanceof Literal){
return ((Literal)result.getObject()).getLexicalForm();
} else {
log.debug("Triple {} does not have a literal as object! -> ignore",result);
}
}
log.info("No Literal value for {} and property {} -> return null",
resource,property);
return null;
} else {
log.debug("No Triple found for "+resource+" and property "+property+"! -> return null");
return null;
}
}
/**
* Getter for the string literal values the property for a resource
* @param graph the graph used to query for the property value
* @param resource the resource
* @param property the property
* @return the value
*/
public static Iterator<String> getStrings(Graph graph, BlankNodeOrIRI resource, IRI property){
final Iterator<Triple> results = graph.filter(resource, property, null);
return new Iterator<String>() {
//TODO: dose not check if the object of the triple is of type IRI
@Override
public boolean hasNext() { return results.hasNext(); }
@Override
public String next() {
return ((Literal)results.next().getObject()).getLexicalForm();
}
@Override
public void remove() { results.remove(); }
};
}
/**
* Getter for the first value of the data type property for a resource
* @param graph the graph used to query for the property value
* @param resource the resource
* @param property the property
* @return the value
*/
public static IRI getReference(Graph graph, BlankNodeOrIRI resource, IRI property){
Iterator<Triple> results = graph.filter(resource, property, null);
if(results.hasNext()){
while(results.hasNext()){
Triple result = results.next();
if(result.getObject() instanceof IRI){
return (IRI)result.getObject();
} else {
log.debug("Triple "+result+" does not have a IRI as object! -> ignore");
}
}
log.info("No IRI value for {} and property {} -> return null",resource,property);
return null;
} else {
log.debug("No Triple found for {} and property {}! -> return null",resource,property);
return null;
}
}
/**
* Getter for the values of the data type property for a resource.
*
* @param graph the graph used to query for the property value
* @param resource the resource
* @param property the property
* @return The iterator over all the values (
*/
public static Iterator<IRI> getReferences(Graph graph, BlankNodeOrIRI resource, IRI property){
final Iterator<Triple> results = graph.filter(resource, property, null);
return new Iterator<IRI>() {
//TODO: dose not check if the object of the triple is of type IRI
@Override
public boolean hasNext() { return results.hasNext(); }
@Override
public IRI next() { return (IRI)results.next().getObject(); }
@Override
public void remove() { results.remove(); }
};
}
/**
* Comparator that allows to sort a list/array of {@link EnhancementEngine}s
* based on there {@link ServiceProperties#ENHANCEMENT_ENGINE_ORDERING}.
*/
public static final Comparator<EnhancementEngine> EXECUTION_ORDER_COMPARATOR = new Comparator<EnhancementEngine>() {
@Override
public int compare(EnhancementEngine engine1, EnhancementEngine engine2) {
Integer order1 = getEngineOrder(engine1);
Integer order2 = getEngineOrder(engine2);
//start with the highest number finish with the lowest ...
return order1 == order2?0:order1<order2?1:-1;
}
};
/**
* Gets the {@link ServiceProperties#ENHANCEMENT_ENGINE_ORDERING} value
* for the parsed EnhancementEngine. If the Engine does not implement the
* {@link ServiceProperties} interface or does not provide the
* {@link ServiceProperties#ENHANCEMENT_ENGINE_ORDERING} the
* {@link ServiceProperties#ORDERING_DEFAULT} is returned <p>
* This method is guaranteed to NOT return <code>null</code>.
* @param engine the engine
* @return the ordering
*/
public static Integer getEngineOrder(EnhancementEngine engine){
log.debug("getOrder "+engine);
if (engine instanceof ServiceProperties){
log.debug(" ... implements ServiceProperties");
Object value = ((ServiceProperties)engine).getServiceProperties().get(ServiceProperties.ENHANCEMENT_ENGINE_ORDERING);
log.debug(" > value = "+value +" "+value.getClass());
if (value !=null && value instanceof Integer){
return (Integer)value;
}
}
return ServiceProperties.ORDERING_DEFAULT;
}
/**
* Getter for the Resources of fise:TextAnnotations that do have a value
* of the dc:language property. The returned list is sorted by 'fise:confidence'.
* Annotations with missing confidence are ranked last.<p>
* NOTE that the returned list will likely contain annotations for the same language
* if multiple language identification are used in the same {@link Chain}.
* @param graph the graph with the enhancement.
* Typically {@link ContentItem#getMetadata()}
* @return the sorted list of language annotations or an empty list if none.
* @throws IllegalArgumentException if <code>null</code> is parsed as graph
*/
public static List<BlankNodeOrIRI> getLanguageAnnotations(Graph graph){
if(graph == null){
throw new IllegalArgumentException("The parsed graph MUST NOT be NULL!");
}
// I do not use SPARQL, because I do not want to instantiate a QueryEngine
final Map<BlankNodeOrIRI,Double> confidences = new HashMap<BlankNodeOrIRI,Double>();
List<BlankNodeOrIRI> langAnnotations = new ArrayList<BlankNodeOrIRI>();
Iterator<Triple> textAnnoataions = graph.filter(null, RDF_TYPE, ENHANCER_TEXTANNOTATION);
while(textAnnoataions.hasNext()){
BlankNodeOrIRI textAnnotation = textAnnoataions.next().getSubject();
String language = getString(graph, textAnnotation, DC_LANGUAGE);
if(language != null){
Double confidence = null;
try {
confidence = get(graph, textAnnotation, ENHANCER_CONFIDENCE, Double.class, lf);
} catch (InvalidLiteralTypeException e){ // STANBOL-1417: not a double value
try { //try with float
Float fconf = get(graph,textAnnotation,ENHANCER_CONFIDENCE,Float.class,lf);
if(fconf != null){
confidence = Double.valueOf(fconf.doubleValue());
}
} catch (InvalidLiteralTypeException e1){
log.warn("Unable to parse confidence for language annotation "
+ textAnnotation, e);
}
}
confidences.put(textAnnotation,confidence);
langAnnotations.add(textAnnotation);
}
}
if(langAnnotations.size() > 1){
Collections.sort(langAnnotations,new Comparator<BlankNodeOrIRI>() {
@Override
public int compare(BlankNodeOrIRI o1, BlankNodeOrIRI o2) {
Double c1 = confidences.get(o1);
Double c2 = confidences.get(o2);
//decrising order (values without confidence last)
if(c1 == null){
return c2 == null ? 0 : 1;
} else if(c2 == null){
return -1;
} else {
return c2.compareTo(c1);
}
}
});
}
return langAnnotations;
}
/**
* Getter for language identified for (extracted-from) the parsed
* ContentItem. The returned value is the Annotation with the highest
* 'fise:confidence' value - or if no annotations are present - the
* 'dc-terms:language' value of the {@link ContentItem#getUri()}.<p>
* Users that want to obtain all language annotations should use
* {@link #getLanguageAnnotations(Graph)} instead.<p>
* This method ensures a write lock on the {@link ContentItem}.
* @param ci the contentItem
* @return the identified language of the parsed {@link ContentItem}.
* <code>null</code> if not available.
* @throws IllegalArgumentException if <code>null</code> is parsed as content item
* @see #getLanguageAnnotations(Graph)
*/
public static String getLanguage(ContentItem ci){
if(ci == null){
throw new IllegalArgumentException("The parsed ContentItem MUST NOT be NULL!");
}
ci.getLock().readLock().lock();
try {
List<BlankNodeOrIRI> langAnnotations = getLanguageAnnotations(ci.getMetadata());
if(langAnnotations.isEmpty()){ //fallback
return getString(ci.getMetadata(), ci.getUri(), DC_LANGUAGE);
} else {
return getString(ci.getMetadata(), langAnnotations.get(0), DC_LANGUAGE);
}
} finally {
ci.getLock().readLock().unlock();
}
}
/*
* Helper Methods for retrieving EnhancementProperties in 0.12 from the
* parsed ContentItem (see STANBOL-1280).
* NOTE: in 1.0.0 those are obsolete as EnhancementProperties will be parsed
* as additional parameter to the computeEnhancement method.
*/
private static final String EHPROP_NS = NamespaceEnum.ehp.getNamespace();
private static final int EHPROP_NS_LENGTH = EHPROP_NS.length();
/**
* Retrieves the Enhancement Properties for the parsed Engine from the ContentItem.
* <p>
* The returned map will contain: <ol>
* <li> Request scoped properties defined for the parsed enhancement engines
* <li> Request scoped properties defined for chain
* <li> Chain scoped properties defined for the parsed enhancement engine
* <li> Chain scoped properties defined for the chain.
* </ol>
* NOTES: <ul>
* <li> The specification (see <a href="https://issues.apache.org/jira/browse/STANBOL-488">STANBOL-488</a>)
* required properties to start with '<code>enhancer.</code>'. While this
* implementation does not enforce this requirement non compliant properties
* will most likely get filtered earlier and not be part of the returned map.
* <li> Properties of an higher priority do override those with an lower one.
* </ul>
* @param engine the enhancement engine requesting the properties
* @param ci the content item (representing the enhancement request).
* @return The enhancement properties. This is a read/write copy of the
* read-only configuration.
* @see #getEnhancementPropertyDict(EnhancementEngine, ContentItem)
*/
public static Dictionary<String,Object> getEnhancementPropertyDict(EnhancementEngine engine, ContentItem ci){
return new DictionaryAdapter<String,Object>(getEnhancementProperties(engine, ci));
}
/**
* Retrieves the Enhancement Properties for the parsed Engine from the ContentItem.
* <p>
* The returned map will contain: <ol>
* <li> Request scoped properties defined for the parsed enhancement engines
* <li> Request scoped properties defined for chain
* <li> Chain scoped properties defined for the parsed enhancement engine
* <li> Chain scoped properties defined for the chain.
* </ol>
* NOTES: <ul>
* <li> The specification (see <a href="https://issues.apache.org/jira/browse/STANBOL-488">STANBOL-488</a>)
* required properties to start with '<code>enhancer.</code>'. While this
* implementation does not enforce this requirement non compliant properties
* will most likely get filtered earlier and not be part of the returned map.
* <li> Properties of an higher priority do override those with an lower one.
* </ul>
* @param engine the enhancement engine requesting the properties
* @param ci the content item (representing the enhancement request).
* @return The enhancement properties. This is a read/write copy of the
* read-only configuration.
* @see #getEnhancementPropertyDict(EnhancementEngine, ContentItem)
*/
public static Map<String,Object> getEnhancementProperties(EnhancementEngine engine, ContentItem ci){
if(engine == null){
throw new IllegalArgumentException("The parsed EnhancementEngine MUST NOT be NULL");
}
if(ci == null){
throw new IllegalArgumentException("The parsed ContentItem MUST NOT be NULL");
}
//(1) retrieve Chain scope Enhancement Properties
Map<String,Object> chainExProps = getChainExecutionProperties(engine, ci);
//(2) retrieve Request specific EnhancementProperties
//TODO: in future Stanbol version request specific EnhancementProperties
// will get stored in the ExecutionMetadata. Chain level properties
// with the `em:ChainExecution` node and engine specific properties
// with the `em:EngineExecution` node.
// So this code will need to be refactored similar to the above one
Map<String,Object> epContentPart = ContentItemHelper.getRequestPropertiesContentPart(ci);
Map<String,Object> chainProperties = new HashMap<String,Object>();
Map<String,Object> engineProperties = new HashMap<String,Object>();
if(epContentPart != null){
String enginePrefix = new StringBuilder(engine.getName()).append(':').toString();
log.debug("Retrieve EnhancementProperties for Engine {} and ContentItem {}",
engine.getName(), ci.getUri());
//Set<String> engineKeys = new HashSet<String>();
for(Entry<String,Object> entry : epContentPart.entrySet()){
String key = entry.getKey();
int sepIndex = key.indexOf(':');
if(sepIndex < 0){
log.debug(" ... add chain request level property {}='{}'", key,entry.getValue());
chainProperties.put(key, entry.getValue());
} else if(key.startsWith(enginePrefix) && key.length() > enginePrefix.length()){
key = key.substring(enginePrefix.length(),key.length());
log.debug(" ... add engine request level property {}='{}'", key,entry.getValue());
engineProperties.put(key, entry.getValue());
} // else not a enhancement property for the current engine.
}
} else {
log.debug(" - no Request scope EnhancementProperties for ContentItem",ci.getUri());
}
//Now we need to merge the properties based on the Enhancement Properties Precedence
//defined by STANBOL-488
// engineProp > engineEx > chainProp > chainExProp
Map<String,Object> properties = new HashMap<String,Object>(chainExProps);
properties.putAll(chainProperties);
properties.putAll(engineProperties);
return properties;
}
/**
* Getter for the {@link Chain} scoped (chain and chain-engine scoped) properties
* for the parsed enhancement engine and content item.
* @param engine the enhancement engine
* @param ci the content item
* @return the chain scoped enhancement properties. This will not include any
* request scoped properties.
* @since 0.12.1 (<a href="https://issues.apache.org/jira/browse/STANBOL-1361">STANBOL-1361</a>)
*/
public static Map<String,Object> getChainExecutionProperties(EnhancementEngine engine, ContentItem ci) {
if(engine == null){
throw new IllegalArgumentException("The parsed EnhancementEngine MUST NOT be NULL");
}
if(ci == null){
throw new IllegalArgumentException("The parsed ContentItem MUST NOT be NULL");
}
Map<String,Object> chainExProps = new HashMap<String,Object>();
Map<String,Object> engineExProps = new HashMap<String,Object>();
ci.getLock().readLock().lock();
try{
Graph em = ExecutionMetadataHelper.getExecutionMetadata(ci);
//(1.a) retrieve EnhancementProperties from the ep:ExecutionPlan
log.debug("> extract EnhancementProperties form the ExecutionPlan");
BlankNodeOrIRI executionPlanNode = ExecutionMetadataHelper.getExecutionPlanNode(em,
ExecutionMetadataHelper.getChainExecution(em, ci.getUri()));
extractEnhancementProperties(chainExProps, em, executionPlanNode, "Chain Execution");
//(1.b) retrieve Enhancement Properties from the ep:ExectutionNode
// for the parsed EnhancementEngine
log.debug("> extract EnhancementProperties form the ExecutionNode of Engine {}",
engine.getName());
Iterator<Triple> engineExecutions = em.filter(null, ExecutionPlan.ENGINE, new PlainLiteralImpl(engine.getName()));
//NOTE: we expect only a single execution node for an engine, but if
// there are multiple we will merge the properties of those
while(engineExecutions.hasNext()){
BlankNodeOrIRI engineExecution = engineExecutions.next().getSubject();
if(em.contains(new TripleImpl(executionPlanNode, ExecutionPlan.HAS_EXECUTION_NODE, engineExecution))){
extractEnhancementProperties(engineExProps,em, engineExecution, "Engine Execution");
} //else engine execution of a different execution plan
}
} catch(NoSuchPartException e){ //no execution metadata are present
log.debug(" - no ExecutionMetadata are present ...");
} finally {
ci.getLock().readLock().unlock();
}
//finally merge the chain-engine scoped properties into the chain scoped properties
chainExProps.putAll(engineExProps);
return chainExProps;
}
/**
* Extracts all EnhancementProperties from the parsed Node and adds them to
* the parsed map
* @param properties The Map to add the extracted properties. extracted values
* are appended to existing values.
* @param graph the RDF graph containing the data
* @param node the node to extract the properties from
* @param level the name of the level (only used for logging)
*/
private static void extractEnhancementProperties(Map<String,Object> properties, Graph graph,
BlankNodeOrIRI node, String level) {
log.debug(" - extract {} properties from {}", level, node);
Iterator<Triple> props = graph.filter(node, null, null);
while(props.hasNext()){
Triple t = props.next();
String propUri = t.getPredicate().getUnicodeString();
if(propUri.startsWith(EHPROP_NS)){
String prop = propUri.substring(EHPROP_NS_LENGTH);
RDFTerm resource = t.getObject();
Object value = extractEnhancementPropertyValue(resource);
if(value != null && !prop.isEmpty()){
Object current = properties.get(prop);
if(log.isDebugEnabled()){
if(current != null){
log.debug(" ... append {} property '{}' to {}='{}'",
new Object[]{level, value, prop,current});
} else {
log.debug(" ... add {} property {}='{}'",
new Object[]{level, prop, value});
}
}
if(current instanceof Collection<?>){
((Collection) current).add(value);
} else if(current != null){
Collection<Object> col = new ArrayList<Object>(4);
col.add(current);
col.add(value);
properties.put(prop, col);
} else {
properties.put(prop, value);
}
}
}
}
}
/**
* Extracts the EnhancementProperty value from the parsed RDFTerm.<p>
* Currently this will return {@link IRI#getUnicodeString()} or
* {@link Literal#getLexicalForm()}. For {@link BlankNode}s <code>null</code>
* is returned.
* @param r the resource to parse the value form
* @return the parsed value
*/
private static Object extractEnhancementPropertyValue(RDFTerm r) {
Object value;
if(r instanceof IRI){
value = ((IRI)r).getUnicodeString();
} else if(r instanceof Literal){
value = ((Literal) r).getLexicalForm();
} else {
value = null;
}
return value;
}
/**
* Getter for all configuration values for the parsed property.<p>
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned. <code>NULL</code>
* value contained in the parsed value will be silently removed.
* @param config the OSGI component configuration.
* @param property the configuration property
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @return the configuration values for the parsed property or <code>null</code>
* if the property was not contained in the parsed configuration
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws ConfigurationException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float).
* @since 0.12.1
*/
public static <T> Collection<T> getConfigValues(Dictionary<String,Object> config,
String property, Class<T> type) throws ConfigurationException {
if(config == null){
throw new NullPointerException("The parsed Dictionary with the configuration MUST NOT be NULL!");
}
if(property == null){
throw new NullPointerException("The parsed configuration property MUST NOT be NULL!");
}
try {
return parseConfigValues(config.get(property),type);
} catch (IllegalStateException e){
throw new ConfigurationException(property, e.getMessage(),e);
}
}
/**
* Getter for all configuration values for the parsed enhancement property.<p>
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned. <code>NULL</code>
* value contained in the parsed value will be silently removed.
* @param ee the enhancement engine (only used to report errors
* @param ci the content item (only used to report errors)
* @param enhProps the enhancement properties as parsed the the engine with
* the parsed content item
* @param enhProp the enhancement property
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @return the configuration values for the parsed property or <code>null</code>
* if the property was not contained in the parsed enhancement properties
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws EnhancementPropertyException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float).
* @since 0.12.1
*/
public static <T> Collection<T> getConfigValues(EnhancementEngine ee,
ContentItem ci, Map<String,Object> enhProps, String enhProp, Class<T> type)
throws EnhancementPropertyException {
if(enhProp == null){
throw new NullPointerException("The parsed EnhancementProperty MUST NOT be NULL");
}
if(enhProps == null){
throw new NullPointerException("The parsed Map with the EnhancementProperties MUST NOT be NULL");
}
try {
return parseConfigValues(enhProps.get(enhProp), type);
} catch(IllegalStateException e){
throw new EnhancementPropertyException(ee, ci, enhProp, e.getMessage(),e);
}
}
/**
* Extracts multiple Configuration values from the parsed Object value.
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned. <code>NULL</code>
* value contained in the parsed value will be silently removed.
* @param value the value. {@link Collection}s and Arrays are supported for
* multiple values. If the parsed value is of an other type a single value
* is assumed.
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @return the configuration values as parsed from the parsed value
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws IllegalStateException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float.
* @since 0.12.1
*/
public static <T> Collection<T> parseConfigValues(Object value, Class<T> type){
return parseConfigValues(value, type, false);
}
/**
* Getter for all configuration values for the parsed property.<p>
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned.
* @param config the OSGI component configuration.
* @param property the configuration property
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param preseveNullValues if <code>null</code> values in the parsed
* value should be preserved or removed.
* @return the configuration values for the parsed property or <code>null</code>
* if the property was not contained in the parsed configuration
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws ConfigurationException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float).
* @since 0.12.1
*/
public static <T> Collection<T> getConfigValues(Dictionary<String,Object> config,
String property, Class<T> type, boolean preserveNullValues) throws ConfigurationException {
if(config == null){
throw new NullPointerException("The parsed Dictionary with the configuration MUST NOT be NULL!");
}
if(property == null){
throw new NullPointerException("The parsed configuration property MUST NOT be NULL!");
}
try {
return parseConfigValues(config.get(property),type, preserveNullValues);
} catch (IllegalStateException e){
throw new ConfigurationException(property, e.getMessage(),e);
}
}
/**
* Getter for all configuration values for the parsed enhancement property.<p>
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned.
* @param ee the enhancement engine (only used to report errors
* @param ci the content item (only used to report errors)
* @param enhProps the enhancement properties as parsed the the engine with
* the parsed content item
* @param enhProp the enhancement property
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param preseveNullValues if <code>null</code> values in the parsed
* value should be preserved or removed.
* @return the configuration values for the parsed property or <code>null</code>
* if the property was not contained in the parsed enhancement properties.
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws EnhancementPropertyException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float).
* @since 0.12.1
*/
public static <T> Collection<T> getConfigValues(EnhancementEngine ee,
ContentItem ci, Map<String,Object> enhProps, String enhProp, Class<T> type,
boolean preserveNullValues) throws EnhancementPropertyException {
if(enhProp == null){
throw new NullPointerException("The parsed EnhancementProperty MUST NOT be NULL");
}
if(enhProps == null){
throw new NullPointerException("The parsed Map with the EnhancementProperties MUST NOT be NULL");
}
try {
return parseConfigValues(enhProps.get(enhProp), type, preserveNullValues);
} catch(IllegalStateException e){
throw new EnhancementPropertyException(ee, ci, enhProp, e.getMessage(),e);
}
}
/**
* Extracts multiple Configuration values from the parsed Object value.
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned.
* @param value the value. {@link Collection}s and Arrays are supported for
* multiple values. If the parsed value is of an other type a single value
* is assumed.
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param preseveNullValues if <code>null</code> values in the parsed
* value should be preserved or removed.
* @return the configuration values as parsed from the parsed value
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws IllegalStateException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float.
* @since 0.12.1
*/
public static <T> Collection<T> parseConfigValues(Object value, Class<T> type,
boolean preseveNullValues){
return parseConfigValues(value,type, null, preseveNullValues);
}
/**
* Getter for all configuration values for the parsed property.<p>
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned. <code>NULL</code>
* value contained in the parsed value will be silently removed.
* @param config the OSGI component configuration.
* @param property the configuration property
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param configValues The collection to add the parsed configuration values
* to. If <code>null</code> an {@link ArrayList} will be used.
* @return the configuration values for the parsed property or <code>null</code>
* if the property was not contained in the parsed configuration
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws ConfigurationException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float).
* @since 0.12.1
*/
public static <T> Collection<T> getConfigValues(Dictionary<String,Object> config,
String property, Class<T> type, Collection<T> configValues) throws ConfigurationException {
if(config == null){
throw new NullPointerException("The parsed Dictionary with the configuration MUST NOT be NULL!");
}
if(property == null){
throw new NullPointerException("The parsed configuration property MUST NOT be NULL!");
}
try {
return parseConfigValues(config.get(property),type, configValues);
} catch (IllegalStateException e){
throw new ConfigurationException(property, e.getMessage(),e);
}
}
/**
* Getter for all configuration values for the parsed enhancement property.<p>
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned. <code>NULL</code>
* value contained in the parsed value will be silently removed.
* @param ee the enhancement engine (only used to report errors
* @param ci the content item (only used to report errors)
* @param enhProps the enhancement properties as parsed the the engine with
* the parsed content item
* @param enhProp the enhancement property
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param configValues The collection to add the parsed configuration values
* to. If <code>null</code> an {@link ArrayList} will be used.
* @return the configuration values for the parsed property or <code>null</code>
* if the property was not contained in the parsed enhancement properties
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws EnhancementPropertyException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float).
* @since 0.12.1
*/
public static <T> Collection<T> getConfigValues(EnhancementEngine ee,
ContentItem ci, Map<String,Object> enhProps, String enhProp, Class<T> type
,Collection<T> configValues) throws EnhancementPropertyException {
if(enhProp == null){
throw new NullPointerException("The parsed EnhancementProperty MUST NOT be NULL");
}
if(enhProps == null){
throw new NullPointerException("The parsed Map with the EnhancementProperties MUST NOT be NULL");
}
try {
return parseConfigValues(enhProps.get(enhProp), type, configValues);
} catch(IllegalStateException e){
throw new EnhancementPropertyException(ee, ci, enhProp, e.getMessage(),e);
}
}
/**
* Extracts multiple Configuration values from the parsed Object value.
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned.
* @param value the value. {@link Collection}s and Arrays are supported for
* multiple values. If the parsed value is of an other type a single value
* is assumed.
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param configValues The collection to add the parsed configuration values
* to. If <code>null</code> an {@link ArrayList} will be used.
* @return the configuration values as parsed from the parsed value.
* <code>null</code> if the parsed value was <code>null</code>.
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws IllegalStateException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float.
* @since 0.12.1
*/
public static <T> Collection<T> parseConfigValues(Object value,
Class<T> type, Collection<T> configValues){
return parseConfigValues(value, type, configValues,false);
}
/**
* Getter for all configuration values for the parsed property.<p>
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned.
* @param config the OSGI component configuration.
* @param property the configuration property
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param configValues The collection to add the parsed configuration values
* to. If <code>null</code> an {@link ArrayList} will be used.
* @param preseveNullValues if <code>null</code> values in the parsed
* value should be preserved or removed.
* @return the configuration values for the parsed property or <code>null</code>
* if the property was not contained in the parsed configuration
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws ConfigurationException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float).
* @since 0.12.1
*/
public static <T> Collection<T> getConfigValues(Dictionary<String,Object> config,
String property, Class<T> type, Collection<T> configValues, boolean preserveNullValues)
throws ConfigurationException {
if(config == null){
throw new NullPointerException("The parsed Dictionary with the configuration MUST NOT be NULL!");
}
if(property == null){
throw new NullPointerException("The parsed configuration property MUST NOT be NULL!");
}
try {
return parseConfigValues(config.get(property),type, configValues, preserveNullValues);
} catch (IllegalStateException e){
throw new ConfigurationException(property, e.getMessage(),e);
}
}
/**
* Getter for all configuration values for the parsed enhancement property.<p>
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned.
* @param ee the enhancement engine (only used to report errors
* @param ci the content item (only used to report errors)
* @param enhProps the enhancement properties as parsed the the engine with
* the parsed content item
* @param enhProp the enhancement property
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param configValues The collection to add the parsed configuration values
* to. If <code>null</code> an {@link ArrayList} will be used.
* @param preseveNullValues if <code>null</code> values in the parsed
* value should be preserved or removed.
* @return the configuration values for the parsed property or <code>null</code>
* if the property was not contained in the parsed enhancement properties
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws EnhancementPropertyException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float).
* @since 0.12.1
*/
public static <T> Collection<T> getConfigValues(EnhancementEngine ee,
ContentItem ci, Map<String,Object> enhProps, String enhProp, Class<T> type
,Collection<T> configValues, boolean preserveNullValues) throws EnhancementPropertyException {
if(enhProp == null){
throw new NullPointerException("The parsed EnhancementProperty MUST NOT be NULL");
}
if(enhProps == null){
throw new NullPointerException("The parsed Map with the EnhancementProperties MUST NOT be NULL");
}
try {
return parseConfigValues(enhProps.get(enhProp), type, configValues, preserveNullValues);
} catch(IllegalStateException e){
throw new EnhancementPropertyException(ee, ci, enhProp, e.getMessage(),e);
}
}
/**
* Extracts multiple Configuration values from the parsed Object value.
* This does support arrays and {@link Collection}s for multiple values.
* In any other case a single value collection will be returned.
* @param value the value. {@link Collection}s and Arrays are supported for
* multiple values. If the parsed value is of an other type a single value
* is assumed.
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param configValues The collection to add the parsed configuration values
* to. If <code>null</code> an {@link ArrayList} will be used.
* @param preseveNullValues if <code>null</code> values in the parsed
* value should be preserved or removed.
* @return the configuration values as parsed from the parsed value.
* <code>null</code> if the parsed value was <code>null</code>.
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws IllegalStateException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float.
* @since 0.12.1
*/
public static <T> Collection<T> parseConfigValues(Object value,
Class<T> type, Collection<T> configValues, boolean preseveNullValues){
if(value == null){
return null;
}
final Collection<?> values;
if(value instanceof Collection<?>){
values = (Collection<?>)value;
} else if(value.getClass().isArray()){
Class<?> componentType = value.getClass().getComponentType();
if(componentType.isPrimitive()){
int len = Array.getLength(value);
List<Object> av = new ArrayList<Object>(len);
for(int i = 0; i < len;i++){
av.add(Array.get(value, i));
}
values = av;
} else {
values = Arrays.asList((Object[])value);
}
} else {
values = Collections.singleton(value);
}
final Constructor<T> constructor = getConfigTypeConstructor(type);
if(configValues == null){
//no idea why I have to cast to C ...
configValues = new ArrayList<T>(values.size());
}
for(Object o : values){
if(o == null){
if(preseveNullValues){
configValues.add(null);
} //else skip
} else {
configValues.add(parseConfigValue(o, type, constructor));
}
}
return configValues;
}
/**
* Getter for the first configuration value
* @param config the OSGI component configuration
* @param property the configuration property
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param preseveNullValues if <code>null</code> values in the parsed
* value should be preserved or removed.
* @return the configuration value as parsed from the parsed value
* @throws ConfigurationException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float.
* @throws NullPointerException if the parsed {@link Dictionary} with the component
* configuration, the configuration property or the type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @since 0.12.1
*/
public static final <T> T getFirstConfigValue(Dictionary<String,Object> config,
String property,Class<T> type) throws ConfigurationException {
if(config == null){
throw new NullPointerException("The parsed configuration MUST NOT be NULL!");
}
if(property == null){
throw new NullPointerException("The pased configuration property MUST NOT be NULL!");
}
try {
return parseFirstConfigValue(config.get(property), type);
} catch(IllegalStateException e){
throw new ConfigurationException(property, e.getMessage(),e);
}
}
/**
* Getter for the first value of an EnhancementProperty
* @param ee the enhancement engine (only used for reporting errors)
* @param ci the content item (only used for reporting errors)
* @param enhProps the map with the enhancement properties
* @param enhProp the enhancement property
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param preseveNullValues if <code>null</code> values in the parsed
* value should be preserved or removed.
* @return the configuration value as parsed from the parsed value
* @throws EnhancementPropertyException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float.
* @throws NullPointerException if the parsed {@link Map} with the enhancement
* properties, the enhancement property or the type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @since 0.12.1
*/
public static final <T> T getFirstConfigValue(EnhancementEngine ee,
ContentItem ci, Map<String,Object> enhProps,
String enhProp, Class<T> type) throws EnhancementPropertyException {
if(enhProp == null){
throw new NullPointerException("The parsed EnhancementProperty MUST NOT be NULL");
}
if(enhProps == null){
throw new NullPointerException("The parsed Map with the EnhancementProperties MUST NOT be NULL");
}
try {
return parseFirstConfigValue(enhProps.get(enhProp), type);
} catch(IllegalStateException e){
throw new EnhancementPropertyException(ee, ci, enhProp, e.getMessage(),e);
}
}
/**
* Extracts a single Configuration values from the parsed Object value.
* In case the parsed value is an Array or a Collection it will take the
* first non <code>null</code> value.
* @param value the value. In case of an Array or a Collection it will take
* the first non <code>null</code> value
* @param type the desired type of the configuration values. The parsed type
* MUST define a {@link Constructor} taking a {@link String} as only parameter.
* @param preseveNullValues if <code>null</code> values in the parsed
* value should be preserved or removed.
* @return the configuration value as parsed from the parsed value
* @throws NullPointerException if the parsed type is <code>null</code>
* @throws IllegalArgumentException if the parsed type does not have a
* {@link Constructor} that takes a {@link String} as only parameter; if the
* {@link Constructor} is not visible or can not be instantiated (e.g.
* because the parsed type is an Interface or an abstract class).
* @throws IllegalStateException if the parsed type can not be instantiated
* if one of the parsed values (e.g. if {@link Float} is used as type and
* one of the parsed values is not a valid float.
* @since 0.12.1
*/
public static final <T> T parseFirstConfigValue(Object value, Class<T> type){
if(value == null){
return null;
}
Object first = null;
if(value instanceof Collection<?>){
Collection<?> c = (Collection<?>)value;
if(c.isEmpty()){
return null;
} else {
Iterator<?> it = c.iterator();
while(first == null && it.hasNext()){
first = it.next();
}
}
} else if(value.getClass().isArray()){
Class<?> componentType = value.getClass().getComponentType();
int len = Array.getLength(value);
if(len < 1){
return null;
} else {
if(componentType.isPrimitive()){
first = Array.get(value, 0);
} else {
for(int i=0; first == null && i < len; i++){
first = Array.get(value, i);
}
}
}
} else {
first = value;
}
return parseConfigValue(first, type, getConfigTypeConstructor(type));
}
/**
* Internally used to get the config value for the parsed value and type.
* @param value
* @param type
* @param constructor the constructor typically retrieved by calling
* {@link #getConfigTypeConstructor(Class)} for the type
* @return the value
*/
private static <T> T parseConfigValue(Object value, Class<T> type, final Constructor<T> constructor) {
if(value == null){
return null;
}
T configValue;
if(constructor == null){
configValue = type.cast(value.toString());
} else {
try {
configValue = constructor.newInstance(value.toString());
} catch (InstantiationException e) {
throw new IllegalArgumentException("Unable to instantiate the "
+ "parsed value type '" + type.getClass().getName() +"'!", e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Unable to access the "
+ "constructor of the parsed value type '"
+ type.getClass().getName() + "'!", e);
} catch (InvocationTargetException e) {
throw new IllegalStateException("Unable to instantiate the "
+ "parsed value type '" + type.getClass().getName()
+ "' with the String value '"+value+ "'!", e);
}
}
return configValue;
}
/**
* Internally used to get the String parameter constructor for the parsed
* config value type
* @param type
* @return
*/
private static <T> Constructor<T> getConfigTypeConstructor(Class<T> type) {
final Constructor<T> constructor;
if(String.class.equals(type)){
constructor = null;
} else {
try {
constructor = type.getConstructor(String.class);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Parsed config value type '"
+ type.getClass().getName()+ "' does not define a Constructor "
+ "that takes a String as only parameter!", e);
}
}
return constructor;
}
}