/*
* 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 org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.apache.stanbol.commons.namespaceprefix.NamespacePrefixService;
import org.apache.stanbol.entityhub.core.mapping.DefaultFieldMapperImpl;
import org.apache.stanbol.entityhub.core.mapping.FieldMappingUtils;
import org.apache.stanbol.entityhub.core.mapping.ValueConverterFactory;
import org.apache.stanbol.entityhub.core.model.InMemoryValueFactory;
import org.apache.stanbol.entityhub.core.query.DefaultQueryFactory;
import org.apache.stanbol.entityhub.core.utils.OsgiUtils;
import org.apache.stanbol.entityhub.servicesapi.mapping.FieldMapper;
import org.apache.stanbol.entityhub.servicesapi.mapping.FieldMapping;
import org.apache.stanbol.entityhub.servicesapi.model.Representation;
import org.apache.stanbol.entityhub.servicesapi.model.ValueFactory;
import org.apache.stanbol.entityhub.servicesapi.query.FieldQuery;
import org.apache.stanbol.entityhub.servicesapi.query.FieldQueryFactory;
import org.apache.stanbol.entityhub.servicesapi.query.QueryResultList;
import org.apache.stanbol.entityhub.servicesapi.util.ModelUtils;
import org.apache.stanbol.entityhub.servicesapi.yard.Cache;
import org.apache.stanbol.entityhub.servicesapi.yard.CacheStrategy;
import org.apache.stanbol.entityhub.servicesapi.yard.Yard;
import org.apache.stanbol.entityhub.servicesapi.yard.YardException;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.component.ComponentContext;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is the Implementation of the {@link Cache} Interface as defined by the
* entityhub services API.<p>
* Currently the dependency to the Cache is managed vial a {@link ServiceTracker}.
* This means that the Cache is activated/keeps active even if the Yard is not
* available or is disabled.
* If the Yard is not available all Yard related methods like get/store/remove/
* find(..) throw {@link YardException}s.<p>
* TODO This is not the intended way to do it, but because I have no Idea how
* to start/stop a OSGI Component from within a class :(
*
* @author Rupert Westenthaler
* @see Cache
*/
@Component(
configurationFactory = true,
policy = ConfigurationPolicy.REQUIRE, //the baseUri is required!
specVersion = "1.1",
metatype = true,
immediate = true)
@Service(value = Cache.class)
@Properties(
value = {
@Property(name = Cache.CACHE_YARD),
@Property(name = Cache.ADDITIONAL_MAPPINGS, cardinality = 1000)})
public class CacheImpl implements Cache {
private Logger log = LoggerFactory.getLogger(CacheImpl.class);
public static final String CACHE_FACTORY_NAME = "org.apache.stanbol.entityhub.yard.CacheFactory";
private ServiceTracker yardTracker;
//private Yard yard;
private String yardId;
private boolean initWithYard = false;
private FieldMapper baseMapper;
private FieldMapper additionalMapper;
private ComponentContext context;
@Reference(cardinality=ReferenceCardinality.OPTIONAL_UNARY)
protected NamespacePrefixService nsPrefixService;
@Activate
protected void activate(ComponentContext context) throws ConfigurationException, YardException, IllegalStateException, InvalidSyntaxException {
if (context == null || context.getProperties() == null) {
throw new IllegalStateException(String.format("Invalid ComponentContext parsed in activate (context=%s)", context));
}
this.context = context;
yardId = OsgiUtils.checkProperty(context.getProperties(), CACHE_YARD).toString();
String cacheFilter = String.format("(&(%s=%s)(%s=%s))", Constants.OBJECTCLASS, Yard.class.getName(), Yard.ID, yardId);
yardTracker = new ServiceTracker(context.getBundleContext(), context.getBundleContext().createFilter(cacheFilter), null);
yardTracker.open();
}
/**
* Lazy initialisation of the yard the first time the yard is ued by this
* cache
*
* @param yard the yard instance. MUST NOT be NULL!
* @throws YardException
*/
protected void initWithCacheYard(Yard yard) throws YardException {
//(1) Read the base mappings from the Yard
this.baseMapper = CacheUtils.loadBaseMappings(yard,nsPrefixService);
//(2) Init the additional mappings based on the configuration
Object mappings = context.getProperties().get(Cache.ADDITIONAL_MAPPINGS);
FieldMapper configuredMappings = null;
if (mappings instanceof String[] && ((String[]) mappings).length > 0) {
configuredMappings = new DefaultFieldMapperImpl(ValueConverterFactory.getDefaultInstance());
for (String mappingString : (String[]) mappings) {
FieldMapping fieldMapping = FieldMappingUtils.parseFieldMapping(mappingString, nsPrefixService);
if (fieldMapping != null) {
configuredMappings.addMapping(fieldMapping);
}
}
//check if there are valid mappings
if (configuredMappings.getMappings().isEmpty()) {
configuredMappings = null; //if no mappings where found set to null
}
}
FieldMapper yardAdditionalMappings = CacheUtils.loadAdditionalMappings(yard,nsPrefixService);
if (yardAdditionalMappings == null) {
if (configuredMappings != null) {
setAdditionalMappings(yard, configuredMappings);
}
} else if (!yardAdditionalMappings.equals(configuredMappings)) {
//this may also set the additional mappings to null!
log.info("Replace Additional Mappings for Cache " + yardId + "with Mappings configured by OSGI");
setAdditionalMappings(yard, configuredMappings);
} //else current config equals configured one -> nothing to do!
initWithYard = true;
}
@Deactivate
protected void deactivate(ComponentContext context) {
// context.getBundleContext().removeServiceListener(this);
this.yardTracker.close();
this.yardTracker = null;
this.initWithYard = false;
this.yardId = null;
this.baseMapper = null;
this.additionalMapper = null;
this.context = null;
}
@Override
public boolean isAvailable() {
return yardTracker.getService() != null;
}
@Override
public CacheStrategy isField(String field) {
// TODO Auto-generated method stub
return null;
}
@Override
public CacheStrategy isLanguage(String lang) {
// TODO Auto-generated method stub
return null;
}
@Override
public CacheStrategy strategy() {
// TODO Auto-generated method stub
return null;
}
// Currently not needed, because instances are created and disposed by the YardManagerImpl!
// @Override
// public void serviceChanged(ServiceEvent event) {
// log.info("Print Service Event for "+event.getSource());
// for(String key : event.getServiceReference().getPropertyKeys()){
// log.info(" > "+key+"="+event.getServiceReference().getProperty(key));
// }
// Object cacheYardPropertyValue = event.getServiceReference().getProperty(CACHE_YARD);
// //TODO: check the type of the Service provided by the Reference!
// if(cacheYardPropertyValue != null && yardId.equals(cacheYardPropertyValue.toString())){
// //process the Event
// if(event.getType() == ServiceEvent.REGISTERED){
//
// } else if(event.getType() == ServiceEvent.UNREGISTERING){
//
// }
//
// }
// }
/**
* Getter for the Yard used by this Cache.
*
* @return the Yard used by this Cache or <code>null</code> if currently not
* available.
*/
public Yard getCacheYard() {
Yard yard = (Yard) yardTracker.getService();
if (yard != null && !initWithYard) {
try {
initWithCacheYard(yard);
} catch (YardException e) {
//this case can be recovered because initWithYard will not be
//set to true.
throw new IllegalStateException("Unable to initialize the Cache with Yard " + yardId + "! This is usually caused by Errors while reading the Cache Configuration from the Yard.", e);
}
}
return yard;
}
/*--------------------------------------------------------------------------
* Store and Update calls MUST respect the mappings configured for the
* Cache!
* --------------------------------------------------------------------------
*/
@Override
public Representation store(Representation representation) throws IllegalArgumentException, YardException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
return yard.store(applyCacheMappings(yard, representation));
}
}
@Override
public Representation update(Representation representation) throws YardException, IllegalArgumentException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
return yard.update(applyCacheMappings(yard, representation));
}
}
/**
* Applies the mappings defined by the {@link #baseMapper} and the {@link #additionalMapper}
* to the parsed Representation.
*
* @param yard The yard (local reference to avoid syncronization)
* @param representation The representation to map
* @return the mapped representation
*/
private Representation applyCacheMappings(Yard yard, Representation representation) {
long start = System.currentTimeMillis();
Representation mapped = null;
ValueFactory valueFactory = getValueFactory();
if (baseMapper != null) {
mapped = yard.getValueFactory().createRepresentation(representation.getId());
baseMapper.applyMappings(representation, mapped,valueFactory);
}
if (additionalMapper != null) {
if (mapped == null) {
mapped = yard.getValueFactory().createRepresentation(representation.getId());
}
additionalMapper.applyMappings(representation, mapped,valueFactory);
}
log.info(" -- applied mappings in " + (System.currentTimeMillis() - start) + "ms");
return mapped != null ? mapped : representation;
}
/*--------------------------------------------------------------------------
* Methods that forward calls to the Yard configured for this Cache
* --------------------------------------------------------------------------
*/
@Override
public Representation create() throws YardException {
Yard yard = getCacheYard();
if (yard == null) {
return createRepresentation(null);
} else {
return yard.create();
}
}
@Override
public Representation create(String id) throws IllegalArgumentException, YardException {
Yard yard = getCacheYard();
if (yard == null) {
return createRepresentation(id);
} else {
return yard.create(id);
}
}
/**
* Only used to create a representation if the Yard is currently not available
*
* @param id the id or <code>null</code> if a random one should be generated
* @return the created Representation
*/
private Representation createRepresentation(String id) {
if (id == null) {
id = String.format("urn:org.apache.stanbol:entityhub.yard.%s:%s.%s", getClass().getSimpleName(), yardId, ModelUtils.randomUUID().toString());
}
return InMemoryValueFactory.getInstance().createRepresentation(id);
}
@Override
public QueryResultList<Representation> find(FieldQuery query) throws YardException, IllegalArgumentException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
return yard.find(query);
}
}
@Override
public QueryResultList<String> findReferences(FieldQuery query) throws YardException, IllegalArgumentException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
return yard.findReferences(query);
}
}
@Override
public QueryResultList<Representation> findRepresentation(FieldQuery query) throws YardException, IllegalArgumentException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
return yard.findRepresentation(query);
}
}
@Override
public String getDescription() {
return String.format("Cache Wrapper for Yard %s ", yardId);
}
@Override
public String getId() {
return yardId;
}
@Override
public String getName() {
return yardId + " Cache";
}
@Override
public FieldQueryFactory getQueryFactory() {
Yard yard = getCacheYard();
if (yard == null) {
return DefaultQueryFactory.getInstance();
} else {
return yard.getQueryFactory();
}
}
@Override
public Representation getRepresentation(String id) throws YardException, IllegalArgumentException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
return yard.getRepresentation(id);
}
}
@Override
public ValueFactory getValueFactory() {
Yard yard = getCacheYard();
if (yard == null) {
return InMemoryValueFactory.getInstance();
} else {
return yard.getValueFactory();
}
}
@Override
public boolean isRepresentation(String id) throws YardException, IllegalArgumentException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
return yard.isRepresentation(id);
}
}
@Override
public void remove(String id) throws IllegalArgumentException, YardException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
yard.remove(id);
}
}
/*--------------------------------------------------------------------------
* Methods for reading and storing and changing the cache configuration
* --------------------------------------------------------------------------
*/
/**
* Getter for the base mappings used by this Cache. Modifications on the
* returned object do not have any influence on the mappings, because this
* method returns a clone. Use {@link #setBaseMappings(FieldMapper)} to
* change the used base mappings. However make sure you understand the
* implications of changing the base mappings as described in the
* documentation of the setter method
*
* @return A clone of the base mappings or <code>null</code> if no base
* mappings are defined
*/
@Override
public final FieldMapper getBaseMappings() {
return baseMapper == null ? null : baseMapper.clone();
}
/**
* Getter for the additional mappings used by this Cache. Modifications on the
* returned object do not have any influence on the mappings, because this
* method returns a clone. Use {@link #setAdditionalMappings(FieldMapper)} to
* change the used additional mappings. However make sure you understand the
* implications of changing the base mappings as described in the
* documentation of the setter method
*
* @return A clone of the additional mappings or <code>null</code> if no
* additional mappings are defined
*/
@Override
public final FieldMapper getAdditionalMappings() {
return additionalMapper == null ? null : additionalMapper.clone();
}
@Override
public void setAdditionalMappings(FieldMapper fieldMapper) throws YardException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
setAdditionalMappings(yard, fieldMapper);
}
}
/**
* Internally used in the initialisation to be able to parse the Yard instance
*
* @param yard the yard used to set the configured additional mappings
* @param fieldMapper the configuration
* @throws YardException on any error while accessing the yard
*/
protected void setAdditionalMappings(Yard yard, FieldMapper fieldMapper) throws YardException {
FieldMapper old = this.additionalMapper;
this.additionalMapper = fieldMapper;
try {
CacheUtils.storeAdditionalMappingsConfiguration(yard, additionalMapper);
} catch (YardException e) {
this.additionalMapper = old;
throw e;
}
}
@Override
public void setBaseMappings(FieldMapper fieldMapper) throws YardException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
if (isAvailable()) {
FieldMapper old = this.baseMapper;
this.baseMapper = fieldMapper;
try {
CacheUtils.storeBaseMappingsConfiguration(yard, baseMapper);
} catch (YardException e) {
this.baseMapper = old;
throw e;
}
}
}
}
@Override
public void remove(Iterable<String> ids) throws IllegalArgumentException, YardException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
yard.remove(ids);
}
}
@Override
public void removeAll() throws YardException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
//ensure that the baseConfig (if present) is not deleted by this
//operation
Representation baseConfig = yard.getRepresentation(Cache.BASE_CONFIGURATION_URI);
yard.removeAll();
if(baseConfig != null){
yard.store(baseConfig);
}
}
}
@Override
public Iterable<Representation> store(Iterable<Representation> representations) throws IllegalArgumentException, YardException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
return yard.store(representations);
}
}
@Override
public Iterable<Representation> update(Iterable<Representation> representations) throws YardException, IllegalArgumentException {
Yard yard = getCacheYard();
if (yard == null) {
throw new YardException(String.format("The Yard %s for this cache is currently not available", yardId));
} else {
return yard.update(representations);
}
}
}