/*
* 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.entityhub.core.site;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import org.apache.stanbol.entityhub.servicesapi.model.ManagedEntityState;
import org.apache.stanbol.entityhub.servicesapi.model.MappingState;
import org.apache.stanbol.entityhub.servicesapi.site.License;
import org.apache.stanbol.entityhub.servicesapi.site.ManagedSiteConfiguration;
import org.apache.stanbol.entityhub.servicesapi.site.ReferencedSiteConfiguration;
import org.apache.stanbol.entityhub.servicesapi.site.Site;
import org.apache.stanbol.entityhub.servicesapi.site.SiteConfiguration;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of the {@link SiteConfiguration} interface.<p>
* While this implementation also provides setter methods when used within an
* OSGI environment configurations are typically read only as the configuration
* is provided as parameter to the component activation method.
*
* @author Rupert Westenthaler
*
*/
public class SiteConfigurationImpl implements SiteConfiguration {
/**
* The logger
*/
@SuppressWarnings("unused")
private final static Logger log = LoggerFactory.getLogger(SiteConfigurationImpl.class);
/**
* Internally used to store the configuration.
*/
protected final Dictionary<String,Object> config;
/**
* Creates a configuration based on the parsed parameter. The parsed
* configuration is validated.<p>
* Changes to the parsed configuration do have no affect on the state of
* the created instance.<p>
* OSGI specific metadata are removed from the parsed configuration.
* @param parsed the configuration used for the initialisation.
* @throws ConfigurationException if the parsed properties are not valid
*/
protected SiteConfigurationImpl(Dictionary<String,Object> parsed) throws ConfigurationException {
this();
//now add the parsed configuration
if(parsed != null){
//copy over the elements
for(Enumeration<String> it = parsed.keys();it.hasMoreElements();) {
String key = it.nextElement();
config.put(key,parsed.get(key));
}
//Remove OSGI specific metadata
config.remove(Constants.SERVICE_ID);
config.remove(Constants.SERVICE_PID);
config.remove(Constants.OBJECTCLASS);
config.remove(Constants.SYSTEM_BUNDLE_LOCATION);
config.remove(Constants.SYSTEM_BUNDLE_SYMBOLICNAME);
}
validateConfiguration();
}
/**
* Constructs an empty configuration
*/
protected SiteConfigurationImpl(){
this.config = new Hashtable<String,Object>();
}
/**
* Validates if the current configuration is valid and also perform type
* transformations on values (e.g. converting string values to the enumerated
* types). if the validation fails an Exception MUST BE thrown.<p>
* @throws ConfigurationException if the validation fails
*/
protected void validateConfiguration() throws ConfigurationException {
if(getId() == null || getId().isEmpty()){
throw new ConfigurationException(ID, "The id of a ReferencedSite configuration MUST NOT be NULL nor empty!");
}
//check if the prefixes can be converted to an String[]
try {
setEntityPrefixes(getEntityPrefixes());
} catch (IllegalArgumentException e) {
throw new ConfigurationException(ENTITY_PREFIX, e.getMessage(),e);
}
//check the configured licenses and create the License array
setLicenses(getLicenses());
//check if the fieldMappings can be converted to an String[]
try {
setFieldMappings(getFieldMappings());
} catch (IllegalArgumentException e) {
throw new ConfigurationException(SITE_FIELD_MAPPINGS, e.getMessage(),e);
}
try {
setDefaultMappedEntityState(getDefaultMappedEntityState());
} catch (IllegalArgumentException e) {
throw new ConfigurationException(DEFAULT_MAPPING_STATE,
String.format("Unknown default MappingState (%s=%s) for Site %s! Valid values are %s ",
DEFAULT_MAPPING_STATE,config.get(DEFAULT_MAPPING_STATE),getId(),
Arrays.toString(MappingState.values())),e);
}
try {
setDefaultManagedEntityState(getDefaultManagedEntityState());
} catch (IllegalArgumentException e) {
throw new ConfigurationException(DEFAULT_SYMBOL_STATE,
String.format("Unknown default SymbolState (%s=%s) for Site %s! Valid values are %s ",
DEFAULT_SYMBOL_STATE,config.get(DEFAULT_SYMBOL_STATE),getId(),
Arrays.toString(ManagedEntityState.values()),e));
}
//check if the default expire duration is a number
try {
setDefaultExpireDuration(getDefaultExpireDuration());
} catch (NumberFormatException e) {
throw new ConfigurationException(DEFAULT_EXPIRE_DURATION,
String.format("Unable to parse Number for %s=%s. Will return -1 (no expire duration)",
DEFAULT_EXPIRE_DURATION,config.get(DEFAULT_EXPIRE_DURATION)),e);
}
}
@Override
public final long getDefaultExpireDuration() {
Object duration = config.get(DEFAULT_EXPIRE_DURATION);
if(duration == null){
return 0;
} else if(duration instanceof Long){
return (Long) duration;
} else {
return Long.valueOf(duration.toString());
}
}
/**
*
* @param duration
* @throws UnsupportedOperationException in case this configuration is {@link #readonly}
* @see #getDefaultExpireDuration()
*/
public final void setDefaultExpireDuration(long duration) throws UnsupportedOperationException {
if(duration <= 0){
config.remove(DEFAULT_EXPIRE_DURATION);
} else {
config.put(DEFAULT_EXPIRE_DURATION, duration);
}
}
@Override
public final MappingState getDefaultMappedEntityState() {
Object defaultMappingState = config.get(DEFAULT_MAPPING_STATE);
if(defaultMappingState == null){
return null;
} else if(defaultMappingState instanceof MappingState){
return (MappingState)defaultMappingState;
} else {
return MappingState.valueOf(defaultMappingState.toString());
}
}
/**
* Setter for the default state of Mappings created between Entities of this
* Site and Entities managed by the Entityhub. If this configuration is not
* present created mappings will have the default state as configured by the
* Entityhub.
* @param state the default state for new Entity mappings.
* @throws UnsupportedOperationException in case this configuration is {@link #readonly}
* @see #getDefaultMappedEntityState()
*/
public final void setDefaultMappedEntityState(MappingState state) throws UnsupportedOperationException {
if(state == null){
config.remove(DEFAULT_MAPPING_STATE);
} else {
config.put(DEFAULT_MAPPING_STATE, state);
}
}
@Override
public final ManagedEntityState getDefaultManagedEntityState() {
Object defaultSymbolState = config.get(DEFAULT_SYMBOL_STATE);
if(defaultSymbolState == null){
return null;
} else if(defaultSymbolState instanceof ManagedEntityState){
return (ManagedEntityState)defaultSymbolState;
} else {
return ManagedEntityState.valueOf(defaultSymbolState.toString());
}
}
/**
* Setter for the default state of Entities after importing them into the
* Entityhub. If this configuration is not present Entities will have the
* default state set for the Entityhub.
* @param state the state or <code>null</code> to remove this configuration
* @throws UnsupportedOperationException in case this configuration is {@link #readonly}
* @see #getDefaultManagedEntityState()
*/
public final void setDefaultManagedEntityState(ManagedEntityState state) throws UnsupportedOperationException {
if(state == null){
config.remove(DEFAULT_SYMBOL_STATE);
} else {
config.put(DEFAULT_SYMBOL_STATE, state);
}
}
@Override
public final String getAttribution() {
Object attribution = config.get(SITE_ATTRIBUTION);
return attribution == null || attribution.toString().isEmpty() ?
null : attribution.toString();
}
/**
*
* @param attribution
* @throws UnsupportedOperationException in case this configuration is {@link #readonly}
* @see #getAttribution()
*/
public final void setAttribution(String attribution) throws UnsupportedOperationException {
if(attribution == null || attribution.isEmpty()){
config.remove(SITE_ATTRIBUTION);
} else {
config.put(SITE_ATTRIBUTION, attribution);
}
}
@Override
public String getAttributionUrl() {
Object attribution = config.get(SITE_ATTRIBUTION_URL);
return attribution == null || attribution.toString().isEmpty() ?
null : attribution.toString();
}
/**
*
* @param attribution
* @throws UnsupportedOperationException in case this configuration is {@link #readonly}
* @see #getAttribution()
*/
public final void setAttributionUrl(String attributionUrl) throws UnsupportedOperationException {
if(attributionUrl == null || attributionUrl.isEmpty()){
config.remove(SITE_ATTRIBUTION_URL);
} else {
config.put(SITE_ATTRIBUTION_URL, attributionUrl);
}
}
@Override
public final String getDescription() {
Object description = config.get(DESCRIPTION);
return description == null || description.toString().isEmpty() ?
null : description.toString();
}
/**
* Setter for the description of the {@link Site}. If set to
* <code>null</code> or an empty string this configuration will be removed.
* @param description the description
* @throws UnsupportedOperationException in case this configuration is {@link #readonly}
* @see #getDescription()
*/
public final void setDescription(String description) throws UnsupportedOperationException {
if(description == null || description.isEmpty()){
config.remove(DESCRIPTION);
} else {
config.put(DESCRIPTION, description);
}
}
@Override
public final String[] getEntityPrefixes() {
String[] prefixes = getStringValues(ENTITY_PREFIX);
return prefixes == null ? new String[]{} : prefixes;
}
/**
* Setter for the Entity prefixes (typically the namespace or the host name)
* of the entities provided by this site. Because Sites might provide Entities
* with different namespaces this site allows to parse an array. Setting the
* prefixes to <code>null</code> or an empty array will cause that this site
* is ask for all requested entities.
* @param prefixes The array with the prefixes or <code>null</code> to
* remove any configured prefixes
* @throws UnsupportedOperationException in case this configuration is {@link #readonly}
* @see #getEntityPrefixes()
*/
public final void setEntityPrefixes(String[] prefixes) throws UnsupportedOperationException {
if(prefixes == null || prefixes.length < 1){
config.remove(ENTITY_PREFIX);
} else {
config.put(ENTITY_PREFIX, prefixes);
}
}
@Override
public final String getId() {
Object id = config.get(ID);
return id == null ? null : id.toString();
}
/**
* Setter for the id of the referenced site
* @param id the id
* @throws UnsupportedOperationException in case this configuration is {@link #readonly}
* @throws IllegalArgumentException in case the parsed ID is <code>null</code> or an empty String
* @see #getId()
*/
public final void setId(String id) throws UnsupportedOperationException, IllegalArgumentException {
if(id == null){
throw new IllegalArgumentException("The ID of the Site MUST NOT be set to NULL!");
} else if (id.isEmpty()){
throw new IllegalArgumentException("The ID of the Site MIST NOT be set to an empty String!");
} else {
config.put(ID, id);
}
}
@Override
public final License[] getLicenses() {
//get Licenses based on related keys
int elements = 0;
String[] names = getLicenseName();
if(names == null){
names = new String[]{};
} else {
elements = Math.max(elements, names.length);
}
String[] texts = getLicenseText();
if(texts == null){
texts = new String[]{};
} else {
elements = Math.max(elements, texts.length);
}
String[] urls = getLicenseUrl();
if(urls == null){
urls = new String[]{};
} else {
elements = Math.max(elements, urls.length);
}
Collection<License> licenseList = new ArrayList<License>();
for(int i=0;i<elements;i++){
try {
licenseList.add(new License(
names.length>i?names[i]:null,
urls.length>i?urls[i]:null,
texts.length>i?texts[i]:null));
} catch(IllegalArgumentException e){
//ignore if name, text and url == null and/or empty
}
}
return licenseList.isEmpty() ? new License[]{} :
licenseList.toArray(new License[licenseList.size()]);
}
/**
* Setter for the {@link License} information. This method stores the name,
* text and url of the license as strings in the according fields of the
* configuration.
* @param licenses the licenses to store. <code>null</code> or an empty
* array to remove existing values
* @throws IllegalArgumentException if the parsed array contains a <code>null</code>
* element
* @throws UnsupportedOperationException if the configuration is read-only
*/
public final void setLicenses(License[] licenses) throws IllegalArgumentException, UnsupportedOperationException {
if(licenses == null || licenses.length < 1){
config.remove(SITE_LICENCE_NAME);
config.remove(SITE_LICENCE_TEXT);
config.remove(SITE_LICENCE_URL);
} else {
String[] names = new String[licenses.length];
String[] texts = new String[licenses.length];
String[] urls = new String[licenses.length];
for(int i=0;i<licenses.length;i++){
if(licenses[i] != null){
names[i] = licenses[i].getName();
texts[i] = licenses[i].getText();
urls[i] = licenses[i].getUrl();
} else {
throw new IllegalArgumentException("The parsed License array" +
"MUST NOT contain a NULL element! (parsed: "+
Arrays.toString(licenses)+")");
}
}
config.put(SITE_LICENCE_NAME, names);
config.put(SITE_LICENCE_TEXT, texts);
config.put(SITE_LICENCE_URL, urls);
}
}
/**
* Internally used to get the names of the licenses
* @return
*/
private String[] getLicenseName() {
return getStringValues(SITE_LICENCE_NAME);
}
/**
* Internally used to get the texts of the licenes
* @return
*/
private String[] getLicenseText() {
return getStringValues(SITE_LICENCE_TEXT);
}
/**
* Internally used to get the urls of the page describing the license
* @return
*/
private String[] getLicenseUrl() {
return getStringValues(SITE_LICENCE_URL);
}
@Override
public String getName() {
Object name = config.get(NAME);
//use ID as fallback!
return name == null || name.toString().isEmpty() ? getId() : name.toString();
}
/**
* Setter for the name of the Referenced Site. Note that if the name is not
* present the {@link #getId() id} will be used as name.
* @param name the name of the site or <code>null</code> to remove it (and
* use the {@link #getId() id} also as name
* @throws UnsupportedOperationException in case this configuration is {@link #readonly}
* @see #getName()
*/
public final void setName(String name) throws UnsupportedOperationException {
if(name == null || name.isEmpty()){
config.remove(NAME);
} else {
config.put(NAME, name);
}
}
/**
* Provides direct access to the internal configuration
* @return the configuration wrapped by this class
*/
public final Dictionary<String,Object> getConfiguration(){
return config;
}
@Override
public String[] getFieldMappings() {
return getStringValues(SITE_FIELD_MAPPINGS);
}
/**
* Setter for the mappings of a site. This mappings are used in case an
* Entity of this site is imported to the Entityhub. Parsing <code>null</code>
* or an empty array will cause all existing mappings to be removed.
* @param mappings the mappings
* @throws UnsupportedOperationException
*/
public final void setFieldMappings(String[] mappings) throws UnsupportedOperationException {
if(mappings == null || mappings.length < 1){
config.remove(SITE_FIELD_MAPPINGS);
} else {
config.put(SITE_FIELD_MAPPINGS, mappings);
}
}
/**
* Internally used to parse String[] based on key values. This method
* supports Stirng, Stirng[] and Iterables<?>. For Iterables<?>
* the {@link Object#toString()} is used and <code>null</code> elementes are
* kept.
* @return
*/
protected final String[] getStringValues(String key) {
Object values = config.get(key);
if(values == null){
return null;
} else if (values instanceof String[]){
if(((String[])values).length<1){ //return null if empty
return null;
} else {
return (String[]) values;
}
} else if (values instanceof Iterable<?>){
Collection<String> prefixes = new ArrayList<String>();
for(Object value : (Iterable<?>)values){
prefixes.add(value==null ? null : value.toString());
}
return prefixes.toArray(new String[prefixes.size()]);
} else if(values instanceof String) {
return new String[]{values.toString()};
} else {
throw new IllegalArgumentException(
String.format("Unable to parse Sting[] for field %s form type %s (supported are String, String[] and Iterables)",
key,values.getClass()));
}
}
}