/* $This file is distributed under the terms of the license in LICENSE$ */

package edu.cornell.mannlib.vitro.webapp.dao.jena;

import static edu.cornell.mannlib.vitro.webapp.dao.jena.WebappDaoFactorySDB.SDBDatasetMode.ASSERTIONS_ONLY;

import java.sql.Timestamp;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;

import org.apache.jena.ontology.OntModel;
import org.apache.jena.ontology.OntModelSpec;
import org.apache.jena.ontology.OntResource;
import org.apache.jena.query.Dataset;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QueryExecutionFactory;
import org.apache.jena.query.QueryFactory;
import org.apache.jena.query.QuerySolution;
import org.apache.jena.query.ResultSet;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.ResourceFactory;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.rdf.model.StmtIterator;
import org.apache.jena.shared.Lock;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.RDFS;

import edu.cornell.mannlib.vitro.webapp.beans.DataProperty;
import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatement;
import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatementImpl;
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.beans.IndividualImpl;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectProperty;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatement;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatementImpl;
import edu.cornell.mannlib.vitro.webapp.beans.VClass;
import edu.cornell.mannlib.vitro.webapp.dao.VClassDao;
import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
import edu.cornell.mannlib.vitro.webapp.dao.jena.WebappDaoFactorySDB.SDBDatasetMode;
import edu.cornell.mannlib.vitro.webapp.filestorage.model.ImageInfo;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;

public class IndividualSDB extends IndividualImpl implements Individual {

    private static final Log log = LogFactory.getLog(
                IndividualSDB.class.getName());
    private OntResource ind = null;
    private WebappDaoFactorySDB webappDaoFactory = null;
    private Float _searchBoostJena = null;
    private boolean retreivedNullRdfsLabel = false;
    private DatasetWrapperFactory dwf = null;
    private SDBDatasetMode datasetMode =
                SDBDatasetMode.ASSERTIONS_AND_INFERENCES;
    private String individualURI = null;
    private Model model = null;
    private Boolean _hasThumb = null;

    public IndividualSDB(String individualURI,
                         DatasetWrapperFactory datasetWrapperFactory,
                         SDBDatasetMode datasetMode,
                         WebappDaoFactorySDB wadf,
                         Model initModel) {
    	this.individualURI = individualURI;
		this.webappDaoFactory = wadf;
    	this.dwf = datasetWrapperFactory;

    	try {
	    	initModel.getLock().enterCriticalSection(Lock.READ);
	    	String getStatements =
	    		"CONSTRUCT \n" +
	    		"{ <"+individualURI+">  <" + RDFS.label.getURI() +
	    		        "> ?ooo. \n" +
	    		   "<"+individualURI+">  a ?type . \n" +
	    		 "} \n" +
	    		 "WHERE { \n" +
	    		 	"{ <"+individualURI+">  <" + RDFS.label.getURI() +
	    		 	        "> ?ooo }  \n" +
	    		 	" UNION { <"+individualURI+"> a ?type } \n" +
	    		 "} ";
    		this.model = QueryExecutionFactory.create(
    		        QueryFactory.create(getStatements), initModel)
    		                .execConstruct();
    	} finally {
    		initModel.getLock().leaveCriticalSection();
    	}

    	OntModel ontModel = ModelFactory.createOntologyModel(
    	        OntModelSpec.OWL_MEM, model);
    	this.ind = ontModel.createOntResource(individualURI);
    	setUpURIParts(ind);
    }

    public IndividualSDB(String individualURI,
            DatasetWrapperFactory datasetWrapperFactory,
            SDBDatasetMode datasetMode,
            WebappDaoFactorySDB wadf,
            boolean skipInitialization) throws IndividualNotFoundException {
    	this.individualURI = individualURI;
    	this.datasetMode = datasetMode;
    	this.dwf = datasetWrapperFactory;
		this.webappDaoFactory = wadf;

		// Check that individualURI is valid. (Prevent SPARQL injection attack.)
		// Valid syntax is defined here: https://www.w3.org/TR/rdf-sparql-query/#rIRI_REF
		if (!individualURI.matches("[^<>\"{}|^`\\\\\u0000-\u0020]*")) {
			throw new IndividualNotFoundException();
		}

    	if (skipInitialization) {
            OntModel ontModel = ModelFactory.createOntologyModel(
                    OntModelSpec.OWL_MEM);
            this.ind = ontModel.createOntResource(individualURI);
    	} else {
        	try {
    	    	String getStatements =
    	    		"CONSTRUCT " +
    	    		"{ <"+individualURI+">  <" + RDFS.label.getURI() +
    	    		        "> ?ooo \n" +
    	    		 "} WHERE {" +
    	    		 	"{ <"+individualURI+">  <" + RDFS.label.getURI() +
    	    		 	        "> ?ooo } \n" +
    	    		 "}";

				model = ModelFactory.createDefaultModel();
				webappDaoFactory.getRDFService().sparqlConstructQuery(getStatements, model);
        	} catch (RDFServiceException e) {
        	}

        	OntModel ontModel = ModelFactory.createOntologyModel(
        	        OntModelSpec.OWL_MEM, model);

        	if (model.isEmpty() && noTriplesFor(individualURI)) {
        	    throw new IndividualNotFoundException();
        	}

        	this.ind = ontModel.createOntResource(individualURI);
    	}
    	setUpURIParts(ind);
    }

    private boolean noTriplesFor(String individualURI) {
		try {
			return !webappDaoFactory.getRDFService().sparqlAskQuery("ASK { <" + individualURI + "> ?p ?o }");
		} catch (RDFServiceException rse) {
		}

		return true;
    }

    static final boolean SKIP_INITIALIZATION = true;

    public IndividualSDB(String individualURI,
            DatasetWrapperFactory datasetWrapperFactory,
            SDBDatasetMode datasetMode,
            WebappDaoFactorySDB wadf) throws IndividualNotFoundException {
        this(individualURI,
             datasetWrapperFactory,
             datasetMode,
             wadf,
             !SKIP_INITIALIZATION);
    }

    public class IndividualNotFoundException extends Exception {}

    private void setUpURIParts(OntResource ind) {
        if (ind != null) {
            if (ind.isAnon()) {
                this.setNamespace(VitroVocabulary.PSEUDO_BNODE_NS);
                this.setLocalName(ind.getId().toString());
            } else {
                this.URI = ind.getURI();
                this.namespace = ind.getNameSpace();
                this.localName = ind.getLocalName();
            }
        } else if (individualURI != null) {
            log.warn("Null individual returned for URI " + individualURI);
        }
    }

    private DatasetWrapper getDatasetWrapper() {
        return this.dwf.getDatasetWrapper();
    }

    public String getName() {
        if (this.name != null) {
            return name;
        } else {
        	ind.getOntModel().enterCriticalSection(Lock.READ);

            try {
                this.name = webappDaoFactory.getJenaBaseDao().getLabelOrId(ind);
                if (this.name == null) {
                    this.name = "[null]";
                }
                return this.name;

            } finally {

            	ind.getOntModel().leaveCriticalSection();
            }
        }
    }

    public String getRdfsLabel() {
        if (this.rdfsLabel != null) {
            return rdfsLabel;
        } else if( this.rdfsLabel == null && retreivedNullRdfsLabel ){
        	return null;
        } else {

        	ind.getOntModel().enterCriticalSection(Lock.READ);
            try {
                this.rdfsLabel = webappDaoFactory.getJenaBaseDao().getLabel(ind);
                retreivedNullRdfsLabel = this.rdfsLabel == null;
                return this.rdfsLabel;
            } finally {

            	ind.getOntModel().leaveCriticalSection();
            }
        }
    }

	public String getVClassURI() {
        if (this.vClassURI != null) {
            return vClassURI;
        } else {
        	List<VClass> clist = getVClasses(true);
        	return (clist.size() > 0) ? clist.get(0).getURI() : null;
        }
    }

    public VClass getVClass() {
        if (this.vClass != null) {
            return this.vClass;
        } else {
        	List<VClass> clist = getVClasses(true);
            return (clist.size() > 0) ? clist.get(0) : null ;
        }
    }

    @Override
    public List<String> getMostSpecificTypeURIs() {
        final List<String> typeURIs = new ArrayList<String>();
        if (this.getURI() == null) {
            return typeURIs;
        } else {
            String queryStr = "SELECT ?type WHERE { <" + this.getURI() + "> <" +
                    VitroVocabulary.MOST_SPECIFIC_TYPE + "> ?type }";
            try {
				webappDaoFactory.getRDFService().sparqlSelectQuery(queryStr, new ResultSetConsumer() {
					@Override
					protected void processQuerySolution(QuerySolution qs) {
						RDFNode node = qs.get("type");
						if (node.isURIResource()) {
							typeURIs.add(node.asResource().getURI());
						}
					}
				});

                return typeURIs;
            } catch (RDFServiceException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public Timestamp getModTime() {
        if (modTime != null) {
            return modTime;
        } else {

        	ind.getOntModel().enterCriticalSection(Lock.READ);
            try {
                Date modDate = webappDaoFactory.getJenaBaseDao()
                        .getPropertyDateTimeValue(
                                ind,webappDaoFactory.getJenaBaseDao().MODTIME);
                if (modDate != null) {
                    modTime = new Timestamp(modDate.getTime());
                }
                return modTime;
            } finally {

            	ind.getOntModel().leaveCriticalSection();
            }
        }
    }

    public Float getSearchBoost(){
        if( this._searchBoostJena != null ){
            return this._searchBoostJena;
        }else{
            String getPropertyValue =
            	"SELECT ?value \n" +
            	"WHERE { \n" +
            	"<" +individualURI+ "> <" +webappDaoFactory.getJenaBaseDao().SEARCH_BOOST_ANNOT+ "> ?value \n" +
            	"}";
            DatasetWrapper w = getDatasetWrapper();
            Dataset dataset = w.getDataset();
        	dataset.getLock().enterCriticalSection(Lock.READ);
        	QueryExecution qe = QueryExecutionFactory.create(
                    QueryFactory.create(getPropertyValue), dataset);
            try{
                ResultSet rs = qe.execSelect();
                if(rs.hasNext()){
                	QuerySolution qs = rs.nextSolution();
                	if(qs.get("value") !=null){
                		Literal value = qs.get("value").asLiteral();
                		searchBoost = Float.parseFloat(value.getLexicalForm());
                		return searchBoost;
                	}
                } else{
                   return null;
                }
            } catch (Exception e){
            	log.error(e,e);
                return null;
            } finally{
                qe.close();
            	dataset.getLock().leaveCriticalSection();
            	w.close();
            }
        }
        return null;
    }

	@Override
	public String getMainImageUri() {
		if (this.mainImageUri != NOT_INITIALIZED) {
			return mainImageUri;
		} else {
			List<ObjectPropertyStatement> mainImgStmts =
					getObjectPropertyStatements(VitroVocabulary.IND_MAIN_IMAGE);
			if (mainImgStmts != null && mainImgStmts.size() > 0) {
				// arbitrarily return the first value in the list
				mainImageUri = mainImgStmts.get(0).getObjectURI();
				return mainImageUri;
			}
			return null;
		}
	}

	@Override
	public String getImageUrl() {
		if (this.imageInfo == null) {
			this.imageInfo = ImageInfo.instanceFromEntityUri(
			        webappDaoFactory, this);
			log.trace("figured imageInfo for " + getURI() + ": '"
					+ this.imageInfo + "'");
		}
		if (this.imageInfo == null) {
			this.imageInfo = ImageInfo.EMPTY_IMAGE_INFO;
			log.trace("imageInfo for " + getURI() + " is empty.");
		}
		return this.imageInfo.getMainImage().getBytestreamAliasUrl();
	}

	@Override
	public String getThumbUrl() {
		if (this.imageInfo == null) {
			this.imageInfo = ImageInfo.instanceFromEntityUri(
			        webappDaoFactory, this);
			log.trace("figured imageInfo for " + getURI() + ": '"
					+ this.imageInfo + "'");
		}
		if (this.imageInfo == null) {
			this.imageInfo = ImageInfo.EMPTY_IMAGE_INFO;
			log.trace("imageInfo for " + getURI() + " is empty.");
		}
		return this.imageInfo.getThumbnail().getBytestreamAliasUrl();
	}

	@Override
	public boolean hasThumb(){
	    if( _hasThumb != null ){
	        return _hasThumb;
        }else{
            String ask =
                "ASK { " +
                "    <" + individualURI + "> <http://vitro.mannlib.cornell.edu/ns/vitro/public#mainImage> ?mainImage . \n" +
                "    ?mainImage <http://vitro.mannlib.cornell.edu/ns/vitro/public#thumbnailImage> ?thumbImage . }\n"  ;
            try{
				_hasThumb = webappDaoFactory.getRDFService().sparqlAskQuery(ask);
            }catch(Exception ex){
                _hasThumb = false;
                log.error(ex,ex);
            }
            return _hasThumb;
        }
	}

    public List<ObjectPropertyStatement> getObjectPropertyStatements() {
        if (this.objectPropertyStatements != null) {
            return this.objectPropertyStatements;
        } else {
            try {
                webappDaoFactory.getObjectPropertyStatementDao()
                        .fillExistingObjectPropertyStatements(this);
            } catch (Exception e) {
                log.error("Could not fill existing ObjectPropertyStatements for "
                        + this.getURI(), e);
            }
            return this.objectPropertyStatements;
        }
    }

    @Override
    public List<ObjectPropertyStatement> getObjectPropertyStatements(String propertyURI) {
    	if (propertyURI == null) {
    		return null;
    	}
    	List<ObjectPropertyStatement> objectPropertyStatements = new ArrayList
    	        <ObjectPropertyStatement>();
	    Model tempModel = ModelFactory.createDefaultModel();
        OntModel ontModel = ModelFactory.createOntologyModel(
                OntModelSpec.OWL_MEM);
        DatasetWrapper w = getDatasetWrapper();
        Dataset dataset = w.getDataset();
    	dataset.getLock().enterCriticalSection(Lock.READ);
        QueryExecution qexec = null;
    	try {
    		String valuesOfProperty =
    			"CONSTRUCT{ <" + this.individualURI + "> <" + propertyURI + "> ?object }" +
    			"WHERE{ <" + this.individualURI + "> <" + propertyURI + "> ?object } \n";
            qexec = QueryExecutionFactory.create(QueryFactory.create(valuesOfProperty), dataset);
    	    tempModel = qexec.execConstruct();
    	    ontModel.add(tempModel.listStatements());
    	    Resource ontRes = ontModel.getResource(this.individualURI);
    	    StmtIterator sit = ontRes.listProperties(ontRes.getModel().getProperty(propertyURI));
    	    while (sit.hasNext()) {
    			Statement s = sit.nextStatement();
    			if (!s.getSubject().canAs(OntResource.class) || !s.getObject().canAs(OntResource.class)) {
    			    continue;
    			}
    			Individual subj = null;
    			try {
    			    subj = new IndividualSDB(
    			            s.getSubject().as(OntResource.class).getURI(),
    			                            this.dwf, datasetMode, webappDaoFactory);
    			} catch (IndividualNotFoundException e) {
    			    // leave null subject
    			}
    			Individual obj = null;
    			try {
    			    obj = new IndividualSDB(
    			            s.getObject().as(OntResource.class).getURI(),
    			                            this.dwf, datasetMode, webappDaoFactory);
    			} catch (IndividualNotFoundException e) {
    			    // leave null object
    			}
    			ObjectProperty op = webappDaoFactory.getObjectPropertyDao().getObjectPropertyByURI(s.getPredicate().getURI());
    			// We don't want to filter out statements simply because we
    			// can't find a type for the property, so we'll just make a
    			// new ObjectProperty bean if we can't get one from the DAO.
    			if (op == null) {
    				op = new ObjectProperty();
    				op.setURI(propertyURI);
    			}
    			if (subj != null && obj != null) {
    				ObjectPropertyStatement ops = new ObjectPropertyStatementImpl();
    				ops.setSubject(subj);
    				ops.setSubjectURI(subj.getURI());
    				ops.setObject(obj);
    				ops.setObjectURI(obj.getURI());
    				ops.setProperty(op);
    				ops.setPropertyURI(op.getURI());
    				objectPropertyStatements.add(ops);
    			}
    		}
     	} finally {
            if(qexec!=null) qexec.close();
    		tempModel.close();
    		ontModel.close();
     		dataset.getLock().leaveCriticalSection();
     		w.close();
    	}
     	return objectPropertyStatements;
    }

    @Override
    public List<Individual> getRelatedIndividuals(String propertyURI) {
    	if (propertyURI == null) {
    		return null;
    	}
    	List<Individual> relatedIndividuals = new ArrayList<Individual>();

    	DatasetWrapper w = getDatasetWrapper();
    	Dataset dataset = w.getDataset();
    	dataset.getLock().enterCriticalSection(Lock.READ);
    	try {
    		String valuesOfProperty =
    			"SELECT ?object " +
    			"WHERE{ <" + this.individualURI + "> <" +
    			        propertyURI + "> ?object } \n";
    		ResultSet values = QueryExecutionFactory.create(
    		        QueryFactory.create(valuesOfProperty), dataset)
    		                .execSelect();
    		QuerySolution result = null;
    	    while (values.hasNext()) {
    	    	result = values.next();
    	    	RDFNode value = result.get("object");
    	    	try {
        	    	if (value.canAs(OntResource.class)) {
            	    	relatedIndividuals.add(
            	    		new IndividualSDB(
            	    		        value.as(OntResource.class).getURI(),
            	    		                this.dwf,
            	    		                datasetMode,
            	    		                webappDaoFactory) );
            	    }
    	    	} catch (IndividualNotFoundException e) {
    	    	    // don't add to the list
    	    	}
    	    }
    	} finally {
    		dataset.getLock().leaveCriticalSection();
    		w.close();
    	}
    	return relatedIndividuals;
    }

    @Override
    public Individual getRelatedIndividual(String propertyURI) {
    	if (propertyURI == null) {
    		return null;
    	}
    	DatasetWrapper w = getDatasetWrapper();
    	Dataset dataset = w.getDataset();
    	dataset.getLock().enterCriticalSection(Lock.READ);
    	try {
    		String valueOfProperty =
    			"SELECT ?object " +
    			"WHERE{ <" + this.individualURI + "> <" +
    			        propertyURI + "> ?object } \n";
    		QueryExecution qe = QueryExecutionFactory.create(
                    QueryFactory.create(valueOfProperty), dataset);
    		try {
    		    ResultSet results = qe.execSelect();
        		if (results.hasNext()) {
            		QuerySolution result = results.next();
            		RDFNode value = result.get("object");
            	    if (value != null && value.canAs(OntResource.class)) {
            	        try {
                	    	return new IndividualSDB(
                	    	        value.as(OntResource.class).getURI(),
                	    	                dwf, datasetMode, webappDaoFactory);
            	        } catch (IndividualNotFoundException e) {
            	            return null;
            	        }
            	    }
        	    }
        		return null;
    		} finally {
    		    qe.close();
    		}
    	} finally {
    		dataset.getLock().leaveCriticalSection();
    		w.close();
    	}
    }

    public List<ObjectProperty> getObjectPropertyList() {
        if (this.propertyList != null) {
            return this.propertyList;
        } else {
            try {
                webappDaoFactory.getObjectPropertyDao()
                        .fillObjectPropertiesForIndividual( this );
            } catch (Exception e) {
                log.error("Could not fillEntityProperties for " + this.getURI(), e);
            }
            return this.propertyList;
        }
    }

    @Override
    public List<ObjectProperty> getPopulatedObjectPropertyList() {
        if (populatedObjectPropertyList == null) {
            populatedObjectPropertyList = webappDaoFactory
                    .getObjectPropertyDao().getObjectPropertyList(this);
        }
        return populatedObjectPropertyList;
    }

    @Override
    public Map<String,ObjectProperty> getObjectPropertyMap() {
    	if (this.objectPropertyMap != null) {
    		return objectPropertyMap;
    	} else {
    		Map map = new HashMap<String,ObjectProperty>();
    		if (this.propertyList == null) {
    			getObjectPropertyList();
    		}
			for (ObjectProperty op : this.propertyList) {
				if (op.getURI() != null) {
					map.put(op.getURI(), op);
				}
			}
    		this.objectPropertyMap = map;
    		return map;
    	}
    }

    public List<DataPropertyStatement> getDataPropertyStatements() {
        if (this.dataPropertyStatements != null) {
            return this.dataPropertyStatements;
        } else {
            try {
                webappDaoFactory.getDataPropertyStatementDao()
                        .fillExistingDataPropertyStatementsForIndividual(this);
            } catch (Exception e) {
                log.error("Could not fill existing DataPropertyStatements for "
                                + this.getURI(), e);
            }
            return this.dataPropertyStatements;
        }
    }

    public List getDataPropertyList() {
        if (this.datatypePropertyList != null) {
            return this.datatypePropertyList;
        } else {
            try {
                webappDaoFactory.getDataPropertyDao()
                        .fillDataPropertiesForIndividual( this );
            } catch (Exception e) {
                log.error("Could not fill data properties for " + this.getURI(), e);
            }
            return this.datatypePropertyList;
        }
    }

    @Override
    public List<DataProperty> getPopulatedDataPropertyList() {
        if (populatedDataPropertyList == null) {
            populatedDataPropertyList = webappDaoFactory.getDataPropertyDao()
                    .getDataPropertyList(this);
        }
        return populatedDataPropertyList;
    }

    @Override
    public Map<String,DataProperty> getDataPropertyMap() {
    	if (this.dataPropertyMap != null) {
    		return dataPropertyMap;
    	} else {
    		Map map = new HashMap<String,DataProperty>();
    		if (this.datatypePropertyList == null) {
    			getDataPropertyList();
    		}
			for (DataProperty dp : this.datatypePropertyList) {
				if (dp.getURI() != null) {
					map.put(dp.getURI(), dp);
				}
			}
    		this.dataPropertyMap = map;
    		return map;
    	}
    }

    @Override
    public List<DataPropertyStatement> getDataPropertyStatements(String propertyUri) {
        List<DataPropertyStatement> stmts = this.dataPropertyStatements;
        if (stmts == null) {
            return sparqlForDataPropertyStatements(propertyUri);
        } else {
            List<DataPropertyStatement> stmtsForProp = new ArrayList<DataPropertyStatement>();
            for (DataPropertyStatement stmt : stmts) {
                if (stmt.getDatapropURI().equals(propertyUri)) {
                    stmtsForProp.add(stmt);
                }
            }
            return stmtsForProp;
        }
    }

    @Override
    public String getDataValue(String propertyUri) {
        if (propertyUri == null) {
            log.error("Cannot retrieve value for null property");
            return null;
        } else if (this.getURI() == null) {
            log.error("Cannot retrieve value of property " + propertyUri +
                    " for anonymous individual");
            return null;
        } else {
            List<DataPropertyStatement> stmts = sparqlForDataPropertyStatements(
                    propertyUri);
            if (stmts != null && stmts.size() > 0) {
                return stmts.get(0).getData();
            }
        }
        return null; // not found
    }

    @Override
    public List<String> getDataValues(String propertyUri) {
        List<String> values = new ArrayList<String>();
        if (propertyUri == null) {
            log.error("Cannot retrieve value for null property");
            return null;
        } else if (this.getURI() == null) {
            log.error("Cannot retrieve value of property " + propertyUri +
                    " for anonymous individual");
            return null;
        } else {
            List<DataPropertyStatement> stmts = sparqlForDataPropertyStatements(
                    propertyUri);
            if (stmts != null) {
                for (DataPropertyStatement stmt : stmts) {
                    values.add(stmt.getData());
                }
            }
            return values;
        }
    }

    private List<DataPropertyStatement> sparqlForDataPropertyStatements(final String propertyUri) {
        final List<DataPropertyStatement> stmts = new ArrayList<DataPropertyStatement>();
		final IndividualSDB individualSDB = this;

        String queryStr = "SELECT (str(?value) as ?valueString) WHERE { <"
                + this.getURI() + "> <" + propertyUri + "> ?value }";
        try {
			webappDaoFactory.getRDFService().sparqlSelectQuery(
					queryStr, new ResultSetConsumer() {
						@Override
						protected void processQuerySolution(QuerySolution qs) {
							RDFNode node = qs.get("valueString");
							if (!node.isLiteral()) {
								log.debug("Ignoring non-literal value for " + node +
										" for property " + propertyUri);
							} else {
								Literal lit = node.asLiteral();
								DataPropertyStatement stmt = new DataPropertyStatementImpl();

								stmt.setData(lit.getLexicalForm());
								stmt.setDatatypeURI(lit.getDatatypeURI());
								stmt.setLanguage(lit.getLanguage());
								stmt.setDatapropURI(propertyUri);
								stmt.setIndividualURI(individualSDB.getURI());
								stmt.setIndividual(individualSDB);
								stmts.add(stmt);
							}
						}
					});
        } catch (RDFServiceException e) {
            log.error(e,e);
            throw new RuntimeException(e);
        }
        return stmts;
    }

    public List<DataPropertyStatement> getExternalIds() {
        if (this.externalIds != null) {
            return this.externalIds;
        } else {
            try {
                List<DataPropertyStatement> dpsList =
                        new ArrayList<DataPropertyStatement>();
                dpsList.addAll(webappDaoFactory.getIndividualDao()
                        .getExternalIds(this.getURI(), null));
                this.externalIds = dpsList;
            } catch (Exception e) {
                log.error("Could not fill external IDs for " + this.getURI(), e);
            }
            return this.externalIds;
        }
    }

    @Override
    public List<VClass> getVClasses() {
    	return getVClasses(false);
    }

    @Override
    public List<VClass> getVClasses(boolean assertedOnly) {
    	if (assertedOnly) {
    		if (directVClasses != null) {
    			return directVClasses;
    		} else {
    			directVClasses = getMyVClasses(true);
    			return directVClasses;
    		}
    	} else {
    		if (allVClasses != null) {
    			return allVClasses;
    		} else {
    			allVClasses = getMyVClasses(false);
    			return allVClasses;
    		}
    	}
    }

    private List<VClass> getMyVClasses(boolean assertedOnly) {
		List<VClass> vClassList = new ArrayList<VClass>();
		Model tempModel = null;
		if (ind.getModel().contains((Resource) null, RDF.type, (RDFNode) null)){
		    tempModel = ind.getModel();
		} else {
			tempModel = ModelFactory.createDefaultModel();
			String getTypesQuery = buildMyVClassesQuery(assertedOnly);

    		RDFService service = webappDaoFactory.getRDFService();
        	try {
				service.sparqlConstructQuery(getTypesQuery, tempModel);
        	} catch (RDFServiceException e) {
        	    throw new RuntimeException(e);
        	}
		}
    	StmtIterator stmtItr = tempModel.listStatements(
    	        (Resource) null, RDF.type, (RDFNode) null);
    	LinkedList<String> list = new LinkedList<String>();
    	while(stmtItr.hasNext()){
    		Statement stmt = stmtItr.nextStatement();
    		if (stmt.getObject().isResource() && !stmt.getObject().isAnon()) {
    			list.add(((Resource) stmt.getObject()).getURI());
    		}
    	}
    	Iterator<String> itr = null;
    	VClassDao checkSubClass = this.webappDaoFactory.getVClassDao();
    	boolean directTypes = false;
    	String currentType = null;
	    ArrayList<String> done = new ArrayList<String>();

	    /* Loop for comparing starts here */
	    if(assertedOnly){
        	while(!directTypes){
        		 itr = list.listIterator();

        		do{
        			if(itr.hasNext()){
        		 currentType = itr.next();}
        			else{
        				directTypes = true; // get next element for comparison
        			 break;}
        		}while(done.contains(currentType));

        		if(directTypes)
        			break;
        		    // check to see if it's all over otherwise start comparing
        		else
        	    itr = list.listIterator();

            	while(itr.hasNext()){
            		String nextType = itr.next();
            	    if(checkSubClass.isSubClassOf(currentType, nextType)
            	            && !currentType.equalsIgnoreCase(nextType)){
            	    	itr.remove();
            	    }
            	}

            	done.add(currentType);  // add the uri to done list.
        	}
	    }

    	/* Loop for comparing ends here */
	    Iterator<String> typeIt = list.iterator();

		for (Iterator it = typeIt; it.hasNext();) {
			Resource type = ResourceFactory
			        .createResource(it.next().toString());
			String typeURI = (!type.isAnon())
			        ? type.getURI()
			        : VitroVocabulary.PSEUDO_BNODE_NS
			                + type.getId().toString();
			if (type.getNameSpace() == null ||
			        (!webappDaoFactory.getNonuserNamespaces()
			                .contains(type.getNameSpace())) ) {
				VClass vc = webappDaoFactory.getVClassDao()
				        .getVClassByURI(type.getURI());
				if (vc != null) {
					vClassList.add(vc);
				}
			}
		}

		try {
			Collections.sort(vClassList);
		} catch (Exception e) {
		    log.error("Unable to sort VClass list", e);
		}

		return vClassList;
	}

	/**
	 * If we are restricting to asserted types, either by request or by dataset
	 * mode, then filter by graph and include a UNION clause to support
	 * retrieving inferred types from the unnamed base graph, as in Sesame and
	 * OWLIM.
	 */
	private String buildMyVClassesQuery(boolean assertedOnly) {
		SDBDatasetMode queryMode = assertedOnly ? ASSERTIONS_ONLY : datasetMode;

		String filterBlock = WebappDaoFactorySDB.getFilterBlock(new String[] { "?g" }, queryMode);

		if (filterBlock.isEmpty()) {
			return
				"CONSTRUCT { <" + this.individualURI + "> " + "<" + RDF.type + "> ?types }\n" +
				"WHERE { <" + this.individualURI +"> <" +RDF.type+ "> ?types } \n";
		} else {
			String unionBlock = (queryMode.equals(ASSERTIONS_ONLY)) ?
				"" :
				"UNION { <" + this.individualURI +"> <" +RDF.type+ "> ?types }";
			return
				"CONSTRUCT{ <" + this.individualURI + "> " + "<" + RDF.type + "> ?types }\n" +
				"WHERE{ { GRAPH ?g"
				+ " { <" + this.individualURI +"> <" +RDF.type+ "> ?types } \n"
			    + filterBlock
				+ "} \n"
			    + unionBlock
			    + "} \n";
		}
	}

	/**
	 * The base method in {@link IndividualImpl} is adequate if the reasoner is
	 * up to date.
	 *
	 * If the base method returns false, check directly to see if
	 * any of the super classes of the direct classes will satisfy this request.
	 */
	@Override
	public boolean isVClass(String uri) {
    	if (uri == null || this.getURI() == null) {
    		return false;
    	}
        String queryString = "ASK { <" + this.getURI() + "> a <" + uri + "> }";
        try {
            return webappDaoFactory.getRDFService().sparqlAskQuery(queryString);
        } catch (RDFServiceException e) {
            throw new RuntimeException(e);
        }
	}

    /**
     * Overriding the base method so that we can do the sorting by arbitrary property here.  An
     * IndividualSDB has a reference back to the model; everything else is just a dumb bean (for now).
     */
    @Override
    protected void sortEnts2EntsForDisplay(){
        if( getObjectPropertyList() == null ) return;

		for (ObjectProperty prop : getObjectPropertyList()) {
			/*  if (prop.getObjectIndividualSortPropertyURI()==null) {
            	prop.sortObjectPropertyStatementsForDisplay(prop,prop.getObjectPropertyStatements());
            } else {*/
			prop.sortObjectPropertyStatementsForDisplay(prop, prop.getObjectPropertyStatements());
        /*  }*/
		}
    }

    private Collator collator = Collator.getInstance();

    private void sortObjectPropertyStatementsForDisplay(ObjectProperty prop) {
    	try {
    		log.info("Doing special sort for "+prop.getDomainPublic());
    		final String sortPropertyURI = prop.getObjectIndividualSortPropertyURI();
            String tmpDir;
            boolean tmpAsc;

            tmpDir = prop.getDomainEntitySortDirection();

            //valid values are "desc" and "asc", anything else will default to ascending
            tmpAsc = !"desc".equalsIgnoreCase(tmpDir);

            final boolean dir = tmpAsc;
            Comparator comp = new Comparator(){
                final boolean cAsc = dir;

                public final int compare(Object o1, Object o2){
                    ObjectPropertyStatement e2e1= (ObjectPropertyStatement)o1, e2e2=(ObjectPropertyStatement)o2;
                    Individual e1 , e2;
                    e1 = e2e1 != null ? e2e1.getObject():null;
                    e2 = e2e2 != null ? e2e2.getObject():null;

                    Object val1 = null, val2 = null;
                    if( e1 != null ){
                        try {
                        	DataProperty dp = e1.getDataPropertyMap().get(sortPropertyURI);
                        	if (dp.getDataPropertyStatements() != null && dp.getDataPropertyStatements().size()>0) {
                        		val1 = dp.getDataPropertyStatements().get(0).getData();
                        	}
                        }
                        catch (Exception e) {
                        	val1 = "";
                        }
                    } else {
                        log.warn( "IndividualSDB.sortObjectPropertiesForDisplay passed object property statement with no range entity.");
                    }

                    if( e2 != null ){
                        try {
                        	DataProperty dp = e2.getDataPropertyMap().get(sortPropertyURI);
                        	if (dp.getDataPropertyStatements() != null && dp.getDataPropertyStatements().size()>0) {
                        		val2 = dp.getDataPropertyStatements().get(0).getData();
                        	}
                        }
                        catch (Exception e) {
                        	val2 = "";
                        }
                    } else {
                        log.warn( "IndividualSDB.sortObjectPropertyStatementsForDisplay() was passed an object property statement with no range entity.");
                    }

                    int rv = 0;
                    try {
                        if( val1 instanceof String )
                        	rv = collator.compare( ((String)val1) , ((String)val2) );
                            //rv = ((String)val1).compareTo((String)val2);
                        else if( val1 instanceof Date ) {
                            DateTime dt1 = new DateTime(val1);
                            DateTime dt2 = new DateTime(val2);
                            rv = dt1.compareTo(dt2);
                        }
                        else
                            rv = 0;
                    } catch (NullPointerException e) {
                        log.error(e, e);
                    }

                    if( cAsc )
                        return rv;
                    else
                        return rv * -1;
                }
            };
            try {
                getObjectPropertyStatements().sort(comp);
            } catch (Exception e) {
                log.error("Exception sorting object property statements for object property "+this.getURI());
            }


    	} catch (Exception e) {
    		log.error(e, e);
    	}
    }

	@Override
	public void resolveAsFauxPropertyStatements(List<ObjectPropertyStatement> list) {
		webappDaoFactory.getObjectPropertyStatementDao().resolveAsFauxPropertyStatements(list);
	}

}
