package eu.dnetlib.enabling.aas.retrievers;

import java.io.StringReader;
import java.io.StringWriter;
import java.net.URI;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.NamespaceContext;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import pl.edu.icm.yadda.desklight.model.reference.Serializer;
import an.xacml.CachedDataObjectHolder;
import an.xacml.Constants;
import an.xacml.ExtendedRequest;
import an.xacml.IndeterminateException;
import an.xacml.context.Attribute;
import an.xacml.context.Request;
import an.xacml.context.Subject;
import an.xacml.converter.AttributeValueDataConverterException;
import an.xacml.converter.IAttributeValueDataConverter;
import an.xacml.engine.AttributeRetriever;
import an.xacml.engine.EvaluationContext;
import an.xacml.policy.AttributeValue;
import an.xml.XMLDataTypeMappingException;
import an.xml.XMLParserWrapper;
import eu.dnetlib.enabling.ISConstants;
import eu.dnetlib.enabling.aas.retrievers.resultset.IResultSetProvider;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.resultset.ResultSetConstants;
import eu.dnetlib.enabling.resultset.rmi.ResultSetException;
import eu.dnetlib.enabling.resultset.rmi.ResultSetService;
import eu.dnetlib.enabling.tools.ServiceLocator;

/**
 * Generic catalog attribute retriever.
 * @author mhorst
 *
 */
public class ISLookupAttributeRetriever implements AttributeRetriever {
	
	/**
	 * Yadda datatype prefix describing which serializer should be used to convert xml source data.
	 */
	public static final String YADDA_DATATYPE_PREFIX = "http://www.icm.edu.pl/yadda#";
	
	/**
	 * Request context path delimiter.
	 */
	public static final String REQ_CTX_PATH_DELIM = "#";
	
	/**
	 * Id path delimiter.
	 */
	public static final String ID_PATH_DELIM = "$";
	
	/**
	 * Direct id cached data type.
	 */
	public static final String DIRECT_ID_CACHED_DATA_TYPE = "#direct_id#";
	
	/**
	 * This is public clone of private {@link XMLParserWrapper#SUN_DOCUMENTBUILDER_FACTORY_CLASSNAME}.
	 */
	public static final String SUN_DOCUMENTBUILDER_FACTORY_CLASSNAME = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl";
	
	/**
	 * Supported AttributeRetriever prefix.
	 */
	public static final String SUPPORTED_PREFIX	= "eu:dnetlib:enabling:aas:is-lookup";
	
	
	public static final String RESOURCE_PROFILE_PREFIX = "RESOURCE_PROFILE/";
	
	
	protected final Logger log = Logger.getLogger(this.getClass());

	
	/**
	 * IS-lookup service locator used for retrieving profiles.
	 */
	private ServiceLocator<ISLookUpService> lookupLocator;
	
	/**
	 * AttributeValue data converter used to convert String source values into proper XACML objects.
	 */
	private IAttributeValueDataConverter avDataConverter;
	
	/**
	 * Yadda serializer module.
	 */
	private Serializer serializer;
	
	/**
	 * NamespaceContext for resolving namespaces by xPath module.
	 */
	private NamespaceContext namespaceContext;
	
	/**
	 * ResultSet provider module. Injected implementation may use service caching etc.
	 * As a bonus makes testing much easier.
	 */
	private IResultSetProvider resultSetProvider;
	
	/**
	 * Flag indicating the full profile needs to be retrieved from IS-LU.
	 */
	protected boolean getFullProfileFlag = true;
	
	/**
	 * Flag indicating full-blown, suffixed (with AR URI "pointer") param is expected in request if given
	 * attribute retriever was marked with URI identifier. If set to false generic params are allowed.
	 * When attribute retriever wasn't marked with URI, generic param is used.
	 */
	protected boolean forceSuffixedParams = false;
	
	
	private static XPathFactory xpathFactory = XPathFactory.newInstance();
	
    protected XPath xpath = null;
    
	/**
	 * Spring init method. Initializes xPath module.
	 */
	public void init() {
		xpath = xpathFactory.newXPath();
		if (namespaceContext!=null) {
			xpath.setNamespaceContext(namespaceContext);
		} else {
			log.warn("namespaceContext is null! no namespace context will be set!");
		}
	}
	
	/* (non-Javadoc)
	 * @see an.xacml.engine.AttributeRetriever#getType()
	 */
	public int getType() {
		return ANY;
	}

