InsightManagerImpl.java

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.ostrichemulators.semtool.rdf.engine.impl;

import com.ostrichemulators.semtool.model.vocabulary.SEMPERS;
import info.aduna.iteration.Iterations;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.openrdf.model.Literal;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.RDFS;
import org.openrdf.model.vocabulary.DCTERMS;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import com.ostrichemulators.semtool.model.vocabulary.OLO;
import com.ostrichemulators.semtool.model.vocabulary.SP;
import com.ostrichemulators.semtool.model.vocabulary.SPIN;
import com.ostrichemulators.semtool.model.vocabulary.SPL;
import com.ostrichemulators.semtool.model.vocabulary.UI;
import com.ostrichemulators.semtool.om.Insight;
import com.ostrichemulators.semtool.om.InsightOutputType;
import com.ostrichemulators.semtool.om.Parameter;
import com.ostrichemulators.semtool.rdf.engine.api.IEngine;
import com.ostrichemulators.semtool.rdf.engine.api.InsightManager;
import com.ostrichemulators.semtool.om.Perspective;
import com.ostrichemulators.semtool.rdf.engine.util.NodeDerivationTools;
import static com.ostrichemulators.semtool.rdf.query.util.QueryExecutorAdapter.getDate;
import com.ostrichemulators.semtool.rdf.query.util.impl.OneVarListQueryAdapter;
import com.ostrichemulators.semtool.user.User;

import com.ostrichemulators.semtool.util.UriBuilder;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.ListIterator;
import org.openrdf.model.Model;
import org.openrdf.model.impl.LinkedHashModel;
import org.openrdf.model.impl.StatementImpl;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.model.vocabulary.XMLSchema;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.rio.RDFParser;
import org.openrdf.rio.helpers.StatementCollector;
import org.openrdf.rio.turtle.TurtleParser;

/**
 *
 * @author ryan
 */
public class InsightManagerImpl implements InsightManager {

	private static final Logger log = Logger.getLogger( InsightManagerImpl.class );

	private UriBuilder urib = UriBuilder.getBuilder( SEMPERS.NAMESPACE );
	private final Map<URI, Insight> insights = new HashMap<>();
	private final List<Perspective> perspectives = new ArrayList<>();

	public InsightManagerImpl() {

	}

	public InsightManagerImpl( InsightManager im ) {
		urib = UriBuilder.getBuilder( im.getInsightNamespace() );
		perspectives.addAll( deepcopy( im.getPerspectives() ) );

		for ( Perspective p : perspectives ) {
			for ( Insight i : p.getInsights() ) {
				insights.put( i.getId(), i );
			}
		}
	}

	@Override
	public void setInsightNamespace( String ns ) {
		urib = UriBuilder.getBuilder( ns );
	}

	@Override
	public String getInsightNamespace() {
		return urib.toString();
	}

	@Override
	public void addAll( Collection<Perspective> newdata, boolean clearfirst ) {
		if ( newdata.equals( perspectives ) ) {
			return; // nothing to copy
		}

		if ( clearfirst ) {
			perspectives.clear();
			insights.clear();
		}

		perspectives.addAll( deepcopy( newdata ) );
		for ( Perspective p : perspectives ) {
			for ( Insight i : p.getInsights() ) {
				insights.put( i.getId(), i );
			}
		}
	}

	@Override
	public List<Perspective> getPerspectives() {
		return new ArrayList<>( perspectives );
	}

	public static InsightManager createFromRepository( Repository repo ) {
		InsightManagerImpl imi = new InsightManagerImpl();
		RepositoryConnection rc = null;
		try {
			if ( !repo.isInitialized() ) {
				repo.initialize();
			}
			rc = repo.getConnection();
			imi.loadFromRepository( rc );
		}
		catch ( RepositoryException re ) {
			log.error( re, re );
		}
		finally {
			if ( null != rc ) {
				try {
					rc.close();
				}
				catch ( RepositoryException re ) {
					log.warn( re, re );
				}
			}

		}
		return imi;
	}

