<commit_before>/**
 * Copyright (C) 2011-2015 Thales Services SAS.
 *
 * This file is part of AuthZForce.
 *
 * AuthZForce is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * AuthZForce is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with AuthZForce. If not, see <http://www.gnu.org/licenses/>.
 */
package org.ow2.authzforce.core.pdp.impl;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;

import org.ow2.authzforce.core.pdp.api.CombiningAlg;
import org.ow2.authzforce.core.pdp.api.DatatypeFactory;
import org.ow2.authzforce.core.pdp.api.DecisionResultFilter;
import org.ow2.authzforce.core.pdp.api.Function;
import org.ow2.authzforce.core.pdp.api.FunctionSet;
import org.ow2.authzforce.core.pdp.api.JaxbBoundPdpExtension;
import org.ow2.authzforce.core.pdp.api.PdpExtension;
import org.ow2.authzforce.core.pdp.api.RequestFilter;
import org.ow2.authzforce.core.pdp.impl.combining.CombiningAlgSet;
import org.ow2.authzforce.xmlns.pdp.ext.AbstractPdpExtension;

/**
 * Loads PDP extensions (implementing {@link PdpExtension}) from classpath using {@link ServiceLoader}.
 * 
 * 
 */
public class PdpExtensionLoader
{
	// private static final Logger LOGGER = LoggerFactory.getLogger(PdpExtensionLoader.class);

	/*
	 * For each type of extension, we build the maps allowing to get the compatible/supporting extension class, using {@link ServiceLoader} API, to discover
	 * these classes from files 'META-INF/services/com.thalesgroup.authzforce.core.PdpExtension' on the classpath, in the format described by {@link
	 * ServiceLoader} API documentation.
	 */

	/*
	 * For each type of XML/JAXB-bound extension, map XML/JAXB conf class to corresponding extension (we assume a one-to-one relationship between the XML/JAXB
	 * type and the extension class)
	 */
	private final static Map<Class<? extends AbstractPdpExtension>, JaxbBoundPdpExtension<? extends AbstractPdpExtension>> JAXB_BOUND_EXTENSIONS_BY_JAXB_CLASS = new HashMap<>();

	/*
	 * Types of zero-conf (non-JAXB-bound) extesnsion
	 */
	private static final Set<Class<? extends PdpExtension>> NON_JAXB_BOUND_EXTENSION_CLASSES = new HashSet<>(Arrays.asList(DatatypeFactory.class,
			Function.class, FunctionSet.class, CombiningAlg.class, RequestFilter.Factory.class, DecisionResultFilter.class));
	/*
	 * For each type of zero-conf (non-JAXB-bound) extension, have a map (extension ID -> extension instance), so that the extension ID is scoped to the
	 * extension type among the ones listed in NON_JAXB_BOUND_EXTENSION_CLASSES (you can have same ID but for different types of extensions).
	 */
	private final static Map<Class<? extends PdpExtension>, Map<String, PdpExtension>> NON_JAXB_BOUND_EXTENSIONS_BY_CLASS_AND_ID = new HashMap<>();

	static
	{
		/*
		 * REMINDER: every service provider (implementation class) loaded by ServiceLoader MUST HAVE a ZERO-ARGUMENT CONSTRUCTOR.
		 */
		final ServiceLoader<PdpExtension> extensionLoader = ServiceLoader.load(PdpExtension.class);
		for (final PdpExtension extension : extensionLoader)
		{
			boolean isValidExt = false;
			if (extension instanceof JaxbBoundPdpExtension<?>)
			{
				final JaxbBoundPdpExtension<?> jaxbBoundExt = (JaxbBoundPdpExtension<?>) extension;
				final JaxbBoundPdpExtension<?> conflictingExt = JAXB_BOUND_EXTENSIONS_BY_JAXB_CLASS.put(jaxbBoundExt.getJaxbClass(), jaxbBoundExt);
				if (conflictingExt != null)
				{
					throw new IllegalArgumentException("Extension " + jaxbBoundExt + " (" + jaxbBoundExt.getClass() + ") is conflicting with " + conflictingExt
							+ "(" + conflictingExt.getClass() + ") for the same XML/JAXB configuration class: " + jaxbBoundExt.getJaxbClass());
				}

				isValidExt = true;
			} else
			{
				for (final Class<? extends PdpExtension> extClass : NON_JAXB_BOUND_EXTENSION_CLASSES)
				{
					if (extClass.isInstance(extension))
					{
						final Map<String, PdpExtension> oldMap = NON_JAXB_BOUND_EXTENSIONS_BY_CLASS_AND_ID.get(extClass);
						final Map<String, PdpExtension> newMap;
						if (oldMap == null)
						{
							newMap = new HashMap<>();
							NON_JAXB_BOUND_EXTENSIONS_BY_CLASS_AND_ID.put(extClass, newMap);
						} else
						{
							newMap = oldMap;
						}

						final PdpExtension conflictingExt = newMap.put(extension.getId(), extension);
						if (conflictingExt != null)
						{
							throw new IllegalArgumentException("Extension " + extension + " is conflicting with " + conflictingExt
									+ " registered with same ID: " + extension.getId());
						}

						isValidExt = true;
						break;
					}
				}
			}

			if (!isValidExt)
			{
				throw new UnsupportedOperationException("Unsupported/invalid type of PDP extension: " + extension.getClass() + " (extension ID = "
						+ extension.getId() + ")");
			}
		}
	}