	/* (non-Javadoc)
	 * @see an.xacml.engine.AttributeRetriever#isAttributeSupported(java.net.URI, java.net.URI)
	 */
	public boolean isAttributeSupported(URI attrId, URI dataType) {
		if (attrId.toString().startsWith(SUPPORTED_PREFIX)) {
            return true;
        }
        return false;
	}

	/* (non-Javadoc)
	 * @see an.xacml.engine.AttributeRetriever#retrieveAttributeValues(an.xacml.engine.EvaluationContext, java.net.URI, java.net.URI, java.lang.String, java.net.URI)
	 */
	public AttributeValue[] retrieveAttributeValues(EvaluationContext context,
			URI attrIdURI, URI dataType, String issuer, URI subjCategory)
			throws IndeterminateException {
		return retrieveAttributeValuesFromLookup(context, issuer, dataType, attrIdURI);
	}

	/* (non-Javadoc)
	 * @see an.xacml.engine.AttributeRetriever#retrieveAttributeValues(an.xacml.engine.EvaluationContext, java.lang.String, java.net.URI, org.w3c.dom.Element, java.util.Map)
	 */
	public AttributeValue[] retrieveAttributeValues(EvaluationContext context,
			String requestCtxPath, URI dataType, Element request,
			Map<String, String> additionalNSMappings) throws IndeterminateException {
//		when running in attribute selector mode, no identifier is accessible
		return retrieveAttributeValuesFromLookup(context, requestCtxPath, dataType, null);
	}
	
	/**
	 * Generates cached data entry key. Different key is generated 
	 * according to the getFullProfileMode and direct id retrieval modes
	 * taking into account whether xPath value was used.
	 * Never returns null.
	 * @param ctxPathDTO
	 * @return cached data entry key
	 */
	private String generateCachedDataKey(ContextPathDTO ctxPathDTO) {
		if (ctxPathDTO.getIdPath()!=null) {
			if (getFullProfileFlag || ctxPathDTO.getValuePath()==null) {
//				no xpath was used when retrieving data
				if (ctxPathDTO.getProfileKind()!=null && ctxPathDTO.getProfileType()!=null) {
					return ctxPathDTO.getProfileKind() + '|' + 
						ctxPathDTO.getProfileType() + '|' +
						ctxPathDTO.getIdPath();
				} else {
					return ctxPathDTO.getIdPath();
				}
			} else {
//				cache data according to the value xpath
				if (ctxPathDTO.getProfileKind()!=null && ctxPathDTO.getProfileType()!=null) {
					return ctxPathDTO.getProfileKind() + '|' + 
						ctxPathDTO.getProfileType() + '|' +
						ctxPathDTO.getIdPath() + "|" + ctxPathDTO.getValuePath();
				} else {
					return ctxPathDTO.getIdPath() + "|" + ctxPathDTO.getValuePath();
				}
				
			}
		} else {
//			direct access mode, no xpath was used when retrieving data
			return DIRECT_ID_CACHED_DATA_TYPE;
		}
	}
	
	/**
	 * Returns data from cache.
	 * @param ctxPathDTO context path DTO
	 * @param req ExtendedRequest containing cache
	 * @return data from cache
	 */
	protected CachedDataObjectHolder getCachedData(ContextPathDTO ctxPathDTO, 
			ExtendedRequest req) {
		if (req==null || ctxPathDTO==null ||
				ctxPathDTO.getId()==null) {
			log.error("Cannot get catalog data from cache! " +
					"One of id or context is null!");
			return null;
		} else {
			String type = generateCachedDataKey(ctxPathDTO);
			return req.getRequestCache().getCachedData(type, ctxPathDTO.getId());
		}
	}
	
	/**
	 * Stores xml catalog data in cache.
	 * @param data data to be stored
	 * @param ctxPathDTO
	 * @param req ExtendedRequest holding request cache
	 */
	protected void storeDataInCache(CachedDataObjectHolder data, 
			ContextPathDTO ctxPathDTO, ExtendedRequest req) {
		if (req==null) {
			log.error("cannot store data: null ExtendedRequest!");
			return;
		}
		if (ctxPathDTO==null || ctxPathDTO.getId()==null) {
			log.error("id cannot be null!");
			return;
		}
		String type = generateCachedDataKey(ctxPathDTO);
		req.getRequestCache().setCachedData(type, ctxPathDTO.getId(), data);
		return;
	}
	