	public void loadFromRepository( RepositoryConnection rc ) {
		List<Perspective> persps = new ArrayList<>();
		try {
			RepositoryResult<Statement> rrs = rc.getStatements( null, RDF.TYPE,
					SEMPERS.Perspective, true );
			List<Statement> stmts = Iterations.asList( rrs );
			Map<Perspective, Integer> ordering = new HashMap<>();
			for ( Statement s : stmts ) {
				Perspective p = loadPerspective( URI.class.cast( s.getSubject() ), rc, urib );

				List<Statement> slotstmts = Iterations.asList(
						rc.getStatements( p.getId(), OLO.index, null, false ) );
				if ( !slotstmts.isEmpty() ) {
					Literal slotval = Literal.class.cast( slotstmts.get( 0 ).getObject() );
					ordering.put( p, slotval.intValue() );
				}

				persps.add( p );
			}

			persps.sort( new Comparator<Perspective>() {

				@Override
				public int compare( Perspective o1, Perspective o2 ) {
					int o1slot = ordering.getOrDefault( o1, Integer.MAX_VALUE );
					int o2slot = ordering.getOrDefault( o2, Integer.MAX_VALUE );
					return o1slot - o2slot;
				}
			} );

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

		perspectives.addAll( persps );

		for ( Perspective p : persps ) {
			for ( Insight i : p.getInsights() ) {
				insights.put( i.getId(), i );
			}
		}
	}

	@Override
	public boolean isEmpty() {
		return ( perspectives.isEmpty() && insights.isEmpty() );
	}

	@Override
	public Perspective getSystemPerspective( IEngine eng ) {
		Perspective persps = new Perspective( urib.uniqueUri(), "Generic Perspective",
				"System Generated Generic Perspective" );

		Insight metamodel = new Insight( "View the Database Metamodel", null,
				InsightOutputType.GRAPH_METAMODEL );

		Insight explore = new Insight( "Explore an instance of a selected node type",
				"SELECT DISTINCT ?instance WHERE { ?instance ?s ?o }",
				InsightOutputType.GRAPH );
		explore.setId( urib.uniqueUri() );

		Parameter concept = new Parameter( "Concept",
				NodeDerivationTools.getConceptQuery( eng ) );
		concept.setId( urib.uniqueUri() );

		Parameter instance = new Parameter( "Instance",
				"SELECT ?instance WHERE { ?instance a ?concept }" );
		instance.setId( urib.uniqueUri() );

		explore.setParameters( Arrays.asList( concept, instance ) );

		String nespql = "CONSTRUCT { ?instance ?step ?neighbor } WHERE {"
				+ "  ?instance ?step ?neighbor ."
				+ "  ?step a ?stepType ."
				+ "  FILTER ( ?stepType = owl:ObjectProperty )"
				+ "}";
		Insight neighbor = new Insight( "Show One Neighbor Away from Selected Node",
				nespql, InsightOutputType.GRAPH );
		neighbor.setId( urib.uniqueUri() );
		neighbor.setParameters( Arrays.asList( concept, instance ) );

		persps.setInsights( Arrays.asList( metamodel, explore, neighbor ) );
		return persps;
	}

	/**
	 * Gets all Parameter objects under the passed-in Insight URI.
	 *
	 * @param insightURI -- (URI) An Insight URI.
	 *
	 * @return -- (Collection<Parameter>) Described above.
	 */
	private static Collection<Parameter> loadParameters( Insight insight,
			RepositoryConnection rc ) {
		List<Parameter> colInsightParameters = new ArrayList<>();

		try {
			// get this insight's constraints/parameters
			Collection<Statement> paramIds = Iterations.asList( rc.getStatements( insight.getId(),
					SPIN.constraint, null, false ) );
			for ( Statement s : paramIds ) {
				URI paramId = URI.class.cast( s.getObject() );
				Parameter parameter = new Parameter();
				parameter.setId( paramId );

				// get data about this parameter
				Collection<Statement> data
						= Iterations.asList( rc.getStatements( paramId, null, null, false ) );
				for ( Statement d : data ) {
					URI pred = d.getPredicate();
					Value val = d.getObject();

					if ( RDFS.LABEL.equals( pred ) ) {
						parameter.setLabel( val.stringValue() );
					}
					else if ( SPL.predicate.equals( pred ) ) {
						List<Statement> preddata
								= Iterations.asList( rc.getStatements( URI.class.cast( val ),
												RDFS.LABEL, null, true ) );
						if ( !preddata.isEmpty() ) {
							// parameter.setVariable( preddata.get( 0 ).getObject().stringValue() );
						}
					}
					else if ( SP.query.equals( pred ) ) {
						List<Statement> preddata
								= Iterations.asList( rc.getStatements( URI.class.cast( val ),
												SP.text, null, true ) );
						if ( !preddata.isEmpty() ) {
							parameter.setDefaultQuery( preddata.get( 0 ).getObject().stringValue() );
						}
					}
					else if ( SPL.valueType.equals( pred ) ) {
						parameter.setParameterType( val.stringValue() );
					}
				}

				colInsightParameters.add( parameter );
			}
		}
		catch ( RepositoryException e ) {
			log.error( e, e );
		}
		return colInsightParameters;
	}

	private static List<Insight> loadInsights( Perspective perspective,
			RepositoryConnection rc, UriBuilder urib ) {
		List<Insight> list = new ArrayList<>();
		if ( perspective != null ) {
			String query = "SELECT ?item "
					+ "WHERE {"
					+ "  ?perspective olo:slot ?slot ."
					+ "  ?slot olo:item ?item ."
					+ "  ?slot olo:index ?index . "
					+ "} ORDER BY ?index";
			OneVarListQueryAdapter<URI> orderquery
					= OneVarListQueryAdapter.getUriList( query );
			orderquery.bind( "perspective", perspective.getId() );
			orderquery.addNamespace( OLO.PREFIX, OLO.NAMESPACE );

			List<URI> insightUris
					= AbstractSesameEngine.getSelectNoEx( orderquery, rc, true );

			for ( URI id : insightUris ) {
				list.add( loadInsight( id, rc, urib ) );
			}
		}

		return list;
	}

	@Override
	public Insight getInsight( URI insightURI ) {
		return insights.get( insightURI );
	}

	private static Insight loadInsight( URI insightURI, RepositoryConnection rc,
			UriBuilder urib ) {

		Insight insight = null;
		try {
			// need a couple things here...the insight data, the query, 
			// and view data (the playsheet)
			List<Statement> stmts
					= Iterations.asList( rc.getStatements( insightURI, null, null, true ) );

			// the query itself
			List<Statement> qstmts
					= Iterations.asList( rc.getStatements( insightURI, SPIN.body, null, true ) );
			if ( !qstmts.isEmpty() ) {
				URI body = URI.class.cast( qstmts.get( 0 ).getObject() );
				List<Statement> querys
						= Iterations.asList( rc.getStatements( body, SP.text, null, true ) );
				stmts.addAll( querys );
			}
			// the data view
			List<Statement> dvstmts
					= Iterations.asList( rc.getStatements( insightURI, UI.dataView, null, true ) );
			if ( !dvstmts.isEmpty() ) {
				URI view = URI.class.cast( dvstmts.get( 0 ).getObject() );
				List<Statement> dvs
						= Iterations.asList( rc.getStatements( view, UI.viewClass, null, true ) );
				stmts.addAll( dvs );
			}

			if ( !stmts.isEmpty() ) {
				insight = insightFromStatements( stmts );

				// finally, set the parameters
				Collection<Parameter> params = loadParameters( insight, rc );
				insight.setParameters( params );
			}
		}
		catch ( RepositoryException e ) {
			// TODO Auto-generated catch block
			log.error( e, e );
		}

		if ( null == insight ) {
			throw new IllegalArgumentException( "unknown insight: " + insightURI );
		}
		return insight;
	}

	@Override
	public Perspective getPerspective( URI perspectiveURI ) {
		ListIterator<Perspective> lit = perspectives.listIterator();
		while ( lit.hasNext() ) {
			Perspective old = lit.next();
			if ( old.getId().equals( perspectiveURI ) ) {
				return old;
			}
		}

		throw new IllegalArgumentException( "unknown perspective: " + perspectiveURI );
	}

	@Override
	public URI add( Perspective p ) {
		p.setId( urib.uniqueUri() );
		perspectives.add( p );
		return p.getId();
	}

	@Override
	public void update( Perspective p ) {
		ListIterator<Perspective> lit = perspectives.listIterator();
		while ( lit.hasNext() ) {
			Perspective old = lit.next();
			if ( old.getId().equals( p.getId() ) ) {
				lit.set( p );
			}
		}
	}

	@Override
	public void addInsight( Perspective p, Insight i, int pos ) {
		insights.put( i.getId(), i );
		if ( pos < 0 ) {
			pos = p.getInsights().size();
		}

		p.getInsights().add( pos, i );
	}

	@Override
	public void remove( Perspective p ) {
		perspectives.remove( p );
	}

	@Override
	public URI add( Insight p ) {
		p.setId( urib.uniqueUri() );
		insights.put( p.getId(), p );
		return p.getId();
	}

	@Override
	public void update( Insight p ) {
		insights.put( p.getId(), p );
	}

	@Override
	public void remove( Insight p ) {
		insights.remove( p.getId() );
	}

	private static Perspective loadPerspective( URI perspectiveURI, RepositoryConnection rc,
			UriBuilder urib ) {
		try {
			Perspective perspective = new Perspective( perspectiveURI );
			Collection<Statement> stmts
					= Iterations.asList( rc.getStatements( perspectiveURI, null, null, false ) );
			for ( Statement s : stmts ) {
				URI pred = s.getPredicate();
				Value val = s.getObject();

				if ( val instanceof Literal ) {
					if ( RDFS.LABEL.equals( pred ) ) {
						perspective.setLabel( val.stringValue() );
					}
					else if ( DCTERMS.DESCRIPTION.equals( pred ) ) {
						perspective.setDescription( val.stringValue() );
					}
				}
			}

			perspective.setInsights( loadInsights( perspective, rc, urib ) );
			return perspective;
		}
		catch ( RepositoryException e ) {
			log.error( e, e );
		}
		throw new IllegalArgumentException( "unknown perspective: " + perspectiveURI );
	}

	private static Insight insightFromStatements( Collection<Statement> stmts ) {
		Insight insight = new Insight();

		for ( Statement stmt : stmts ) {
			URI pred = stmt.getPredicate();
			Value val = stmt.getObject();
			if ( val instanceof Literal ) {
				Literal obj = Literal.class.cast( val );

				if ( RDFS.LABEL.equals( pred ) ) {
					insight.setId( URI.class.cast( stmt.getSubject() ) );

					insight.setLabel( obj.stringValue() );
				}
				else if ( SEMPERS.INSIGHT_OUTPUT_TYPE.equals( pred ) ) {
					try {
						insight.setOutput( InsightOutputType.valueOf( obj.stringValue() ) );
					}
					catch ( IllegalArgumentException iae ) {
						log.warn( "unknown insight output type: " + obj.stringValue()
								+ "(using grid instead)" );
						insight.setOutput( InsightOutputType.GRID );
					}
				}
				else if ( DCTERMS.CREATOR.equals( pred ) ) {
					insight.setCreator( obj.stringValue() );
				}
				else if ( DCTERMS.CREATED.equals( pred ) ) {
					URI uri = obj.getDatatype();
					if ( XMLSchema.DATE.equals( uri ) || XMLSchema.DATETIME.equals( uri ) ) {
						insight.setCreated( getDate( Literal.class.cast( obj ).calendarValue() ) );
					}
				}
				else if ( DCTERMS.MODIFIED.equals( pred ) ) {
					URI uri = obj.getDatatype();
					if ( XMLSchema.DATE.equals( uri ) || XMLSchema.DATETIME.equals( uri ) ) {
						insight.setModified( getDate( Literal.class.cast( obj ).calendarValue() ) );
					}
				}
				else if ( DCTERMS.DESCRIPTION.equals( pred ) ) {
					insight.setDescription( obj.stringValue() );
				}
				else if ( SP.text.equals( pred ) ) {
					insight.setSparql( obj.stringValue() );
				}
				else if ( SEMPERS.INSIGHT_OUTPUT_TYPE.equals( pred ) ) {
					insight.setOutput( InsightOutputType.valueOf( obj.stringValue() ) );
				}
			}
		}

		// make sure every insight has an output type
		if ( null == insight.getOutput() ) {
			insight.setOutput( InsightOutputType.GRID );
		}

		return insight;
	}

	public static Model getModel( InsightManager im, User user ) {
		Model statements = new LinkedHashModel();
		int idx = 0;

		RDFParser parser = new TurtleParser();
		StatementCollector coll = new StatementCollector();
		parser.setRDFHandler( coll );
		try ( InputStream is = IEngine.class.getResourceAsStream( "/models/sempers.ttl" ) ) {
			parser.parse( is, SEMPERS.BASE_URI );
		}
		catch ( Exception e ) {
			log.warn( "could not include sempers.ttl ontology in statements", e );
		}

		statements.addAll( coll.getStatements() );

		ValueFactory vf = new ValueFactoryImpl();
		for ( Perspective p : im.getPerspectives() ) {
			statements.addAll( getStatements( p, user ) );
			statements.add( new StatementImpl( p.getId(), OLO.index,
					vf.createLiteral( idx++ ) ) );
		}

		return statements;
	}

	/**
	 * Converts the Perspective, its Insights and Parameters (and ordering!) to
	 * Statements for adding to a Repository. If any Perspective, Insight, or
	 * Parameter is missing an ID, a new one will be generated for it, and set on
	 * the object
	 *
	 * @param p the perspective to convert.
	 * @param user
	 * @return a list of statements that completely represent the perspective tree
	 */
	public static Model getStatements( Perspective p, User user ) {
		Model statements = new LinkedHashModel();
		UriBuilder urib = UriBuilder.getBuilder( SEMPERS.NAMESPACE );

		// if we're creating statements, mark our repository as an insights db
		statements.add( new StatementImpl( SEMPERS.INSIGHT_DB, RDF.TYPE,
				SEMPERS.INSIGHT_CORE_TYPE ) );

		ValueFactory vf = new ValueFactoryImpl();

		if ( null == p.getId() ) {
			p.setId( urib.build( p.getLabel() ) );
		}
		statements.addAll( getPerspectiveStatements( p, vf, urib, user ) );

		for ( Insight i : p.getInsights() ) {
			final String piname = p.getLabel() + "-" + i.getLabel();

			if ( null == i.getId() ) {
				i.setId( urib.build( piname ) );
			}
			statements.addAll( getInsightStatements( i, vf, urib, user ) );

			for ( Parameter a : i.getInsightParameters() ) {
				final String pianame = piname + "-" + a.getLabel();

				URI predicateUri = urib.build( pianame + "-pred" );
				URI queryUri = urib.build( pianame + "-query" );

				if ( null == a.getId() ) {
					a.setId( urib.build( pianame ) );
				}

				statements.addAll( getParameterStatements( a, predicateUri, queryUri,
						vf, urib, user ) );
			}

			statements.addAll( getConstraintStatements( i, i.getInsightParameters() ) );
		}

		statements.addAll( getOrderingStatements( p, p.getInsights(), vf, urib ) );

		return statements;
	}

	protected static Model getPerspectiveStatements( Perspective p,
			ValueFactory vf, UriBuilder urib, User user ) {

		Model statements = new LinkedHashModel();
		URI pid = p.getId();
		Date now = new Date();

		statements.add( new StatementImpl( pid, RDF.TYPE, SEMPERS.Perspective ) );
		statements.add( new StatementImpl( pid, RDFS.LABEL,
				vf.createLiteral( p.getLabel() ) ) );
		if ( null != p.getDescription() ) {
			statements.add( new StatementImpl( pid, DCTERMS.DESCRIPTION,
					vf.createLiteral( p.getDescription() ) ) );
		}

		statements.add( new StatementImpl( pid, DCTERMS.CREATED,
				vf.createLiteral( now ) ) );
		statements.add( new StatementImpl( pid, DCTERMS.MODIFIED,
				vf.createLiteral( now ) ) );
		statements.add( new StatementImpl( pid, DCTERMS.CREATOR,
				vf.createLiteral( getAuthorInfo( user ) ) ) );

		return statements;
	}

	protected static Model getInsightStatements( Insight insight,
			ValueFactory vf, UriBuilder urib, User user ) {

		Model statements = new LinkedHashModel();
		URI iid = insight.getId();

		statements.add( new StatementImpl( iid, RDF.TYPE, SPIN.MagicProperty ) );
		statements.add( new StatementImpl( iid, RDFS.LABEL,
				vf.createLiteral( insight.getLabel() ) ) );
		if ( null != insight.getDescription() ) {
			statements.add( new StatementImpl( iid, DCTERMS.DESCRIPTION,
					vf.createLiteral( insight.getDescription() ) ) );
		}

		if ( null != insight.getOutput() ) {
			statements.add( new StatementImpl( iid, SEMPERS.INSIGHT_OUTPUT_TYPE,
					vf.createLiteral( insight.getOutput().toString() ) ) );
		}

		statements.add( new StatementImpl( iid, RDFS.SUBCLASSOF, SEMPERS.InsightProperties ) );
		statements.add( new StatementImpl( iid, DCTERMS.CREATED,
				vf.createLiteral( null == insight.getCreated() ? new Date()
								: insight.getCreated() ) ) );
		statements.add( new StatementImpl( iid, DCTERMS.MODIFIED,
				vf.createLiteral( new Date() ) ) );
		statements.add( new StatementImpl( iid, DCTERMS.CREATOR,
				vf.createLiteral( getAuthorInfo( user ) ) ) );

		String sparql = insight.getSparql();

		URI spinid = urib.build( insight.getLabel() + "-query" );
		statements.add( new StatementImpl( iid, SPIN.body, spinid ) );
		statements.add( new StatementImpl( spinid, SP.text,
				vf.createLiteral( sparql ) ) );

		// Insights can only have SELECT, CONSTRUCT, or DESCRIBE queries:
		URI bodytype;
		if ( sparql.toUpperCase().startsWith( "DESCRIBE" ) ) {
			bodytype = SP.Describe;
		}
		else if ( sparql.toUpperCase().startsWith( "CONSTRUCT" ) ) {
			bodytype = SP.Construct;
		}
		else {
			bodytype = SP.Select;
		}
		statements.add( new StatementImpl( spinid, RDF.TYPE, bodytype ) );

		return statements;
	}

	protected static Model getParameterStatements( Parameter parameter,
			URI predicateUri, URI queryUri, ValueFactory vf, UriBuilder urib,
			User user ) {

		Model statements = new LinkedHashModel();

		URI pid = parameter.getId();

		statements.add( new StatementImpl( pid, RDFS.LABEL,
				vf.createLiteral( parameter.getLabel() ) ) );

		statements.add( new StatementImpl( pid, SPL.predicate, predicateUri ) );
		statements.add( new StatementImpl( pid, SP.query, queryUri ) );

		statements.add( new StatementImpl( predicateUri, RDFS.LABEL,
				vf.createLiteral( parameter.getLabel() ) ) );
		statements.add( new StatementImpl( queryUri, SP.text,
				vf.createLiteral( parameter.getDefaultQuery() ) ) );

		return statements;
	}

	protected static Model getOrderingStatements( Perspective p,
			List<Insight> insights, ValueFactory vf, UriBuilder urib ) {
		Model statements = new LinkedHashModel();
		int idx = 0;
		for ( Insight i : insights ) {
			URI slot = urib.build( p.getLabel() + "-slot-" + Integer.toString( ++idx ) );
			statements.add( new StatementImpl( p.getId(), OLO.slot, slot ) );
			statements.add( new StatementImpl( slot, OLO.index, vf.createLiteral( idx ) ) );
			statements.add( new StatementImpl( slot, OLO.item, i.getId() ) );
		}

		return statements;
	}

	protected static Collection<Statement> getConstraintStatements( Insight ins,
			Collection<Parameter> params ) {
		Model statements = new LinkedHashModel();
		for ( Parameter p : params ) {
			statements.add( new StatementImpl( ins.getId(), SPIN.constraint, p.getId() ) );
		}
		return statements;
	}

	protected static String getAuthorInfo( User user ) {
		return ( null == user || null == user.getAuthorInfo()
				? "Created By Insight Manager, "
				+ System.getProperty( "release.nameVersion", "VA SEMOSS" )
				: user.getAuthorInfo() );
	}

	public static Collection<Perspective> deepcopy( Collection<Perspective> oldps ) {
		List<Perspective> perspectives = new ArrayList<>();
		for ( Perspective oldp : oldps ) {
			Perspective newp
					= new Perspective( oldp.getId(), oldp.getLabel(), oldp.getDescription() );
			List<Insight> newpInsights = new ArrayList<>();
			for ( Insight oldi : oldp.getInsights() ) {
				Insight newi = new Insight( oldi );
				newpInsights.add( newi );
			}
			newp.setInsights( newpInsights );

			perspectives.add( newp );
		}

		return perspectives;
	}
}