All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.stanbol.entityhub.core.impl.EntityhubImpl Maven / Gradle / Ivy

/*
 * 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.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

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.EntityImpl;
import org.apache.stanbol.entityhub.core.query.DefaultQueryFactory;
import org.apache.stanbol.entityhub.core.query.QueryResultListImpl;
import org.apache.stanbol.entityhub.servicesapi.Entityhub;
import org.apache.stanbol.entityhub.servicesapi.EntityhubConfiguration;
import org.apache.stanbol.entityhub.servicesapi.EntityhubException;
import org.apache.stanbol.entityhub.servicesapi.defaults.NamespaceEnum;
import org.apache.stanbol.entityhub.servicesapi.mapping.FieldMapper;
import org.apache.stanbol.entityhub.servicesapi.mapping.FieldMapping;
import org.apache.stanbol.entityhub.servicesapi.model.ManagedEntityState;
import org.apache.stanbol.entityhub.servicesapi.model.MappingState;
import org.apache.stanbol.entityhub.servicesapi.model.Representation;
import org.apache.stanbol.entityhub.servicesapi.model.Entity;
import org.apache.stanbol.entityhub.servicesapi.model.ValueFactory;
import org.apache.stanbol.entityhub.servicesapi.model.rdf.RdfResourceEnum;
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.query.ReferenceConstraint;
import org.apache.stanbol.entityhub.servicesapi.site.Site;
import org.apache.stanbol.entityhub.servicesapi.site.SiteManager;
import org.apache.stanbol.entityhub.servicesapi.site.SiteConfiguration;
import org.apache.stanbol.entityhub.servicesapi.util.ModelUtils;
import org.apache.stanbol.entityhub.servicesapi.yard.Yard;
import org.apache.stanbol.entityhub.servicesapi.yard.YardException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Default implementation of the Entityhub.
 * @author Rupert Westenthaler
 *
 */
public final class EntityhubImpl implements Entityhub {//, ServiceListener {

    private final Logger log = LoggerFactory.getLogger(EntityhubImpl.class);

    /**
     * The field mapper holding global mappings that are used for mapping
     * representations of entities for any referenced sites
     */
    private final FieldMapper fieldMapper;

    /**
     * The Configuration of the Entityhub
     */
    private final EntityhubConfiguration config;
    /**
     * The site manager is used to search for entities within the Entityhub framework
     */
    private final SiteManager siteManager;
    
    private final Yard entityhubYard;

    private static final String DEFAULT_MANAGED_ENTITY_PREFIX = "entity";
    private static final String DEFAULT_MAPPING_PREFIX = "mapping";
    
    public EntityhubImpl(Yard entityhubYard, SiteManager siteManager, 
            EntityhubConfiguration config, NamespacePrefixService nsPrefixService) {
        if(entityhubYard == null){
            throw new IllegalArgumentException("The parsed Yard MUST NOT be NULL!");
        }
        this.entityhubYard = entityhubYard;
        if(siteManager == null){
            throw new IllegalArgumentException("The parsed SiteManager MUST NOT be NULL!");
        }
        this.siteManager = siteManager;
        if(config == null){
            throw new IllegalArgumentException("The parsed EntityhubConfig MUST NOT be NULL!");
        }
        this.config = config;
        log.info(" ... init FieldMappings");
        fieldMapper = new DefaultFieldMapperImpl(ValueConverterFactory.getDefaultInstance());
        for(String mappingString : config.getFieldMappingConfig()){
            FieldMapping mapping = FieldMappingUtils.parseFieldMapping(mappingString,nsPrefixService);
            if(mapping != null){
                log.info("   + mapping: "+mapping);
                fieldMapper.addMapping(mapping);
            }
        }
    }

    @Override
    public final EntityhubConfiguration getConfig() {
        return config;
    }
    @Override
    public Yard getYard() {
        return entityhubYard;
    }
    
    @Override
    public Entity lookupLocalEntity(String entity) throws YardException{
        return lookupLocalEntity(entity,false);
    }

