SolrItem.java

/*
 * Copyright (c) 2002-2021, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.plugins.search.solr.indexer;

import fr.paris.lutece.plugins.leaflet.business.GeolocItem;
import fr.paris.lutece.portal.service.search.SearchItem;
import fr.paris.lutece.portal.service.util.AppLogService;

import org.apache.solr.client.solrj.beans.Field;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrInputDocument;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 *
 * An item that is easy to add to a SolrServer.
 *
 */
public class SolrItem
{
    public static final String FIELD_CONTENT = "content";
    public static final String FIELD_SITE = "site";
    public static final String FIELD_XML_CONTENT = "xml_content";
    public static final String FIELD_FILE_CONTENT = "file_content";
    public static final String FIELD_CATEGORIE = "categorie";
    public static final String FIELD_HIERATCHY_DATE = "hiedate";
    public static final String FIELD_METADATA = "metadata";
    public static final String FIELD_ID_RESOURCE = "id_resource";
    public static final String DYNAMIC_STRING_FIELD_SUFFIX = "_string";
    public static final String DYNAMIC_TEXT_FIELD_SUFFIX = "_text";
    public static final String DYNAMIC_URL_FIELD_SUFFIX = "_url";
    public static final String DYNAMIC_DATE_FIELD_SUFFIX = "_date";
    public static final String DYNAMIC_LIST_DATE_FIELD_SUFFIX = "_list_date";
    public static final String DYNAMIC_LONG_FIELD_SUFFIX = "_long";
    public static final String DYNAMIC_LIST_FIELD_SUFFIX = "_list";
    public static final String DYNAMIC_GEOLOC_FIELD_SUFFIX = "_geoloc";
    public static final String DYNAMIC_GEOJSON_FIELD_SUFFIX = "_geojson";
    public static final String DYNAMIC_FLOAT_FIELD_SUFFIX = "_float";
    public static final String DYNAMIC_GEOJSON_ADDRESS_FIELD_SUFFIX = "_address";
    private static final String GEOLOC_JSON_PATH_GEOMETRY = "geometry";
    private static final String GEOLOC_JSON_PATH_GEOMETRY_COORDINATES = "coordinates";
    public static final String FIELD_CHILD_DOCUMENTS = "_childDocuments";

    @Field( SearchItem.FIELD_URL )
    private String _strUrl;
    @Field( SearchItem.FIELD_DATE )
    private Date _lDate;
    @Field( SearchItem.FIELD_TITLE )
    private String _strTitle;
    @Field( SearchItem.FIELD_UID )
    private String _strUid;
    @Field( FIELD_CONTENT )
    private String _strContent;
    @Field( FIELD_SITE )
    private String _strSite;
    @Field( SearchItem.FIELD_SUMMARY )
    private String _strSummary;
    @Field( SearchItem.FIELD_TYPE )
    private String _strType;
    @Field( SearchItem.FIELD_ROLE )
    private String _strRole;
    @Field( FIELD_XML_CONTENT )
    private String _strXmlContent;
    @Field( FIELD_CATEGORIE )
    private List<String> _strCategorie;
    @Field( FIELD_HIERATCHY_DATE )
    private String _strHieDate;
    @Field( FIELD_METADATA )
    private String _strMetadata;
    @Field( SearchItem.FIELD_DOCUMENT_PORTLET_ID )
    private String _strDocPortletId;
    @Field( FIELD_FILE_CONTENT )
    private String _strFileContent;
    @Field( FIELD_ID_RESOURCE )
    private String _strIdresource;
    // DynamicField
    @Field( "*" + DYNAMIC_LIST_FIELD_SUFFIX )
    private Map<String, List<String>> _dfListBox;
    @Field( "*" + DYNAMIC_TEXT_FIELD_SUFFIX )
    private Map<String, String> _dfText;
    @Field( "*" + DYNAMIC_STRING_FIELD_SUFFIX )
    private Map<String, String> _dfString;
    @Field( "*" + DYNAMIC_DATE_FIELD_SUFFIX )
    private Map<String, Date> _dfDate;
    @Field( "*" + DYNAMIC_LONG_FIELD_SUFFIX )
    private Map<String, Long> _dfNumericText;
    @Field( "*" + DYNAMIC_GEOLOC_FIELD_SUFFIX )
    private Map<String, String> _dfGeoloc;
    @Field( "*" + DYNAMIC_GEOJSON_FIELD_SUFFIX )
    private Map<String, String> _dfGeojson;
    @Field( "*" + DYNAMIC_FLOAT_FIELD_SUFFIX )
    private Map<String, Float> _dfFloat;
    @Field( "*" + DYNAMIC_LIST_DATE_FIELD_SUFFIX )
    private Map<String, List<Date>> _dfListDate;
    @Field( "*" + FIELD_CHILD_DOCUMENTS )
    private Map<String, List<SolrDocument>> _searchChilDocuments;
    private Map<String, Collection<SolrItem>> _childDocuments;

    /**
     * Returns list of all dynamic fields
     * 
     * @return the list of all dynamic fields
     */
    public Map<String, Object> getDynamicFields( )
    {
        Map<String, Object> mapDynamicFields = new HashMap<>( );

        if ( _dfString != null )
        {
            mapDynamicFields.putAll( _dfString );
        }

        if ( _dfDate != null )
        {
            mapDynamicFields.putAll( _dfDate );
        }

        if ( _dfListBox != null )
        {
            mapDynamicFields.putAll( _dfListBox );
        }

        if ( _dfNumericText != null )
        {
            mapDynamicFields.putAll( _dfNumericText );
        }

        if ( _dfText != null )
        {
            mapDynamicFields.putAll( _dfText );
        }

        if ( _dfGeoloc != null )
        {
            mapDynamicFields.putAll( _dfGeoloc );
        }

        if ( _dfGeojson != null )
        {
            mapDynamicFields.putAll( _dfGeojson );
        }

        if ( _dfListDate != null )
        {
            mapDynamicFields.putAll( _dfListDate );
        }

        if ( _dfFloat != null )
        {
            mapDynamicFields.putAll( _dfFloat );
        }
        return mapDynamicFields;
    }

    /**
     * Add a dynamic field
     * 
     * @param strName
     *            the name of the field
     * @param dValue
     *            the value of the field
     */
    public void addDynamicField( String strName, Date dValue )
    {
        if ( _dfDate == null )
        {
            _dfDate = new HashMap<>( );
        }

        _dfDate.put( strName + DYNAMIC_DATE_FIELD_SUFFIX, dValue );
    }

    /**
     * Add a dynamic field
     * 
     * @param strName
     *            the name of the field
     * @param dValue
     *            the value of the field
     */
    public void addDynamicField( String strName, Long lValue )
    {
        if ( _dfNumericText == null )
        {
            _dfNumericText = new HashMap<>( );
        }

        _dfNumericText.put( strName + DYNAMIC_LONG_FIELD_SUFFIX, lValue );
    }

    /**
     * Add a dynamic field
     * 
     * @param strName
     *            the name of the field
     * @param dValue
     *            the value of the field
     */
    public void addDynamicField( String strName, String strValue )
    {
        if ( _dfText == null )
        {
            _dfText = new HashMap<>( );
        }

        _dfText.put( strName + DYNAMIC_TEXT_FIELD_SUFFIX, strValue );
    }

    /**
     * Add a dynamic field
     * 
     * @param strName
     *            the name of the field
     * @param dValue
     *            the value of the field
     */
    public void addDynamicField( String strName, List<String> strValue )
    {
        if ( _dfListBox == null )
        {
            _dfListBox = new HashMap<>( );
        }

        _dfListBox.put( strName + DYNAMIC_LIST_FIELD_SUFFIX, strValue );
    }

    /**
     * Add a dynamic field
     * 
     * @param strName
     *            the name of the field
     * @param dateValue
     *            the value of the field
     */
    public void addDynamicFieldListDate( String strName, List<Date> dateValue )
    {
        if ( _dfListDate == null )
        {
            _dfListDate = new HashMap<>( );
        }

        _dfListDate.put( strName + DYNAMIC_LIST_DATE_FIELD_SUFFIX, dateValue );
    }

    /**
     * Add a dynamic field
     * 
     * @param strName
     *            the name of the field
     * @param dValue
     *            the value of the field
     */
    public void addDynamicFieldNotAnalysed( String strName, String strValue )
    {
        if ( _dfString == null )
        {
            _dfString = new HashMap<>( );
        }

        _dfString.put( strName + DYNAMIC_STRING_FIELD_SUFFIX, strValue );
    }

    /**
     * Add a dynamic field
     * 
     * @param strName
     *            the name of the field
     * @param geolocItem
     *            a GeolocItem
     * @param codeDocumentType
     *            the codeDocumentType
     */
    public void addDynamicFieldGeoloc( String strName, GeolocItem geolocItem, String codeDocumentType )
    {
        if ( geolocItem.getIcon( ) == null )
        {
            geolocItem.setIcon( codeDocumentType + "-" + strName );
        }

        List<Double> coordinates = geolocItem.getLonLat( );
        if ( coordinates != null || geolocItem.getTypegeometry().equals( GeolocItem.VALUE_GEOMETRY_TYPE_POLYGON) || geolocItem.getTypegeometry().equals( GeolocItem.VALUE_GEOMETRY_TYPE_POLYLINE) )
        {
            if ( _dfGeoloc == null )
            {
                _dfGeoloc = new HashMap<>( );
            }

            if ( _dfGeojson == null )
            {
                _dfGeojson = new HashMap<>( );
            }

            if ( geolocItem.getTypegeometry( ).equals( GeolocItem.VALUE_GEOMETRY_TYPE ) )
            {
	            String strCoordinates = String.format( Locale.ENGLISH, "%.6f,%.6f", coordinates.get( 1 ), coordinates.get( 0 ) );
	            _dfGeoloc.put( strName + DYNAMIC_GEOLOC_FIELD_SUFFIX, strCoordinates );
            }
            
            _dfGeojson.put( strName + DYNAMIC_GEOJSON_FIELD_SUFFIX, geolocItem.toJSON( ) );

            if ( geolocItem.getAddress( ) != null )
            {
                addDynamicField( strName + DYNAMIC_GEOJSON_ADDRESS_FIELD_SUFFIX, geolocItem.getAddress( ) );
            }
        }
    }

    /**
     * Add a dynamic field
     *
     * @param strName
     *            the name of the field
     * @param value
     *            the value of the field
     */
    public void addDynamicField( String strName, Float value )
    {
        if ( _dfFloat == null )
        {
            _dfFloat = new HashMap<>( );
        }

        _dfFloat.put( strName + DYNAMIC_FLOAT_FIELD_SUFFIX, value );
    }

    /**
     * Add a dynamic field
     * 
     * @param strName
     *            the name of the field
     * @param strAdress
     *            the adress of the field
     * @param dLongitude
     *            the longitude of the field
     * @param dLatitude
     *            the latitude of the field
     * @param codeDocumentType
     *            the codeDocumentType
     */
    public void addDynamicFieldGeoloc( String strName, String strAdress, double dLongitude, double dLatitude, String codeDocumentType )
    {
        GeolocItem geolocItem = new GeolocItem( );
        HashMap<String, Object> properties = new HashMap<>( );
        properties.put( GeolocItem.PATH_PROPERTIES_ADDRESS, strAdress );

        HashMap<String, Object> geometry = new HashMap<>( );
        geometry.put( GeolocItem.PATH_GEOMETRY_COORDINATES, Arrays.asList( dLongitude, dLatitude ) );
        geometry.put( GeolocItem.PATH_GEOMETRY_TYPE, GeolocItem.VALUE_GEOMETRY_TYPE );
        
        geolocItem.setGeometry( geometry );
        geolocItem.setProperties( properties );
        addDynamicFieldGeoloc( strName, geolocItem, codeDocumentType );
    }