	/**
	 * Retrieves attribute values from IS-lookup service.
	 * @param context evaluation context
	 * @param requestCtxPath extended request context path containing id and xpath expressions
	 * @param dataType data type
	 * @param attrIdURI
	 * @return attribute values
	 */
	protected AttributeValue[] retrieveAttributeValuesFromLookup(EvaluationContext context,
			String requestCtxPath, URI dataType, URI attrIdURI) throws IndeterminateException {
		return retrieveAttributeValuesFromLookup((ExtendedRequest) context.getRequest(),
				parseRequestCtxPath(requestCtxPath), dataType, attrIdURI);
	}
	
	/**
	 * Retrieves attribute values from IS-lookup service.
	 * @param req ExtendedRequest object holding cache
	 * @param typedCtxPathDTO context path containing id and xpath expressions
	 * @param dataType data type
	 * @param attrIdURI
	 * @return attribute values
	 */
	protected AttributeValue[] retrieveAttributeValuesFromLookup(ExtendedRequest req,
			ContextPathDTO typedCtxPathDTO, URI dataType, URI attrIdURI) throws IndeterminateException {
		
		if (typedCtxPathDTO.getValuePath()==null) {
			log.warn("No value xPath reference was specified! " +
					"Assuming whole profile should be returned");
		}
		List<String> fields = getFieldsFromRequest(req, attrIdURI);
		if(fields.size()==0) {
			log.error("no field passed to AttributeRetriever! " +
					"Cannot determine profile identifier!");
			return new AttributeValue[0];
		} else {
//			TODO in future more than one field may be sported
			typedCtxPathDTO.setId(fields.get(0));
			if (fields.size()>1) {
				log.warn("got "+ fields.size() + " fields, only one is currently supported!");
			}
		}
		String[] lookupDataArray = getLookupData(req, typedCtxPathDTO);
		if (lookupDataArray!=null && lookupDataArray.length>0) {
			if (getFullProfileFlag && typedCtxPathDTO.getValuePath()!=null && 
					typedCtxPathDTO.getValuePath().trim().length()>0) {
//				when xPath expression is specified
				List<AttributeValue> avResults = new ArrayList<AttributeValue>();
				for (String profileData : lookupDataArray) {
					NodeList nList = null;
					try {
						nList = (NodeList)xpath.evaluate(
								typedCtxPathDTO.getValuePath(), new InputSource(new StringReader(profileData)), 
		                    XPathConstants.NODESET);
					} catch (XPathExpressionException e) {
						throw new IndeterminateException("Couldn't get elements for xpath expr: " +
								typedCtxPathDTO.getValuePath() + " from profile data:" + profileData, e);
					} 
					if (nList==null) {
						log.debug("got no results for xpath expr:" + typedCtxPathDTO.getValuePath() + 
								" on profile data: " + profileData);
						continue;
					}
					for (int i = 0; i < nList.getLength(); i ++) {
		                Node node = nList.item(i);
		                short nodeType = node.getNodeType();
		                if (nodeType == Node.TEXT_NODE || nodeType == Node.ATTRIBUTE_NODE || 
		                    nodeType == Node.PROCESSING_INSTRUCTION_NODE || nodeType == Node.COMMENT_NODE) {
		                	avResults.add(buildAttributeValue(null, node.getNodeValue(), dataType));
		                }
		                else if (nodeType == Node.ELEMENT_NODE) {
		                	try {
	//		                	processing element node to it's string representation
	//		                	it might be converted later to an object using serialization
			                	TransformerFactory tf = TransformerFactory.newInstance();
			                	Transformer trans = tf.newTransformer();
			                	StringWriter sw = new StringWriter();
			                	trans.transform(new DOMSource(node), new StreamResult(sw));
			                	avResults.add(buildAttributeValue(null, sw.toString(), dataType));
		                	} catch (TransformerConfigurationException e) {
								throw new IndeterminateException("Problem occured when coverting Element node " +
										"for xpath: " + typedCtxPathDTO.getValuePath() + ", from source: " + profileData, e);
							} catch (TransformerException e) {
								throw new IndeterminateException("Problem occured when coverting Element node " +
										"for xpath: " + typedCtxPathDTO.getValuePath() + ", from source: " + profileData, e);
							}
		                } else {
		                    throw new IndeterminateException("The node selected by specfied XPath expression is not one of "
		                    + "following - a text node, an attribute node, a processing instruction node, element node or a " 
		                    + "comment node. Problem occured when processing xPath: " + typedCtxPathDTO.getValuePath() 
		                    + " on catalog data: " + profileData, Constants.STATUS_SYNTAXERROR);
		                }
		            }
					
				}
				return avResults.toArray(new AttributeValue[avResults.size()]);
			} else {
//				xPath expression is not specified or was already used when extracting data from IS-LU
				AttributeValue[] avResults = new AttributeValue[lookupDataArray.length];
				for (int i=0; i<lookupDataArray.length; i++) {
					avResults[i] = buildAttributeValue(typedCtxPathDTO.getId(), 
							lookupDataArray[i], dataType);
				}
				return avResults;
			}
		} else {
			log.warn("got null or empty data from lookup service for id: " + 
					typedCtxPathDTO.getId() + ", id path: " + typedCtxPathDTO.getIdPath());
			return new AttributeValue[0];
		}
	}

	/**
	 * Returns data retrieved from IS-lookup service or from cache if 
	 * already retrieved during current request processing phase.
	 * @param req
	 * @param typedCtxPathDTO
	 * @return lookup data
	 * @throws IndeterminateException
	 */
	protected String[] getLookupData(ExtendedRequest req, 
			ContextPathDTO typedCtxPathDTO) throws IndeterminateException {
		CachedDataObjectHolder cachedData = getCachedData(typedCtxPathDTO, req);
		if (cachedData==null) {
			try {
				if (typedCtxPathDTO.getIdPath()==null) {
//					direct id mode
					String resultProfile = lookupLocator.getService().getResourceProfile(
							typedCtxPathDTO.getId());
					if (resultProfile!=null) {
						String[] catalogDataArray = new String[] {resultProfile};
						storeDataInCache(new CachedDataObjectHolder((Object) catalogDataArray[0]), 
								typedCtxPathDTO, req);
						return catalogDataArray;
					} else {
						return null;
					}
				} else {
//					xquery mode
					W3CEndpointReference epr = lookupLocator.getService().searchProfile(
							generateXQuery(typedCtxPathDTO, !getFullProfileFlag));
					ResultSetService resultSet = resultSetProvider.discover(epr);
					String rsId = resultSetProvider.extractId(epr);
					if (rsId==null) {
						throw new IndeterminateException("got invalid W3CEndpointReference, " +
								"no rsId could be found: " + epr.toString());
					}
					int numberOfElements = resultSet.getNumberOfElements(rsId);
					if (numberOfElements==0) {
						return null;
					}
					List<String> foundResults = resultSet.getResult(rsId, 
							ResultSetConstants.RESULT_SET_FIRST_ELEMENT, 
							numberOfElements, 
							ResultSetConstants.RESULT_SET_REQUEST_MODE_WAITING);

					if (foundResults!=null && foundResults.size()>0) {
						if (foundResults.size()==1) {
							String[] sourceDataArray = new String[] {foundResults.get(0)};
							storeDataInCache(new CachedDataObjectHolder((Object) sourceDataArray[0]), 
									typedCtxPathDTO, req);
							return sourceDataArray;
						} else {
							String[] sourceDataArray = foundResults.toArray(new String[foundResults.size()]);
							storeDataInCache(new CachedDataObjectHolder((Object[]) sourceDataArray), 
									typedCtxPathDTO, req);
							return sourceDataArray;
						}
					} else {
						return null;
					}
				}
			} catch (ISLookUpException e) {
				throw new IndeterminateException("Couldn't get IS-lookup data for id: " + 
						typedCtxPathDTO.getId() + " and id path: "+typedCtxPathDTO.getIdPath(), e);
			} catch (ResultSetException e) {
				throw new IndeterminateException("Couldn't get IS-lookup data for id: " + 
						typedCtxPathDTO.getId() + " and id path: "+typedCtxPathDTO.getIdPath(), e);
			}
		} else {
			if (cachedData.isSingleObject()) {
				return new String[] {(String) cachedData.getSingleDataObject()};
			} else {
				return (String[]) cachedData.getArrayDataObject();
			}
		}
	}
	
	/**
     * Builds collection name for given profileKind and profileType.
     * Never returns null.
     * @param profileKind
     * @param profileType
     * @return collection name
     */
    public static String buildCollectionName(String profileKind, String profileType) {
            StringBuffer strBuff = new StringBuffer(ISConstants.IS_COLLECTION_DRIVER_ROOT_PATH);
            if (profileKind != null && profileType != null) {
//            	both are required
            	strBuff.append('/');
            	strBuff.append(profileKind);
                strBuff.append('/');
                strBuff.append(profileType);
            }
            return strBuff.toString();
    }
	
	/**
	 * Generates xquery for given context path DTO.
	 * @param contextPathDTO
	 * @param attachValuePath
	 * @return xquery
	 */
	public static String generateXQuery(ContextPathDTO contextPathDTO,
			boolean attachValuePath) {
		String collectionName = buildCollectionName(contextPathDTO.getProfileKind(), 
				contextPathDTO.getProfileType());
		
		StringBuffer strBuff = new StringBuffer("for $el in fn:collection(\"");
        strBuff.append(collectionName.substring("db/".length()));
        strBuff.append("\")");
        if (contextPathDTO.getIdPath()!=null && contextPathDTO.getId()!=null) {
        	if (contextPathDTO.getIdPath().startsWith(RESOURCE_PROFILE_PREFIX)) {
        		strBuff.append("/RESOURCE_PROFILE[" + contextPathDTO.getIdPath().substring(
            			RESOURCE_PROFILE_PREFIX.length()) + 
                		" = '" + contextPathDTO.getId() + "']");
        	} else {
        		strBuff.append("/RESOURCE_PROFILE[" + contextPathDTO.getIdPath() + 
                		" = '" + contextPathDTO.getId() + "']");
        	}
        }
        strBuff.append(" return $el");
        if (attachValuePath && contextPathDTO.getValuePath()!=null) {
                strBuff.append('/');
                if (contextPathDTO.getValuePath().startsWith(RESOURCE_PROFILE_PREFIX)) {
                	strBuff.append(contextPathDTO.getValuePath().substring(
                			RESOURCE_PROFILE_PREFIX.length()));
                } else {
                	strBuff.append(contextPathDTO.getValuePath());
                }
        }
        return strBuff.toString();
	}
	
	/**
	 * Retrieves fields which should identify the required object accessible via IS-Lookup.
	 * Checks whether attrId URI is available to be used as suffix. If forceSuffixedParams
	 * flag is not set generic params identified by dnetlib:service:lookup:parameter are returned 
	 * when no suffixed params could be found.
	 * Never returns null.
	 * @param req xacml request
	 * @param attrIdURI attribute id which is used as suffix of SUBJECT_PARAM_ID
	 * @return list of fields extracted from request
	 */
	protected List<String> getFieldsFromRequest(Request req, URI attrIdURI) {
//		list of generic fields, with no suffixes
		List<String> genericFields = new ArrayList<String>();
//		suffixed fields related values, set only when attrIdURI was provided
		URI suffixedURI = null;
		List<String> suffixedFields = null;
		if (attrIdURI!=null) {
			suffixedURI = URI.create(ISLookupAttributeRetrieverConstants.SUBJECT_PARAM_ID +
					':' + attrIdURI.toASCIIString());
			suffixedFields = new ArrayList<String>();
		}
		
		URI expectedSubjectCategoryURI = URI.create(ISLookupAttributeRetrieverConstants.SUBJECT_CATEGORY);
		URI expectedGenericURI = URI.create(ISLookupAttributeRetrieverConstants.SUBJECT_PARAM_ID); 
		for(Subject subject:req.getSubjects()) {
			if(subject.getSubjectCategory()!=null && subject.getSubjectCategory().compareTo(
					expectedSubjectCategoryURI)==0) {
				for(Attribute attr:subject.getAllAttributes()) {
					if(attr.getAttributeID().compareTo(expectedGenericURI)==0) {
//						generic fields
						for (Object value : attr.getAttributeValues()) {
							if (value instanceof String) {
								genericFields.add((String) value);
							} else {
								log.warn("Expected value instance: String, got: " + 
										value.getClass().getName());
							}
						}
					} else if (suffixedURI!=null && 
							attr.getAttributeID().compareTo(suffixedURI)==0) {
//						suffixed fields
						for (Object value : attr.getAttributeValues()) {
							if (value instanceof String) {
								suffixedFields.add((String) value);
							} else {
								log.warn("Expected value instance: String, got: " + 
										value.getClass().getName());
							}
						}
					}
				}
			}
		}
//		returning results
		if (attrIdURI!=null) {
			if (forceSuffixedParams) {
				return suffixedFields;
			} else {
//				returning genericFields if no suffixedFields found
				if (!suffixedFields.isEmpty()) {
					return suffixedFields;
				} else {
					return genericFields;
				}
			}
		} else {
			return genericFields;
		}
	}
	
	/**
	 * Creates result AttributeValue based on String result given as parameter and data type.
	 * Performs xml deserialization into Yadda model object if nedded. 
	 * @param id identifier
	 * @param result object's String representation
	 * @param dataType result data type
	 * @return AttributeValue
	 * @throws IndeterminateException
	 */
	protected AttributeValue buildAttributeValue(String id, String result, URI dataType) 
		throws IndeterminateException {
		try {
			if (Constants.TYPE_STRING.equals(dataType)) {
				return AttributeValue.getInstance(dataType, result);
			} else if (dataType.toString().startsWith(YADDA_DATATYPE_PREFIX)) {
				try {
//					using yadda converter
					return AttributeValue.getInstance(dataType, 
						serializer.toObject(id, result));
				} catch (Exception e) {
					throw new IndeterminateException("Cannot convert yadda object for given id: " + 
							id + " and string representation: " + result, e);
				}
			} else {
				try {
//					using standard AttributeValue converter.
					return AttributeValue.getInstance(dataType, 
							avDataConverter.convertData(result, dataType));
				} catch (AttributeValueDataConverterException e) {
					throw new IndeterminateException("Couldn't convert data: " + result + 
							" for given result type: " + dataType, e);
				}
			}
		} catch (XMLDataTypeMappingException e) {
			throw new IndeterminateException("Cannot create AttributeValue instance for value: " 
					+ result + ", and dataType: " + dataType, e);
		}
	}
	
	/**
	 * Parses extended requestCtxPath by extracting xPaths to the id and value.
	 * requestCtxPath should contain idXPath + delimiter + valueXPath.
	 * If no delimiter is present it is assumed the value of 
	 * requestCtxPath contains valueXPath only as it is required field.
	 * Never returns null.
	 * @param requestCtxPath
	 * @return TypedContextPathDTO
	 */
	protected ContextPathDTO parseRequestCtxPath(String requestCtxPath) {
		if (requestCtxPath==null || requestCtxPath.trim().length()==0) {
			return new ContextPathDTO(null, null, null);
		} else {
			String[] tokens = StringUtils.tokenizeToStringArray(
					requestCtxPath, REQ_CTX_PATH_DELIM);
			if (tokens==null || tokens.length==0) {
				return new ContextPathDTO(null, null, null);
			} else if (tokens.length==1) {
//				value path only mode
				return new ContextPathDTO(null, 
						null, 
						tokens[0]);
			} else {
				String[] idPathTokens = StringUtils.tokenizeToStringArray(
						tokens[0], ID_PATH_DELIM);
				if (idPathTokens.length==1) {
//					supported pattern pathToId
					return new ContextPathDTO(null, 
							tokens[0], 
							tokens[1]);
				} else if (idPathTokens.length==3) {
//					supported pattern: profileKind#profileType#pathToId
					return new ContextPathDTO(null, 
							idPathTokens[2], 
							tokens[1], 
							idPathTokens[0], idPathTokens[1]);
				} else {
					throw new InvalidParameterException(
							"invalid identifier path: " + tokens[0]);
				}
			}
		}
	}

	/**
	 * Sets AttributeValue data converter.
	 * @param avDataConverter
	 */
	public void setAvDataConverter(IAttributeValueDataConverter avDataConverter) {
		this.avDataConverter = avDataConverter;
	}

	/**
	 * Sets yadda serializer module.
	 * @param serializer
	 */
	public void setSerializer(Serializer serializer) {
		this.serializer = serializer;
	}

	/**
	 * Sets namespace context.
	 * @param namespaceContext
	 */
	public void setNamespaceContext(NamespaceContext namespaceContext) {
		this.namespaceContext = namespaceContext;
	}

	/**
	 * Sets IS-lookup service locator used for retrieving profiles.
	 * @param lookupLocator
	 */
	public void setLookupLocator(ServiceLocator<ISLookUpService> lookupLocator) {
		this.lookupLocator = lookupLocator;
	}

	/**
	 * Sets getFullProfileFlag.
	 * @param getFullProfileFlag
	 */
	public void setGetFullProfileFlag(boolean getFullProfileFlag) {
		this.getFullProfileFlag = getFullProfileFlag;
	}

	/**
	 * Sets ResultSet provider module.
	 * @param resultSetProvider
	 */
	public void setResultSetProvider(IResultSetProvider resultSetProvider) {
		this.resultSetProvider = resultSetProvider;
	}

	/**
	 * Sets forceSuffixedParams flag.
	 * @param forceSuffixedParams
	 */
	public void setForceSuffixedParams(boolean forceSuffixedParams) {
		this.forceSuffixedParams = forceSuffixedParams;
	}

}