	/**
	 * Get PDP extension configuration classes (JAXB-generated from XML schema)
	 * 
	 * @return classes representing datamodels of configurations of all PDP extensions
	 */
	public static Set<Class<? extends AbstractPdpExtension>> getExtensionJaxbClasses()
	{
		return Collections.unmodifiableSet(JAXB_BOUND_EXTENSIONS_BY_JAXB_CLASS.keySet());
	}

	/**
	 * Get non-JAXB-bound (aka zero-configuration) extension
	 * 
	 * @param extensionType
	 *            type of extension: {@link DatatypeFactory }, {@link Function}, {@link CombiningAlgSet}, etc.
	 * @param id
	 *            extension ID
	 * @return PDP extension instance of class {@code extensionType} and such that its method {@link PdpExtension#getId()} returns {@code id}
	 * @throws IllegalArgumentException
	 *             if there is not any extension found for type {@code extensionType} with ID {@code id}
	 */
	public static <T extends PdpExtension> T getExtension(Class<T> extensionType, String id) throws IllegalArgumentException
	{
		final Map<String, PdpExtension> typeSpecificExtsById = NON_JAXB_BOUND_EXTENSIONS_BY_CLASS_AND_ID.get(extensionType);
		if (typeSpecificExtsById == null)
		{
			throw new IllegalArgumentException("Invalid (non-JAXB-bound) PDP extension type: " + extensionType + ". Expected types: "
					+ NON_JAXB_BOUND_EXTENSION_CLASSES);
		}

		final PdpExtension ext = typeSpecificExtsById.get(id);
		if (ext == null)
		{
			throw new IllegalArgumentException("No PDP extension of type '" + extensionType + "' found with ID: " + id + ". Expected IDs: "
					+ typeSpecificExtsById.keySet());
		}

		return extensionType.cast(ext);
	}

	/**
	 * Get XML/JAXB-bound extension
	 * <commit_msg>Fixed error message when no extension available for valid type of extension<commit_after>/**
 * Copyright (C) 2011-2015 Thales Services SAS.
 *
 * This file is part of AuthZForce.
 *
 * AuthZForce is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * AuthZForce is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with AuthZForce. If not, see <http://www.gnu.org/licenses/>.
 */
package org.ow2.authzforce.core.pdp.impl;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;

import org.ow2.authzforce.core.pdp.api.CombiningAlg;
import org.ow2.authzforce.core.pdp.api.DatatypeFactory;
import org.ow2.authzforce.core.pdp.api.DecisionResultFilter;
import org.ow2.authzforce.core.pdp.api.Function;
import org.ow2.authzforce.core.pdp.api.FunctionSet;
import org.ow2.authzforce.core.pdp.api.JaxbBoundPdpExtension;
import org.ow2.authzforce.core.pdp.api.PdpExtension;
import org.ow2.authzforce.core.pdp.api.RequestFilter;
import org.ow2.authzforce.core.pdp.impl.combining.CombiningAlgSet;
import org.ow2.authzforce.xmlns.pdp.ext.AbstractPdpExtension;

/**
 * Loads PDP extensions (implementing {@link PdpExtension}) from classpath using {@link ServiceLoader}.
 * 
 * 
 */
public class PdpExtensionLoader
{
	// private static final Logger LOGGER = LoggerFactory.getLogger(PdpExtensionLoader.class);

	/*
	 * For each type of extension, we build the maps allowing to get the compatible/supporting extension class, using {@link ServiceLoader} API, to discover
	 * these classes from files 'META-INF/services/com.thalesgroup.authzforce.core.PdpExtension' on the classpath, in the format described by {@link
	 * ServiceLoader} API documentation.
	 */

	/*
	 * For each type of XML/JAXB-bound extension, map XML/JAXB conf class to corresponding extension (we assume a one-to-one relationship between the XML/JAXB
	 * type and the extension class)
	 */
	private final static Map<Class<? extends AbstractPdpExtension>, JaxbBoundPdpExtension<? extends AbstractPdpExtension>> JAXB_BOUND_EXTENSIONS_BY_JAXB_CLASS = new HashMap<>();

	/*
	 * Types of zero-conf (non-JAXB-bound) extesnsion
	 */
	private static final Set<Class<? extends PdpExtension>> NON_JAXB_BOUND_EXTENSION_CLASSES = new HashSet<>(Arrays.asList(DatatypeFactory.class,
			Function.class, FunctionSet.class, CombiningAlg.class, RequestFilter.Factory.class, DecisionResultFilter.class));
	/*
	 * For each type of zero-conf (non-JAXB-bound) extension, have a map (extension ID -> extension instance), so that the extension ID is scoped to the
	 * extension type among the ones listed in NON_JAXB_BOUND_EXTENSION_CLASSES (you can have same ID but for different types of extensions).
	 */
	private final static Map<Class<? extends PdpExtension>, Map<String, PdpExtension>> NON_JAXB_BOUND_EXTENSIONS_BY_CLASS_AND_ID = new HashMap<>();

	static
	{
		/*
		 * REMINDER: every service provider (implementation class) loaded by ServiceLoader MUST HAVE a ZERO-ARGUMENT CONSTRUCTOR.
		 */
		final ServiceLoader<PdpExtension> extensionLoader = ServiceLoader.load(PdpExtension.class);
		for (final PdpExtension extension : extensionLoader)
		{
			boolean isValidExt = false;
			if (extension instanceof JaxbBoundPdpExtension<?>)
			{
				final JaxbBoundPdpExtension<?> jaxbBoundExt = (JaxbBoundPdpExtension<?>) extension;
				final JaxbBoundPdpExtension<?> conflictingExt = JAXB_BOUND_EXTENSIONS_BY_JAXB_CLASS.put(jaxbBoundExt.getJaxbClass(), jaxbBoundExt);
				if (conflictingExt != null)
				{
					throw new IllegalArgumentException("Extension " + jaxbBoundExt + " (" + jaxbBoundExt.getClass() + ") is conflicting with " + conflictingExt
							+ "(" + conflictingExt.getClass() + ") for the same XML/JAXB configuration class: " + jaxbBoundExt.getJaxbClass());
				}

				isValidExt = true;
			} else
			{
				for (final Class<? extends PdpExtension> extClass : NON_JAXB_BOUND_EXTENSION_CLASSES)
				{
					if (extClass.isInstance(extension))
					{
						final Map<String, PdpExtension> oldMap = NON_JAXB_BOUND_EXTENSIONS_BY_CLASS_AND_ID.get(extClass);
						final Map<String, PdpExtension> newMap;
						if (oldMap == null)
						{
							newMap = new HashMap<>();
							NON_JAXB_BOUND_EXTENSIONS_BY_CLASS_AND_ID.put(extClass, newMap);
						} else
						{
							newMap = oldMap;
						}

						final PdpExtension conflictingExt = newMap.put(extension.getId(), extension);
						if (conflictingExt != null)
						{
							throw new IllegalArgumentException("Extension " + extension + " is conflicting with " + conflictingExt
									+ " registered with same ID: " + extension.getId());
						}

						isValidExt = true;
						break;
					}
				}
			}

			if (!isValidExt)
			{
				throw new UnsupportedOperationException("Unsupported/invalid type of PDP extension: " + extension.getClass() + " (extension ID = "
						+ extension.getId() + ")");
			}
		}
	}

	/**
	 * Get PDP extension configuration classes (JAXB-generated from XML schema)
	 * 
	 * @return classes representing datamodels of configurations of all PDP extensions
	 */
	public static Set<Class<? extends AbstractPdpExtension>> getExtensionJaxbClasses()
	{
		return Collections.unmodifiableSet(JAXB_BOUND_EXTENSIONS_BY_JAXB_CLASS.keySet());
	}

	/**
	 * Get non-JAXB-bound (aka zero-configuration) extension
	 * 
	 * @param extensionType
	 *            type of extension: {@link DatatypeFactory }, {@link Function}, {@link CombiningAlgSet}, etc.
	 * @param id
	 *            extension ID
	 * @return PDP extension instance of class {@code extensionType} and such that its method {@link PdpExtension#getId()} returns {@code id}
	 * @throws IllegalArgumentException
	 *             if there is not any extension found for type {@code extensionType} with ID {@code id}
	 */
	public static <T extends PdpExtension> T getExtension(Class<T> extensionType, String id) throws IllegalArgumentException
	{
		if(!NON_JAXB_BOUND_EXTENSIONS_BY_CLASS_AND_ID.containsKey(extensionType)) 
		{
			throw new IllegalArgumentException("Invalid (non-JAXB-bound) PDP extension type: " + extensionType + ". Expected types: "
					+ NON_JAXB_BOUND_EXTENSION_CLASSES);	
		}
		
		final Map<String, PdpExtension> typeSpecificExtsById = NON_JAXB_BOUND_EXTENSIONS_BY_CLASS_AND_ID.get(extensionType);
		if (typeSpecificExtsById == null)
		{
			throw new IllegalArgumentException("No PDP extension of type '" + extensionType + "' found");	
		}

		final PdpExtension ext = typeSpecificExtsById.get(id);
		if (ext == null)
		{
			throw new IllegalArgumentException("No PDP extension of type '" + extensionType + "' found with ID: " + id + ". Expected IDs: "
					+ typeSpecificExtsById.keySet());
		}

		return extensionType.cast(ext);
	}

	/**
	 * Get XML/JAXB-bound extension
	 * 
{'fraction_non_alphanumeric': 0.09762458588848946, 'fraction_numerical': 0.0028194826249383236, 'mean_word_length': 4.1667880553532415, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 57, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4825111', 'n_tokens_mistral': 4831, 'n_tokens_neox': 4262, 'n_words': 1191}