/** * Copyright (C) 2016 Pink Summit, LLC (info@pinksummit.com) * * Licensed 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 net.di2e.ecdr.describe.generator; import java.io.Serializable; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.Duration; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.namespace.QName; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import org.apache.commons.lang.StringUtils; import org.codice.ddf.security.common.Security; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatterBuilder; import org.joda.time.format.DateTimeParser; import org.joda.time.format.ISODateTimeFormat; import org.opengis.filter.Filter; import org.opengis.filter.sort.SortBy; import org.opengis.filter.sort.SortOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKTReader; import com.vividsolutions.jts.io.WKTWriter; import ddf.catalog.CatalogFramework; import ddf.catalog.data.Metacard; import ddf.catalog.data.Result; import ddf.catalog.data.impl.MetacardImpl; import ddf.catalog.filter.FilterBuilder; import ddf.catalog.filter.impl.SortByImpl; import ddf.catalog.operation.QueryResponse; import ddf.catalog.operation.impl.QueryImpl; import ddf.catalog.operation.impl.QueryRequestImpl; import ddf.security.Subject; import ddf.util.NamespaceMapImpl; import ddf.util.XPathHelper; import mil.ces.metadata.mdr.ns.gsip.tspi._2_0.core.EnvelopeType; import mil.ces.metadata.mdr.ns.gsip.tspi._2_0.core.EnvelopeType.LowerCorner; import mil.ces.metadata.mdr.ns.gsip.tspi._2_0.core.EnvelopeType.UpperCorner; import net.di2e.ecdr.describe.generator.util.DescribeXMLParser; import net.di2e.ecdr.describe.generator.util.TemporalCoverageHolder; import net.di2e.jaxb.cdr.describe.cc.ContentCollectionType; import net.di2e.jaxb.cdr.describe.cc.MIMETypeType; import net.di2e.jaxb.cdr.describe.cc.MetricsType; import net.di2e.jaxb.cdr.describe.cc.MimeTypes; import net.di2e.jaxb.cdr.describe.cc.RecordRateType; import net.di2e.jaxb.cdr.describe.cc.SecurityCoverage; import net.di2e.jaxb.cdr.describe.cc.SecurityType; import net.di2e.jaxb.cdr.describe.wrapper.Collection; import net.di2e.jaxb.cdr.describe.wrapper.Describe; import net.opengis.gml.v_3_2_1.DirectPositionType; import us.gov.ic.cvenum.ism.classification.all.CVEnumISMClassificationAll; import us.mil.ces.metadata.ddms._5.BoundingGeometryType; import us.mil.ces.metadata.ddms._5.CompoundCategoryIdentifierType; import us.mil.ces.metadata.ddms._5.CompoundKeywordIdentifierType; import us.mil.ces.metadata.ddms._5.CompoundResourceIdentifierType; import us.mil.ces.metadata.ddms._5.CompoundSourceIdentifierType; import us.mil.ces.metadata.ddms._5.CompoundTypeIdentifierType; import us.mil.ces.metadata.ddms._5.DescriptionType; import us.mil.ces.metadata.ddms._5.PlaceType; import us.mil.ces.metadata.ddms._5.ResourceType; import us.mil.ces.metadata.ddms._5.SubjectType; import us.mil.ces.metadata.ddms._5.TimePeriodType; import us.mil.ces.metadata.ddms._5.TitleType; public class DescribeGeneratorImpl implements DescribeGenerator { private static DateTimeFormatter formatter; static { DateTimeParser[] parsers = { ISODateTimeFormat.date().getParser(), ISODateTimeFormat.dateTime().getParser(), ISODateTimeFormat.dateTimeNoMillis().getParser(), ISODateTimeFormat.basicDateTime().getParser(), ISODateTimeFormat.basicDateTimeNoMillis().getParser() }; formatter = new DateTimeFormatterBuilder().append( null, parsers ).toFormatter(); } private static final Logger LOGGER = LoggerFactory.getLogger( DescribeGeneratorImpl.class ); private static DateFormat df = new SimpleDateFormat( "yyyy-MM-dd\'T\'HH:mm:ss.SSSZ" ); private static final String DESCRIBE_EXT_NAMESPACE = "urn:cdr-ex:describe:ddms-ext:1.0"; private static final String COUNT_ATTRIBUTE = "count"; private static final String RESULT_WKT = "result-wkt"; private static final String XPATH_KEYWORD = "//*[local-name()=\'keyword\']"; private static final String XPATH_CATEGORY = "//*[local-name()=\'category\']"; private static final String XPATH_TYPE = "//ddms:type"; private static final String XPATH_SECURITY = "//*[local-name()=\'security\']"; private static final String DDMS_NAMESPACE = "http://metadata.dod.mil/mdr/ns/DDMS/2.0/"; private static final String ICISM_NAMESPACE = "urn:us:gov:ic:ism:v2"; private static final String VALUE_ATTRIBUTE = "value"; private static final String QUALIFIER_ATTRIBUTE = "qualifier"; private Map<String, String> namespaceMap = new HashMap<>(); private static DatatypeFactory dtf = null; private CatalogFramework framework = null; private FilterBuilder filterBuilder = null; private GeneratorConfiguration generatorConfig = null; private Map<String, Serializable> requestProperties; public DescribeGeneratorImpl( CatalogFramework fw, FilterBuilder builder, GeneratorConfiguration config ) { this.framework = fw; this.filterBuilder = builder; this.generatorConfig = config; try { dtf = DatatypeFactory.newInstance(); } catch ( DatatypeConfigurationException e ) { LOGGER.error( e.getMessage(), e ); } this.requestProperties = new HashMap<>(); this.requestProperties.put( "ddf.security.subject", this.getSystemSubject() ); namespaceMap.put( "ddms", DDMS_NAMESPACE ); namespaceMap.put( "ICISM", ICISM_NAMESPACE ); } public Metacard generate( String sourceId ) { HashMap<String, String> resultProperties = new HashMap<>(); Describe describe = this.createDescribeRecord( sourceId, resultProperties ); Metacard describeMetacard = this.toMetacard( describe, sourceId, resultProperties ); return describeMetacard; } public Map<String, Metacard> generateAll() { HashMap<String, Metacard> describeXmls = new HashMap<>(); this.framework.getSourceIds().forEach( ( sourceId ) -> { LOGGER.debug( "Creating Describe record for sourceId {}", sourceId ); HashMap<String, String> resultProperties = new HashMap<>(); Describe describe = this.createDescribeRecord( sourceId, resultProperties ); Metacard describeMetacard = this.toMetacard( describe, sourceId, resultProperties ); describeXmls.put( sourceId, describeMetacard ); } ); return describeXmls; } private Metacard toMetacard( Describe describe, String sourceId, Map<String, String> resultProperties ) { MetacardImpl metacard = new MetacardImpl(); metacard.setMetadata( DescribeXMLParser.marshalDescribe( describe ) ); metacard.setId( "guide://999715/" + sourceId ); ResourceType resource = ((Collection) describe.getCollection().get( 0 )).getResource(); metacard.setDescription( resource.getDescription().getValue() ); metacard.setTitle( ((TitleType) resource.getTitle().get( 0 )).getValue() ); Date now = new Date(); metacard.setCreatedDate( now ); metacard.setModifiedDate( now ); metacard.setContentTypeName( "desribe" ); metacard.setLocation( (String) resultProperties.get( RESULT_WKT ) ); HashSet<String> tags = new HashSet<>(); tags.add( generatorConfig.getMetacardTag() ); metacard.setTags( tags ); return metacard; } protected Describe createDescribeRecord( String sourceId, Map<String, String> resultProperties ) { Describe describe = new Describe(); boolean hasMoreRecords = true; Date startDate = StringUtils.isBlank( generatorConfig.getStartDate() ) ? null : formatter.parseDateTime( generatorConfig.getStartDate() ).toDate(); Date endDate = new Date(); String queryKeywords = generatorConfig.getKeywords(); Collection collection = new Collection(); describe.getCollection().add( collection ); ContentCollectionType contentCollection = new ContentCollectionType(); collection.setContentCollection( contentCollection ); contentCollection.setOriginator( this.generatorConfig.getCollectionOriginator() ); contentCollection.setClassification( CVEnumISMClassificationAll.fromValue( this.generatorConfig.getClassification() ) ); contentCollection.setOwnerProducer( this.generatorConfig.getOwnerProducer() ); ResourceType resource = new ResourceType(); collection.setResource( resource ); CompoundResourceIdentifierType identifier = new CompoundResourceIdentifierType(); identifier.setQualifier( "GUIDE" ); identifier.setValue( "guide://999715/" + UUID.randomUUID() ); resource.getIdentifier().add( identifier ); TitleType title = new TitleType(); title.setValue( "Describe Record: " + sourceId ); resource.setTitle( Arrays.asList( new TitleType[] { title } ) ); DescriptionType descType = new DescriptionType(); descType.setValue( "Generated CDR Describe record for site \'" + sourceId + "\' - generated by " + this.generatorConfig.getCollectionOriginator() ); resource.setDescription( descType ); LOGGER.debug( "Generating Content Colleciton details for site {}", sourceId ); HashMap<String, TemporalCoverageHolder> timeMap = new HashMap<>(); Envelope geoEnvelope = new Envelope(); int maxRecordCount = this.generatorConfig.getMaxRecordsPerPoll(); ConcurrentHashMap<String, Long> keywordCounter = new ConcurrentHashMap<>(); ConcurrentHashMap<QualifierValueHolder, Long> sourceCounter = new ConcurrentHashMap<>(); ConcurrentHashMap<SecurityType, Long> secCounter = new ConcurrentHashMap<>(); ConcurrentHashMap<CompoundCategoryIdentifierType, Long> categoryCounter = new ConcurrentHashMap<>(); ConcurrentHashMap<CompoundTypeIdentifierType, Long> typeCounter = new ConcurrentHashMap<>(); ConcurrentHashMap<String, Long> mimeCounter = new ConcurrentHashMap<>(); ConcurrentHashMap<String, Long> contentTypeCounter = new ConcurrentHashMap<>(); try { while ( hasMoreRecords ) { QueryImpl query = new QueryImpl( this.getFilter( startDate, endDate, queryKeywords ), 1, maxRecordCount, this.getSortBy(), true, 300000L ); QueryRequestImpl queryRequest = new QueryRequestImpl( query, Arrays.asList( new String[] { sourceId } ) ); queryRequest.setProperties( this.requestProperties ); QueryResponse response = this.framework.query( queryRequest ); contentCollection.setUpdated( dtf.newXMLGregorianCalendar( new GregorianCalendar() ) ); MetricsType metrics = new MetricsType(); contentCollection.setMetrics( metrics ); if ( metrics.getCount() == 0 ) { metrics.setCount( response.getHits() ); } List<Result> results = response.getResults(); hasMoreRecords = (results.size() == maxRecordCount) && startDate != null; LOGGER.debug( "Adding details from query results for {} records from site {} in the Content Collection date range [{} - {}] and keywords[{}]", Integer.valueOf( results.size() ), sourceId, startDate, endDate, queryKeywords ); for ( Result result : results ) { try { Metacard metacard = result.getMetacard(); populateTemporalCoverage( timeMap, metacard ); populateGeoCoverage( geoEnvelope, metacard ); populateContentType( contentTypeCounter, metacard ); String metadata = metacard.getMetadata(); if ( StringUtils.isNotBlank( metadata ) ) { // Populate MIME Types XPathHelper xpathHelper = new XPathHelper( metadata ); populateMIMEType( sourceId, mimeCounter, result, metacard, xpathHelper ); // populate source populateSource( sourceId, sourceCounter, result, metacard, xpathHelper ); // Populate Keywords populateKeywords( sourceId, keywordCounter, metacard, xpathHelper ); // Populate Category populateCategory( sourceId, categoryCounter, metacard, xpathHelper ); // Populate Type populateType( sourceId, typeCounter, metacard, xpathHelper ); // Populate Security populateSecurityCoverage( sourceId, secCounter, metacard, xpathHelper ); } TemporalCoverageHolder tch = (TemporalCoverageHolder) timeMap.get( generatorConfig.getDateType() ); if ( tch != null ) { startDate = tch.getEndDate(); } } catch ( Exception e ) { LOGGER.error( "Error handling result {} ", result.getMetacard().getId(), e ); } } } if ( !geoEnvelope.isNull() ) { resultProperties.put( RESULT_WKT, (new WKTWriter()).write( (new GeometryFactory()).toGeometry( geoEnvelope ) ) ); } List<Entry<String, Long>> keywords = keywordCounter.entrySet().stream().sorted( Entry.comparingByValue( Comparator.reverseOrder() ) ) .limit( (long) this.generatorConfig.getMaxKeywords() ).collect( Collectors.toList() ); this.setKeywords( keywords, resource.getSubjectCoverage() ); this.setGeospatialCoverage( resource.getGeospatialCoverage(), geoEnvelope ); this.setTemporalCoverage( resource.getTemporalCoverage(), timeMap ); this.setRecordRate( contentCollection.getMetrics(), timeMap ); List<Entry<String, Long>> contentTypeDetails = contentTypeCounter.entrySet().stream().sorted( Entry.comparingByValue( Comparator.reverseOrder() ) ) .limit( (long) this.generatorConfig.getMaxContentTypes() ).collect( Collectors.toList() ); this.setContentTypes( resource, contentTypeDetails ); List<Entry<String, Long>> mimeTypeDetails = mimeCounter.entrySet().stream().sorted( Entry.comparingByValue( Comparator.reverseOrder() ) ) .limit( (long) this.generatorConfig.getMaxMimeTypes() ).collect( Collectors.toList() ); this.setMimeTypes( contentCollection, mimeTypeDetails ); List<Entry<CompoundCategoryIdentifierType, Long>> categoryDetails = categoryCounter.entrySet().stream() .sorted( Entry.comparingByValue( Comparator.reverseOrder() ) ).limit( (long) this.generatorConfig.getMaxCategories() ) .collect( Collectors.toList() ); this.setCategoryDetails( resource, categoryDetails ); List<Entry<CompoundTypeIdentifierType, Long>> typeDetails = typeCounter.entrySet().stream() .sorted( Entry.comparingByValue( Comparator.reverseOrder() ) ).limit( (long) this.generatorConfig.getMaxTypes() ) .collect( Collectors.toList() ); this.setTypeDetails( resource, typeDetails ); List<Entry<QualifierValueHolder, Long>> sources = sourceCounter.entrySet().stream().sorted( Entry.comparingByValue( Comparator.reverseOrder() ) ) .limit( (long) this.generatorConfig.getMaxSources() ).collect( Collectors.toList() ); this.setSourceTypes( resource, sources ); List<Entry<SecurityType, Long>> securityDetails = secCounter.entrySet().stream().sorted( Entry.comparingByValue( Comparator.reverseOrder() ) ) .limit( (long) this.generatorConfig.getMaxSecurity() ).collect( Collectors.toList() ); this.setSecurityDetails( contentCollection, securityDetails ); } catch ( Exception arg34 ) { LOGGER.warn( "Query failed against source {}", sourceId, arg34 ); } return describe; } private void populateTemporalCoverage( HashMap<String, TemporalCoverageHolder> timeMap, Metacard metacard ) { this.updateDate( Metacard.EFFECTIVE, metacard.getEffectiveDate(), timeMap ); this.updateDate( Metacard.CREATED, metacard.getCreatedDate(), timeMap ); this.updateDate( Metacard.MODIFIED, metacard.getModifiedDate(), timeMap ); this.updateDate( Metacard.EXPIRATION, metacard.getExpirationDate(), timeMap ); } private void populateGeoCoverage( Envelope geoEnvelope, Metacard metacard ) throws ParseException { String wkt = metacard.getLocation(); if ( StringUtils.isNotBlank( wkt ) ) { geoEnvelope.expandToInclude( (new WKTReader()).read( wkt ).getEnvelopeInternal() ); } } private void populateContentType( ConcurrentHashMap<String, Long> contentTypeCounter, Metacard metacard ) { String contentType = metacard.getContentTypeName(); if ( StringUtils.isNotBlank( contentType ) ) { contentTypeCounter.compute( contentType, ( k, v ) -> { return Long.valueOf( v == null ? 1L : v.longValue() + 1L ); } ); } } private void populateMIMEType( String sourceId, ConcurrentHashMap<String, Long> mimeCounter, Result result, Metacard metacard, XPathHelper xpathHelper ) { generatorConfig.getMimeTypesXPaths().forEach( xpath -> { try { String mimeType = xpathHelper.evaluate( xpath ); LOGGER.trace( "MIME Type info from site {} record {}: {}", sourceId, metacard.getId(), mimeType ); if ( StringUtils.isNotBlank( mimeType ) ) { mimeCounter.compute( mimeType, ( k, v ) -> { return Long.valueOf( v == null ? 1L : v.longValue() + 1L ); } ); } } catch ( Exception e ) { LOGGER.error( "Error extracting MIMETypes {} ", result.getMetacard().getId(), e ); } } ); } private void populateSource( String sourceId, ConcurrentHashMap<QualifierValueHolder, Long> sourceCounter, Result result, Metacard metacard, XPathHelper xpathHelper ) { generatorConfig.getSourceXPaths().forEach( xpath -> { try { NodeList sourceNodes = (NodeList) xpathHelper.evaluate( xpath, XPathConstants.NODESET ); LOGGER.trace( "Source Element info from site {} record {}: {}", sourceId, metacard.getId(), sourceNodes ); if ( sourceNodes != null ) { for ( int i = 0; i < sourceNodes.getLength(); i++ ) { String qual = getAttributeValue( sourceNodes.item( i ), DDMS_NAMESPACE, QUALIFIER_ATTRIBUTE ); String val = getAttributeValue( sourceNodes.item( i ), DDMS_NAMESPACE, VALUE_ATTRIBUTE ); sourceCounter.compute( new QualifierValueHolder( qual, val ), ( k, v ) -> { return Long.valueOf( v == null ? 1L : v.longValue() + 1L ); } ); } } } catch ( Exception e ) { LOGGER.error( "Error extracting Sources {} ", result.getMetacard().getId(), e ); } } ); } private void populateKeywords( String sourceId, ConcurrentHashMap<String, Long> keywordCounter, Metacard metacard, XPathHelper xpathHelper ) throws XPathExpressionException { NodeList keywordNodes = (NodeList) xpathHelper.evaluate( XPATH_KEYWORD, XPathConstants.NODESET ); LOGGER.trace( "Keyword info from site {} record {}: {}", sourceId, metacard.getId(), keywordNodes ); if ( keywordNodes != null ) { for ( int i = 0; i < keywordNodes.getLength(); i++ ) { String word = getAttributeValue( keywordNodes.item( i ), DDMS_NAMESPACE, VALUE_ATTRIBUTE ); if ( StringUtils.isNotBlank( word ) ) { keywordCounter.compute( word, ( k, v ) -> { return Long.valueOf( v == null ? 1L : v.longValue() + 1L ); } ); } } } } private void populateCategory( String sourceId, ConcurrentHashMap<CompoundCategoryIdentifierType, Long> categoryCounter, Metacard metacard, XPathHelper xpathHelper ) throws XPathExpressionException { NodeList categoryNode = (NodeList) xpathHelper.evaluate( XPATH_CATEGORY, XPathConstants.NODESET ); LOGGER.trace( "Category info from site {} record {}: {}", sourceId, metacard.getId(), categoryNode ); if ( categoryNode != null ) { for ( int i = 0; i < categoryNode.getLength(); i++ ) { CompoundCategoryIdentifierType catType = new CompoundCategoryIdentifierType(); catType.setCode( getAttributeValue( categoryNode.item( i ), DDMS_NAMESPACE, "code" ) ); catType.setQualifier( getAttributeValue( categoryNode.item( i ), DDMS_NAMESPACE, "qualifier" ) ); catType.setLabel( getAttributeValue( categoryNode.item( i ), DDMS_NAMESPACE, "label" ) ); categoryCounter.compute( catType, ( k, v ) -> { return Long.valueOf( v == null ? 1L : v.longValue() + 1L ); } ); } } } private void populateType( String sourceId, ConcurrentHashMap<CompoundTypeIdentifierType, Long> typeCounter, Metacard metacard, XPathHelper xpathHelper ) throws XPathExpressionException { NodeList typeNode = (NodeList) xpathHelper.evaluate( XPATH_TYPE, XPathConstants.NODESET, new NamespaceMapImpl( namespaceMap ) ); LOGGER.trace( "Type info from site {} record {}: {}", sourceId, metacard.getId(), typeNode ); if ( typeNode != null ) { for ( int i = 0; i < typeNode.getLength(); i++ ) { CompoundTypeIdentifierType type = new CompoundTypeIdentifierType(); type.setQualifier( getAttributeValue( typeNode.item( i ), DDMS_NAMESPACE, QUALIFIER_ATTRIBUTE ) ); type.setValue( getAttributeValue( typeNode.item( i ), DDMS_NAMESPACE, VALUE_ATTRIBUTE ) ); typeCounter.compute( type, ( k, v ) -> { return Long.valueOf( v == null ? 1L : v.longValue() + 1L ); } ); } } } private void populateSecurityCoverage( String sourceId, ConcurrentHashMap<SecurityType, Long> secCounter, Metacard metacard, XPathHelper xpathHelper ) throws XPathExpressionException { Node securityNode = (Node) xpathHelper.evaluate( XPATH_SECURITY, XPathConstants.NODE ); LOGGER.trace( "Security info from site {} record {}: {}", sourceId, metacard.getId(), securityNode ); if ( securityNode != null ) { SecurityType secType = new SecurityType(); String value = getAttributeValue( securityNode, ICISM_NAMESPACE, "classification" ); if ( value != null ) { secType.setClassification( CVEnumISMClassificationAll.fromValue( value ) ); } value = getAttributeValue( securityNode, ICISM_NAMESPACE, "ownerProducer" ); if ( value != null ) { secType.setOwnerProducer( Arrays.asList( value.split( " " ) ) ); } value = getAttributeValue( securityNode, ICISM_NAMESPACE, "releasableTo" ); if ( value != null ) { secType.setReleasableTo( Arrays.asList( value.split( " " ) ) ); } value = getAttributeValue( securityNode, ICISM_NAMESPACE, "disseminationControls" ); if ( value != null ) { secType.setDisseminationControls( Arrays.asList( value.split( " " ) ) ); } secCounter.compute( secType, ( k, v ) -> { return Long.valueOf( v == null ? 1L : v.longValue() + 1L ); } ); } } private String getAttributeValue( Node item, String namespace, String attribute ) { String attributeValue = null; Node node = item.getAttributes().getNamedItemNS( namespace, attribute ); if ( node != null ) { attributeValue = node.getTextContent(); } return attributeValue; } private void setTypeDetails( ResourceType resource, List<Entry<CompoundTypeIdentifierType, Long>> typeDetails ) { List<CompoundTypeIdentifierType> typeList = resource.getType(); typeDetails.forEach( entry -> { CompoundTypeIdentifierType type = entry.getKey(); type.getOtherAttributes().put( new QName( DESCRIBE_EXT_NAMESPACE, COUNT_ATTRIBUTE ), ((Long) entry.getValue()).toString() ); typeList.add( type ); } ); } private void setCategoryDetails( ResourceType resource, List<Entry<CompoundCategoryIdentifierType, Long>> categoryDetails ) { List<SubjectType> subjectList = resource.getSubjectCoverage(); if ( subjectList.isEmpty() ) { subjectList.add( new SubjectType() ); } SubjectType subject = subjectList.get( 0 ); List<Serializable> categoryList = subject.getCategoryOrKeywordOrProductionMetric(); categoryDetails.forEach( ( entry ) -> { CompoundCategoryIdentifierType cat = entry.getKey(); cat.getOtherAttributes().put( new QName( DESCRIBE_EXT_NAMESPACE, COUNT_ATTRIBUTE ), ((Long) entry.getValue()).toString() ); categoryList.add( cat ); } ); } private void setSecurityDetails( ContentCollectionType collection, List<Entry<SecurityType, Long>> securityDetails ) { SecurityCoverage secCoverage = new SecurityCoverage(); collection.setSecurityCoverage( secCoverage ); List<SecurityType> secList = secCoverage.getSecurityMarkings(); securityDetails.forEach( entry -> { SecurityType secType = entry.getKey(); secType.setCount( entry.getValue() ); secList.add( secType ); } ); } private void setSourceTypes( ResourceType resource, List<Entry<QualifierValueHolder, Long>> sources ) { sources.forEach( entry -> { CompoundSourceIdentifierType sourceType = new CompoundSourceIdentifierType(); QualifierValueHolder qual = entry.getKey(); sourceType.setQualifier( qual.getQualifier() ); sourceType.setValue( qual.getValue() ); sourceType.getOtherAttributes().put( new QName( DESCRIBE_EXT_NAMESPACE, COUNT_ATTRIBUTE ), ((Long) entry.getValue()).toString() ); resource.getSource().add( sourceType ); } ); } private void setKeywords( List<Entry<String, Long>> topResults, List<SubjectType> subjectCoverage ) { if ( subjectCoverage.isEmpty() ) { subjectCoverage.add( new SubjectType() ); } SubjectType subject = subjectCoverage.get( 0 ); List<Serializable> keywordList = subject.getCategoryOrKeywordOrProductionMetric(); topResults.forEach( ( entry ) -> { CompoundKeywordIdentifierType keyword = new CompoundKeywordIdentifierType(); keyword.setValue( (String) entry.getKey() ); keyword.getOtherAttributes().put( new QName( DESCRIBE_EXT_NAMESPACE, COUNT_ATTRIBUTE ), ((Long) entry.getValue()).toString() ); keywordList.add( keyword ); } ); } private void setGeospatialCoverage( List<PlaceType> geospatialCoverage, Envelope geoEnvelope ) { if ( !geoEnvelope.isNull() ) { PlaceType placeType = new PlaceType(); geospatialCoverage.add( placeType ); BoundingGeometryType boundingGeo = new BoundingGeometryType(); placeType.setGeographicIdentifierOrBoundingGeometryOrPostalAddress( Arrays.asList( new Serializable[] { boundingGeo } ) ); EnvelopeType envelope = new EnvelopeType(); boundingGeo.setPolygonOrPointOrEnvelope( Arrays.asList( new Serializable[] { envelope } ) ); envelope.setId( UUID.randomUUID().toString() ); LowerCorner lowerCorner = new LowerCorner(); DirectPositionType lcPosition = new DirectPositionType(); lcPosition.setSrsName( "http://metadata.dod.mil/mdr/ns/GSIP/crs/WGS84E_2D" ); lcPosition.setValue( Arrays.asList( new Double[] { Double.valueOf( geoEnvelope.getMinY() ), Double.valueOf( geoEnvelope.getMinX() ) } ) ); lowerCorner.setPos( lcPosition ); UpperCorner upperCorner = new UpperCorner(); DirectPositionType ucPosition = new DirectPositionType(); ucPosition.setSrsName( "http://metadata.dod.mil/mdr/ns/GSIP/crs/WGS84E_2D" ); ucPosition.setValue( Arrays.asList( new Double[] { Double.valueOf( geoEnvelope.getMaxY() ), Double.valueOf( geoEnvelope.getMaxX() ) } ) ); upperCorner.setPos( ucPosition ); envelope.setUpperCorner( upperCorner ); envelope.setLowerCorner( lowerCorner ); } } protected void setContentTypes( ResourceType resource, List<Entry<String, Long>> contentTypeDetails ) { if ( !contentTypeDetails.isEmpty() ) { contentTypeDetails.forEach( entry -> { CompoundTypeIdentifierType type = new CompoundTypeIdentifierType(); type.setQualifier( "dib-content-type" ); type.setValue( entry.getKey() ); type.getOtherAttributes().put( new QName( DESCRIBE_EXT_NAMESPACE, COUNT_ATTRIBUTE ), ((Long) entry.getValue()).toString() ); resource.getType().add( type ); } ); } } protected void setMimeTypes( ContentCollectionType collection, List<Entry<String, Long>> mimeTypeDetails ) { if ( !mimeTypeDetails.isEmpty() ) { MimeTypes types = new MimeTypes(); mimeTypeDetails.forEach( entry -> { MIMETypeType mimeType = new MIMETypeType(); mimeType.setValue( entry.getKey() ); mimeType.setCount( entry.getValue() ); types.getMimeType().add( mimeType ); } ); collection.setMimeTypes( types ); } } protected void setTemporalCoverage( List<TimePeriodType> timeList, Map<String, TemporalCoverageHolder> timeMap ) { timeMap.forEach( ( k, v ) -> { TimePeriodType tp = new TimePeriodType(); TemporalCoverageHolder tcHolder = (TemporalCoverageHolder) v; tp.setName( tcHolder.getLabel() ); tp.setStart( Arrays.asList( new String[] { df.format( tcHolder.getStartDate() ) } ) ); tp.setEnd( Arrays.asList( new String[] { df.format( tcHolder.getEndDate() ) } ) ); timeList.add( tp ); } ); } protected void setRecordRate( MetricsType metrics, Map<String, TemporalCoverageHolder> timeMap ) { TemporalCoverageHolder tc = timeMap.containsKey( "modified" ) ? (TemporalCoverageHolder) timeMap.get( "modified" ) : (timeMap.containsKey( "effective" ) ? (TemporalCoverageHolder) timeMap.get( "effective" ) : (TemporalCoverageHolder) timeMap.get( "created" )); try { if ( tc != null ) { Date startDate = tc.getStartDate(); if ( startDate != null ) { long totalHits = metrics.getCount(); LocalDateTime start = LocalDateTime.ofInstant( startDate.toInstant(), ZoneId.systemDefault() ); Duration duration = Duration.between( start, LocalDateTime.now() ); RecordRateType rate = new RecordRateType(); metrics.setRecordRate( rate ); long dur = totalHits / duration.toHours(); if ( dur < 15L ) { dur = totalHits / duration.toDays(); if ( dur < 4L ) { dur = totalHits * 30L / duration.toDays(); if ( dur < 10L ) { dur = totalHits * 365L / duration.toDays(); rate.setFrequency( "Yearly" ); } else { rate.setFrequency( "Monthly" ); } } else { rate.setFrequency( "Daily" ); } } else if ( totalHits > 1000L ) { dur = duration.toMinutes(); if ( totalHits > 1000L ) { dur = duration.toMillis() / 1000L; rate.setFrequency( "Second" ); } else { rate.setFrequency( "Minute" ); } } else { rate.setFrequency( "Hourly" ); } rate.setValue( (int) dur ); } } } catch ( Exception e ) { LOGGER.warn( "Could not set record rate: {}", e.getMessage(), e ); } } protected Filter getFilter( Date startDate, Date endDate, String keywords ) { LOGGER.debug( "Creating query for date type {} and date range {} through {} and keywords {}", generatorConfig.getDateType(), startDate, endDate, keywords ); Filter filter = null; if ( startDate != null ) { filter = this.filterBuilder.attribute( generatorConfig.getDateType() ).during().dates( startDate, endDate ); } if ( StringUtils.isNotBlank( keywords ) ) { filter = filter == null ? filterBuilder.attribute( Metacard.ANY_TEXT ).like().text( keywords ) : filterBuilder.allOf( filter, filterBuilder.attribute( Metacard.ANY_TEXT ).like().text( keywords ) ); } return filter; } protected SortBy getSortBy() { SortByImpl sortBy = new SortByImpl( generatorConfig.getSortDateType(), SortOrder.ASCENDING ); return sortBy; } protected void updateDate( String label, Date date, Map<String, TemporalCoverageHolder> map ) { if ( date != null ) { TemporalCoverageHolder tCoverage = (TemporalCoverageHolder) map.get( label ); if ( tCoverage == null ) { tCoverage = new TemporalCoverageHolder(); tCoverage.setLabel( label ); map.put( label, tCoverage ); } tCoverage.updateDate( date ); } } protected Subject getSystemSubject() { return (Subject) Security.runAsAdmin( () -> { return Security.getInstance().getSystemSubject(); } ); } private class QualifierValueHolder { private String qualifier = null; private String value = null; QualifierValueHolder( String q, String v ) { this.qualifier = q; this.value = v; } public String getQualifier() { return qualifier; } public String getValue() { return value; } @Override public int hashCode() { return Objects.hash( this.qualifier, this.value ); } @Override public boolean equals( Object obj ) { if ( obj instanceof QualifierValueHolder ) { QualifierValueHolder qual2 = (QualifierValueHolder) obj; return StringUtils.equals( qual2.getQualifier(), this.qualifier ) && StringUtils.equals( qual2.getValue(), this.value ); } else { return false; } } } }