    @Override
    public Entity lookupLocalEntity(String entityId, boolean create) throws YardException {
        Entity entity = getEntity(entityId);
        if(entity != null){
            return entity;
        } else {
            //parsed reference was not a locally managed entity ->
            //search for an mapped Entity
            Entity entityMapping = getMappingBySource(entityId);
            if(entityMapping != null){
                entity = getEntity(EntityMapping.getTargetId(entityMapping));
                if(entity != null){
                    return entity;
                } else {
                    log.warn("Unable to find Entity for Entity Mapping "+entityMapping+"!");
                    return recoverEntity(EntityMapping.getTargetId(entityMapping));
                }
            } else if(create){
                //search if the parsed reference is known by any referenced site
                // and if YES, create a new Symbol
                Entity remoteEntity = siteManager.getEntity(entityId);
                 if(remoteEntity == null){ //id not found by any referred site
                     return null;
                 } else {
                     return importEntity(remoteEntity);
                 }
            } else {
                return null;
            }
        }
    }
    @Override
    public final Entity importEntity(String reference) throws IllegalStateException, IllegalArgumentException, YardException {
        Entity entity = getEntity(reference);
        if(entity == null){
            Entity entityMapping = getMappingBySource(reference);
            if(entityMapping == null){
                Entity remoteEntity = siteManager.getEntity(reference);
                if(remoteEntity == null){
                    return null;
                } else {
                    return importEntity(remoteEntity);
                }
            } else {
                throw new IllegalStateException(String.format(
                    "The reference %s is already imported to the Entityhub " +
                    "(local Entity %s)",reference,
                    EntityMapping.getTargetId(entityMapping)));
            }
        } else {
            throw new IllegalStateException("The parsed id "+reference+" refers " +
            		"to an Entity managed by the Entityhub (entity="+entity+")!");
        }
    }
    /**
     * Recovers a missing locally managed entity based on the available 
     * {@link EntityMapping}s and the data available through referenced sites.
     * @param entityId the id of the missing locally managed entity
     * @return the recovered Entity or null if the recovering is 
     * not possible
     */
    private Entity recoverEntity(String entityId) {
        /*
         * TODO: recover the Symbol by recreating it based on the available
         *       mapped Entities
         * 1) search for all EntityMappings with this SymbolId
         * 2) get all mapped Entities
         * 3) apply the FieldMappings
         * 4) store the Symbol
         * 5) return the recovered Symbol
         */
        return null;
    }
    /*
     * @throws IllegalArgumentException if a {@link Representation} for the parsed ID is present in the
     *  {@link #entityhubYard}, but the representation can not be wrapped by an {@link Symbol}.
     */
    @Override
    public final Entity getEntity(String entityId) throws IllegalArgumentException,IllegalStateException, YardException {
        if(entityId == null || entityId.isEmpty()){
            throw new IllegalArgumentException("The parsed id MUST NOT be NULL nor empty!");
        }
        Entity entity = loadEntity(entityId);
        if(entity == null){
            return null;
        } else if (ManagedEntity.canWrap(entity)){
            return entity;
        } else {
            log.info("The parsed id does not represent a locally managed Entity {}", entity);
            return null;
        }
    }
    @Override
    public final boolean isRepresentation(String entityId) throws EntityhubException, IllegalArgumentException {
        if(entityId == null || entityId.isEmpty()){
            throw new IllegalArgumentException("The parsed id MUST NOT be NULL nor empty!");
        }
        return entityhubYard.isRepresentation(entityId);
    }
    @Override
    public final Entity store(Representation representation) throws EntityhubException, IllegalArgumentException {
        if(representation == null){
            throw new IllegalArgumentException("The parsed Representation MUST NOT be NULL!");
        }
        //parse only the id of the representation, because we need the current
        //stored version of the entity!
        Entity entity = loadEntity(representation.getId());
        //now we need to check if the parsed representation is the data or the
        //metadata of the Entity
        ManagedEntity updated;
        if(entity == null || representation.getId().equals(entity.getRepresentation().getId())){
            //update the data or create a new entity
            updated = ManagedEntity.init(new EntityImpl(config.getID(), representation,
                entity != null ? entity.getMetadata() : null),
                config.getDefaultManagedEntityState());
            if(entity == null){ //add creation date
                updated.setCreated(new Date());
            }
        } else {
            //update the metadata
            entity = new EntityImpl(config.getID(), entity.getRepresentation(),
                representation);
            //we need to validate the metadata
            updated = ManagedEntity.init(
                entity, config.getDefaultManagedEntityState());
        }
        storeEntity(updated.getWrappedEntity());
        return updated.getWrappedEntity();
    }
    @Override
    public final Entity delete(String id) throws EntityhubException, IllegalArgumentException {
        if(id == null || id.isEmpty()){
            throw new IllegalArgumentException("The parsed id MUST NOT be NULL nor emtpty!");
        }
        Entity entity = loadEntity(id);
        if(entity != null){
            log.debug("delete Entity {} as requested by the parsed id {}",entity.getId(),id);
            //we need to remove all mappings for this Entity
            deleteMappingsbyTarget(entity.getId());
            deleteEntity(entity);
        } else {
            log.debug("Unable to delete Entity for id {}, because no Entity for this id is" +
            		"managed by the Entityhub",id);
        }
        return entity;
    }
    @Override
    public final void deleteAll() throws EntityhubException {
        entityhubYard.removeAll();
    }
    @Override
    public final Entity setState(String id, ManagedEntityState state) throws EntityhubException,
                                                               IllegalArgumentException {
        if(id == null || id.isEmpty()){
            throw new IllegalStateException("The parsed id MUST NOT be NULL nor empty!");
        }
        if(state == null){
            throw new IllegalStateException("The parsed state for the Entity MUST NOT ne NULL");
        }
        Entity entity = loadEntity(id);
        if(entity != null){
            ManagedEntity managed = new ManagedEntity(entity);
            if(managed.getState() != state){
                managed.setState(state);
                storeEntity(entity);
            }
        }
        return entity;
    }
    /**
     * Deleted both the representation and the metadata of an Entity
     * @param yard the yard to delete the entity from
     * @param entity the entity to delete
     * @throws YardException an any Exception while deleting the Entity
     */
    private void deleteEntity(Entity entity) throws YardException {
        if(entity != null){
            entityhubYard.remove(Arrays.asList(
                entity.getRepresentation().getId(),
                entity.getMetadata().getId()));
        }
    }
    private void deleteEntities(Collection ids) throws YardException {
        FieldQuery fieldQuery = getQueryFactory().createFieldQuery();
        Collection toDelete = new HashSet(ids);
        for(String id : ids){
            if(id != null && !id.isEmpty()){
                fieldQuery.setConstraint(RdfResourceEnum.aboutRepresentation.getUri(), new ReferenceConstraint(id));
                for(Iterator it = entityhubYard.findReferences(fieldQuery).iterator();it.hasNext();){
                    toDelete.add(it.next());
                }
            }
        }
        if(!toDelete.isEmpty()){
            entityhubYard.remove(toDelete);
        }
        
    }