    /**
     * Add a dynamic field
     * 
     * @param strName
     *            the name of the field
     * @param strValue
     *            the value of the field which should be a geojson string
     * @param codeDocumentType
     *            the codeDocumentType
     */
    public void addDynamicFieldGeoloc( String strName, String strValue, String codeDocumentType )
    {
        JsonNode object;

        try
        {
            object = new ObjectMapper( ).readTree( strValue );
        }
        catch( IOException e )
        {
            AppLogService.error( "SolrItem: exception during GEOJSON parsing : " + strValue + " : " + e );

            return;
        }

        JsonNode objCoordinates = object.path( GEOLOC_JSON_PATH_GEOMETRY ).path( GEOLOC_JSON_PATH_GEOMETRY_COORDINATES );

        if ( objCoordinates.isMissingNode( ) )
        {
            AppLogService.error( "SolrItem: missing coordinates : " + strValue );
        }
        else
        {
            if ( !objCoordinates.isArray( ) )
            {
                AppLogService.error( "SolrItem: coordinates not an array : " + strValue );
            }
            else
            {
                double [ ] parsedCoordinates = new double [ 2];
                Iterator<JsonNode> it = objCoordinates.elements( );

                for ( int i = 0; i < parsedCoordinates.length; i++ )
                {
                    if ( !it.hasNext( ) )
                    {
                        AppLogService.error( "SolrItem: coordinates array too short : " + strValue + " at element " + Integer.toString( i ) );
                    }
                    else
                    {
                        JsonNode node = it.next( );
                        if ( !node.isNumber( ) )
                        {
                            AppLogService.error( "SolrItem: coordinate not a number : " + strValue + " at element " + Integer.toString( i ) );
                        }
                        else
                        {
                            parsedCoordinates [i] = node.asDouble( );
                        }
                    }
                }

            }
        }

        GeolocItem geolocItem = null;

        try
        {
            geolocItem = GeolocItem.fromJSON( strValue );
        }
        catch( IOException e )
        {
            AppLogService.error( "SolrItem: Error parsing JSON: " + strValue + "exception: " + e );
        }
        if ( geolocItem != null )
        {
            addDynamicFieldGeoloc( strName, geolocItem, codeDocumentType );
        }
    }

    /**
     * Gets the url
     * 
     * @return the url
     */
    public String getUrl( )
    {
        return _strUrl;
    }

    /**
     * Sets the url
     * 
     * @param strUrl
     *            the url
     */
    public void setUrl( String strUrl )
    {
        _strUrl = strUrl;
    }

    /**
     * Return the date
     * 
     * @return the date
     */
    public Date getDate( )
    {
        return _lDate;
    }

    /**
     * Sets the date
     * 
     * @param lDate
     *            the date
     */
    public void setDate( Date lDate )
    {
        _lDate = lDate;
    }

    /**
     * Gets the title
     * 
     * @return the title
     */
    public String getTitle( )
    {
        return _strTitle;
    }

    /**
     * Sets the title
     * 
     * @param strTitle
     *            the title
     */
    public void setTitle( String strTitle )
    {
        _strTitle = strTitle;
    }

    /**
     * Gets the id
     * 
     * @return the id
     */
    public String getUid( )
    {
        return _strUid;
    }

    /**
     * Sets the id
     * 
     * @param strUid
     *            the id
     */
    public void setUid( String strUid )
    {
        _strUid = strUid;
    }

    /**
     * Gets the content
     * 
     * @return the content
     */
    public String getContent( )
    {
        return _strContent;
    }

    /**
     * Sets the content
     * 
     * @param strContent
     *            the content
     */
    public void setContent( String strContent )
    {
        _strContent = strContent;
    }

    /**
     * Gets the site
     * 
     * @return the site name
     */
    public String getSite( )
    {
        return _strSite;
    }

    /**
     * Sets the site name
     * 
     * @param strSite
     *            the site name
     */
    public void setSite( String strSite )
    {
        _strSite = strSite;
    }

    /**
     * Gets the summary
     * 
     * @return the summary
     */
    public String getSummary( )
    {
        return _strSummary;
    }

    /**
     * Sets the summary
     * 
     * @param strSummary
     *            the summary
     */
    public void setSummary( String strSummary )
    {
        _strSummary = strSummary;
    }

    /**
     * Gets the type
     * 
     * @return the type
     */
    public String getType( )
    {
        return _strType;
    }

    /**
     * Sets the type
     * 
     * @param strType
     *            the type
     */
    public void setType( String strType )
    {
        _strType = strType;
    }

    /**
     * Gets the role
     * 
     * @return the role
     */
    public String getRole( )
    {
        return _strRole;
    }

    /**
     * Sets the role
     * 
     * @param strRole
     *            the role
     */
    public void setRole( String strRole )
    {
        _strRole = strRole;
    }

    public String getXmlContent( )
    {
        return _strXmlContent;
    }

    public void setXmlContent( String strXmlContent )
    {
        _strXmlContent = strXmlContent;
    }

    public List<String> getCategorie( )
    {
        return _strCategorie;
    }

    public void setCategorie( List<String> strCategorie )
    {
        _strCategorie = strCategorie;
    }

    public String getHieDate( )
    {
        return _strHieDate;
    }

    public void setHieDate( String strHieDate )
    {
        _strHieDate = strHieDate;
    }

    /**
     * Gets the metadata
     * 
     * @return the metadata
     */
    public String getMetadata( )
    {
        return _strMetadata;
    }

    /**
     * Sets the metadata
     * 
     * @param strMetadata
     *            the metadata
     */
    public void setMetadata( String strMetadata )
    {
        _strMetadata = strMetadata;
    }

    /**
     * Gets the portlet document id
     * 
     * @return the portlet document id
     */
    public String getDocPortletId( )
    {
        return _strDocPortletId;
    }

    /**
     * Sets the portlet document id
     * 
     * @param strDocPortletId
     *            the portlet document id
     */
    public void setDocPortletId( String strDocPortletId )
    {
        _strDocPortletId = strDocPortletId;
    }

    /**
     * @return the strFileContent
     */
    public String getFileContent( )
    {
        return _strFileContent;
    }

    /**
     * @param strFileContent
     *            the strFileContent to set
     */
    public void setFileContent( String strFileContent )
    {
        _strFileContent = strFileContent;
    }
    /**
     * @return the strIdresource
     */
    public String getIdResource( )
    {
        return _strIdresource;
    }

    /**
     * @param strIdresource
     *            the resource id to set
     */
    public void setIdResource( String strIdresource )
    {
    	_strIdresource = strIdresource;
    }
    public void addChildDocument(String strName, SolrItem child) {
    	
   	   if (_childDocuments == null) {
   		   
    	   _childDocuments = new HashMap<>( );
   	   }
   	   Collection<SolrItem> items= _childDocuments.get(strName + FIELD_CHILD_DOCUMENTS );
   	   if(items == null) {
   		   
   		   Collection<SolrItem> list =new ArrayList<>();
   		   list.add(child);
   		   _childDocuments.put( strName + FIELD_CHILD_DOCUMENTS , list);
   	   }
   	   else {
	   		items.add(child);
	   		_childDocuments.put(strName + FIELD_CHILD_DOCUMENTS, items);
   	   }
   }

   public void addChildDocuments(String strName, Collection<SolrItem> children) {
	   
       for (SolrItem child : children) {
   	      addChildDocument(strName, child);
   	    }
   }
   /** Returns the list of child documents, or null if none. */
   public Map<String, Collection<SolrItem>> getChildDocuments( ) {
     return _childDocuments;
   }
   /** Returns the list of child solr documents, or null if none. */
   public Map<String, List<SolrDocument>> getSearchChilDocuments( ) {
     return _searchChilDocuments;
   }
   
   /** Returns the list of child documents, or null if none. */
   public Map<String, Collection<SolrInputDocument>> getChilSolrInputDocument( ) 
   {
	   Map<String, Collection<SolrInputDocument>> childSolrInputDocument = new HashMap<>( );
	   if(hasChildDocuments( )) {
		   
		   _childDocuments.forEach( ( key, value) ->{
			   Collection<SolrInputDocument> coll= new ArrayList<>( );
			   value.forEach( child -> 
					   coll.add( SolrIndexerService.solrItem2SolrInputDocument(child))
				);
			   childSolrInputDocument.put(key, coll);
		   });
		   
	   }
     return childSolrInputDocument;
   }

   public boolean hasChildDocuments() {
     boolean isEmpty = (_childDocuments == null || _childDocuments.isEmpty());
     return !isEmpty;
   }

}