    private void deleteMappingsbyTarget(String id) throws YardException {
        if(id != null && !id.isEmpty()){
            FieldQuery fieldQuery = getQueryFactory().createFieldQuery();
            fieldQuery.setConstraint(RdfResourceEnum.mappingTarget.getUri(), new ReferenceConstraint(id));
            deleteEntities(ModelUtils.asCollection(
                entityhubYard.findReferences(fieldQuery).iterator()));
        }
    }

    /**
     * Imports the Entity
     * @param remoteEntity the Entity to import
     * @return the Entity created and stored within the Entityhub
     * @throws YardException
     */
    protected Entity importEntity(Entity remoteEntity) throws YardException{
        if(remoteEntity == null){
            return null;
        }
        Site site = siteManager.getSite(remoteEntity.getSite());
        if(site == null){
            log.warn("Unable to import Entity {} because the ReferencedSite {} is currently not active -> return null",
                remoteEntity.getId(),remoteEntity.getSite());
            return null;
        }
        ValueFactory valueFactory = entityhubYard.getValueFactory();
        //Create the locally managed Entity
        Representation localRep = entityhubYard.create(constructResourceId(DEFAULT_MANAGED_ENTITY_PREFIX));
        Entity localEntity = loadEntity(localRep);
        importEntity(remoteEntity, site, localEntity, valueFactory);

        //Second create and init the Mapping
        Representation entityMappingRepresentation = entityhubYard.create(
            constructResourceId(DEFAULT_MAPPING_PREFIX));
        Entity entityMappingEntity = loadEntity(entityMappingRepresentation);
        establishMapping(localEntity, remoteEntity, site, entityMappingEntity);
        
        //Store the entity and the mappedEntity in the entityhubYard
        storeEntity(localEntity);
        storeEntity(entityMappingEntity);
        return localEntity;
    }
    /**
     * Imports a {@link Entity} from a {@link Site}. This Method imports
     * the {@link Representation} by applying all configured mappings. It also
     * sets the {@link ManagedEntityState} to the configured default value by the 
     * referenced site of the imported entity or the default for the Entityhub 
     * if the site does not define this configuration.

* @param remoteEntity The entity to import * @param site the referenced site of the entity to import * @param localEntity the target entity for the import * @param valueFactory the valusFactory used to create instance while importing */ private void importEntity(Entity remoteEntity, Site site, Entity localEntity, ValueFactory valueFactory) { SiteConfiguration siteConfig = site.getConfiguration(); ManagedEntityState state; state = siteConfig.getDefaultManagedEntityState(); if(state == null){ state = config.getDefaultManagedEntityState(); } //this wrapper allows to use an API to write metadata ManagedEntity managedEntity = ManagedEntity.init(localEntity, state); FieldMapper siteMapper = site.getFieldMapper(); FieldMapper mapper = this.fieldMapper.clone(); for(FieldMapping siteMapping : siteMapper.getMappings()){ mapper.addMapping(siteMapping); } //TODO: As soon as MappingActivities are implemented we need to add such // information to the EntityMapping instance! mapper.applyMappings(remoteEntity.getRepresentation(), localEntity.getRepresentation(),valueFactory); //set general metadata managedEntity.setCreated(new Date()); //set the metadata required by the referenced site managedEntity.addAttributionLink(site.getConfiguration().getAttributionUrl()); managedEntity.addAttributionText(site.getConfiguration().getAttribution(), null); //TODO: maybe replace with the URL of the site managedEntity.addContributorName(site.getConfiguration().getName()); } /** * Creates a new {@link EntityMapping} for the parsed source and target * {@link Entity}. This also sets the State and the expire date based on the * configurations of the ReferencesSite of the source entity and the defaults of the * Entityhub * @param localEntity the locally managed entity (target for the mapping) * @param remoteEntity the remote Entity (source for the mapping) * @param site the referenced site managing the source Entity * @param entityMappingRepresentation the Entity for the mapping * @return the EntityMapping */ private EntityMapping establishMapping(Entity localEntity, Entity remoteEntity, Site site, Entity entityMappingEntity) { EntityMapping entityMapping = EntityMapping.init(entityMappingEntity); //now init the mappingState and the expireDate based on the config of the //ReferencedSite of the source entity (considering also the defaults of the entityhub) SiteConfiguration siteConfig = site.getConfiguration(); MappingState mappingState = siteConfig.getDefaultMappedEntityState(); if(mappingState == null){ mappingState = config.getDefaultMappingState(); } entityMapping.setState(mappingState); long expireDuration = siteConfig.getDefaultExpireDuration(); if(expireDuration > 0){ Date expireDate = new Date(System.currentTimeMillis()+expireDuration); entityMapping.setExpires(expireDate); } entityMapping.setSourceId(remoteEntity.getId()); entityMapping.setTargetId(localEntity.getId()); //initialise additional metadata entityMapping.setCreated(new Date()); return entityMapping; } /** * Uses the Prefix as configured by the {@link #config} and the parsed * prefix for the type to create an unique ID for a resource. * @param typePrefix the prefix of the type * @return An id in the form {@link EntityhubConfiguration#getEntityhubPrefix()} * + typePrefix + '.' + {@link ModelUtils#randomUUID()}. Note that between * the entity hub prefix and the type prefix a separation chars are added * if it is not already defined by the {@link EntityhubConfiguration#getEntityhubPrefix()} * value. */ private String constructResourceId(String typePrefix) { StringBuilder id = new StringBuilder(); String prefix = config.getEntityhubPrefix(); if(prefix == null || prefix.isEmpty()){ prefix = Entityhub.DEFAUTL_ENTITYHUB_PREFIX; } id.append(prefix); switch(prefix.charAt(prefix.length()-1)){ case '#': case ':': break; default: //add a separator if(prefix.startsWith("urn:")){ id.append(':'); //use a point for now } else { id.append('/'); //use '/' instead of '#' because one needs not to escape it in GET requests } } if(typePrefix != null && !typePrefix.isEmpty()){ id.append(typePrefix); id.append('.'); } id.append(ModelUtils.randomUUID()); return id.toString(); } @Override public Entity getMappingBySource(String reference) throws YardException{ if(reference == null){ log.warn("NULL parsed as Reference -> call to getMappingByEntity ignored (return null)"); return null; } FieldQuery fieldQuery = getQueryFactory().createFieldQuery(); fieldQuery.setConstraint(RdfResourceEnum.mappingSource.getUri(), new ReferenceConstraint(reference)); QueryResultList resultList = entityhubYard.findRepresentation(fieldQuery); if(!resultList.isEmpty()){ Iterator resultIterator = resultList.iterator(); Entity mapping = loadEntity(resultIterator.next()); //print warnings in case of multiple mappings if(resultIterator.hasNext()){ log.warn("Multiple Mappings found for Entity {}!",reference); log.warn(" > {} -> returned instance",mapping.getId()); while(resultIterator.hasNext()){ log.warn(" > {} -> ignored",resultIterator.next()); } } if(!EntityMapping.isValid(mapping)){ log.warn("Entity {} is not a valid EntityMapping. -> return null",mapping); mapping = null; } return mapping; } else { log.debug("No Mapping found for Entity {}",reference); return null; } } @Override public Collection getMappingsByTarget(String targetId) throws YardException{ if(targetId == null){ log.warn("NULL parsed as Reference -> call to getMappingsBySymbol ignored (return null)"); return null; } FieldQuery fieldQuery = getQueryFactory().createFieldQuery(); fieldQuery.setConstraint(RdfResourceEnum.mappingTarget.getUri(), new ReferenceConstraint(targetId)); QueryResultList resultList = entityhubYard.findRepresentation(fieldQuery); Collection mappings = new HashSet(); for(Representation rep : resultList){ mappings.add(loadEntity(rep)); } return mappings; } /** * Loads an Entity (Representation and Metadata) for the parsed id. In case * the parsed id represents metadata, than the id of the returned Entity will * be different from the parsed id. * @param id the id of the data or the metadata of the Entity to load * @return the Entity or null if not found * @throws YardException On any error with the parsed Yard. */ private Entity loadEntity(String id) throws YardException { return id == null || id.isEmpty() ? null : loadEntity(entityhubYard.getRepresentation(id)); } /** * Loads the Entity based on the parsed representation. The parsed * {@link Representation} can be both the data and the metadata. In case the * parsed representation are metadat the id of the returned Entity will be * not the same as the id of the parsed {@link Representation}. * @param rep the representation or metadata of an entity * @return the created Entity including both data and metadata or * null if the parsed Representation does not represent a * Representation managed by the Entityhub (this may be the case if an other * thread has deleted that Entity in the meantime) * @throws YardException On any error with the parsed Yard. */ private Entity loadEntity(Representation rep) throws YardException { if(rep != null){ Representation data; Representation metadata = null; String entityId = ModelUtils.getAboutRepresentation(rep); if(entityId != null){ data = entityhubYard.getRepresentation(entityId); metadata = rep; } else { data = rep; entityId = rep.getId(); //needed for logs } if(data != null){ metadata = lookupMetadata(rep.getId(),true); return new EntityImpl(config.getID(), data,metadata); } else { log.warn("Unable find representation for Entity {} (metadata: {}", entityId,metadata); return null; } } else { return null; } } /** * Lookups (or initialises) the metadata for the entity with the parsed id * @param entityId The id of the entity * @param init if the metadata should be initialised of not existing * @return the metadata for that Entity or null if not existing * and init == false * @throws YardException */ private Representation lookupMetadata(String entityId, boolean init) throws YardException { Representation metadata; //TODO: check the asumption that the Metadata always use the // extension ".meta" String metaID = entityId+".meta"; metadata = entityhubYard.getRepresentation(metaID); if(metadata == null){ metadata = entityhubYard.create(metaID); } return metadata; } /** * Stores both the Representation and the Metadata of the parsed Entity to the * parsed yard.

* This Method also updated the modification date of the Metadata. * @param entity the stored entity * @throws YardException */ private void storeEntity(Entity entity) throws YardException{ if(entity != null){ entityhubYard.store(entity.getRepresentation()); entity.getMetadata().set(NamespaceEnum.dcTerms+"modified", new Date()); entityhubYard.store(entity.getMetadata()); } } @Override public final Entity getMappingById(String id) throws IllegalArgumentException, EntityhubException{ if(id == null || id.isEmpty()){ throw new IllegalArgumentException("The parsed id MUST NOT be NULL nor empty"); } Entity mapping = loadEntity(id); if(mapping == null){ return null; } else if(mapping != null && EntityMapping.isValid(mapping)){ return mapping; } else { log.info("The Entity {} for the parsed id does not represent an EntityMapping -> return null!", mapping); return null; } } @Override public final FieldQueryFactory getQueryFactory() { Yard entityhubYard = getYard(); return entityhubYard==null? //if no yard available DefaultQueryFactory.getInstance(): //use the default entityhubYard.getQueryFactory(); //else return the query factory used by the yard } @Override public final FieldMapper getFieldMappings() { return fieldMapper; } @Override public final QueryResultList find(FieldQuery query) throws YardException{ return entityhubYard.find(query); } @Override public final QueryResultList findEntityReferences(FieldQuery query) throws YardException{ return entityhubYard.findReferences(query); } @Override public final QueryResultList findEntities(FieldQuery query) throws YardException{ QueryResultList references = entityhubYard.findReferences(query); List entities = new ArrayList(references.size()); for(String reference : references){ Entity entity = lookupLocalEntity(reference); if(entity != null){ entities.add(entity); } else { log.warn("Unable to create Entity for Reference {} in the Yard " + "usd by the entity hub [id={}] -> ignore reference", reference,config.getEntityhubYardId()); } } return new QueryResultListImpl(references.getQuery(), entities, Entity.class); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy