diff --git a/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/pom.xml b/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/pom.xml new file mode 100644 index 000000000000..4c2b9ab262c0 --- /dev/null +++ b/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/pom.xml @@ -0,0 +1,99 @@ + + + + + org.wso2.carbon.identity.framework + servlet-mgt + 7.7.124-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.identity.servlet.mgt + bundle + WSO2 Identity - Servlet Management Component + The Servlet Management backend component + http://wso2.org + + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + javax.servlet + javax.servlet-api + provided + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + provided + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + + ${project.artifactId} + + ${project.artifactId} + + org.wso2.carbon.identity.servlet.mgt.*; version="${carbon.identity.package.export.version}" + + + javax.servlet.*; version="${imp.pkg.version.javax.servlet}" + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + false + + + + com.github.spotbugs + spotbugs-maven-plugin + + ../../../spotbugs-exclude.xml + Max + High + true + + + + + + diff --git a/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/src/main/java/org/wso2/carbon/identity/servlet/mgt/CustomCXFNonSpringJaxrsServlet.java b/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/src/main/java/org/wso2/carbon/identity/servlet/mgt/CustomCXFNonSpringJaxrsServlet.java new file mode 100644 index 000000000000..1b36883680d7 --- /dev/null +++ b/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/src/main/java/org/wso2/carbon/identity/servlet/mgt/CustomCXFNonSpringJaxrsServlet.java @@ -0,0 +1,648 @@ +/* +* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +* +* WSO2 LLC. licenses this file to you 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 org.wso2.carbon.identity.servlet.mgt; + +import org.apache.cxf.Bus; +import org.apache.cxf.common.classloader.ClassLoaderUtils; +import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.common.util.PrimitiveUtils; +import org.apache.cxf.common.util.PropertyUtils; +import org.apache.cxf.common.util.StringUtils; +import org.apache.cxf.feature.Feature; +import org.apache.cxf.helpers.CastUtils; +import org.apache.cxf.interceptor.Interceptor; +import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; +import org.apache.cxf.jaxrs.lifecycle.PerRequestResourceProvider; +import org.apache.cxf.jaxrs.lifecycle.ResourceProvider; +import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider; +import org.apache.cxf.jaxrs.model.ApplicationInfo; +import org.apache.cxf.jaxrs.model.ProviderInfo; +import org.apache.cxf.jaxrs.provider.ProviderFactory; +import org.apache.cxf.jaxrs.utils.InjectionUtils; +import org.apache.cxf.jaxrs.utils.ResourceUtils; +import org.apache.cxf.message.Message; +import org.apache.cxf.service.invoker.Invoker; +import org.apache.cxf.transport.http.DestinationRegistry; +import org.apache.cxf.transport.servlet.CXFNonSpringServlet; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.ws.rs.core.Application; + +/** + * Custom CXFNonSpringJaxrsServlet to override the default behavior of CXFNonSpringJaxrsServlet. + */ +public class CustomCXFNonSpringJaxrsServlet extends CXFNonSpringServlet { + + private static final long serialVersionUID = -8916352798780577499L; + private static final Logger LOG = LogUtils.getL7dLogger(CustomCXFNonSpringJaxrsServlet.class); + private static final String USER_MODEL_PARAM = "user.model"; + private static final String SERVICE_ADDRESS_PARAM = "jaxrs.address"; + private static final String IGNORE_APP_PATH_PARAM = "jaxrs.application.address.ignore"; + private static final String SERVICE_CLASSES_PARAM = "jaxrs.serviceClasses"; + private static final String PROVIDERS_PARAM = "jaxrs.providers"; + private static final String FEATURES_PARAM = "jaxrs.features"; + private static final String OUT_INTERCEPTORS_PARAM = "jaxrs.outInterceptors"; + private static final String OUT_FAULT_INTERCEPTORS_PARAM = "jaxrs.outFaultInterceptors"; + private static final String IN_INTERCEPTORS_PARAM = "jaxrs.inInterceptors"; + private static final String INVOKER_PARAM = "jaxrs.invoker"; + private static final String SERVICE_SCOPE_PARAM = "jaxrs.scope"; + private static final String EXTENSIONS_PARAM = "jaxrs.extensions"; + private static final String LANGUAGES_PARAM = "jaxrs.languages"; + private static final String PROPERTIES_PARAM = "jaxrs.properties"; + private static final String SCHEMAS_PARAM = "jaxrs.schemaLocations"; + private static final String DOC_LOCATION_PARAM = "jaxrs.documentLocation"; + private static final String STATIC_SUB_RESOLUTION_PARAM = "jaxrs.static.subresources"; + private static final String SERVICE_SCOPE_SINGLETON = "singleton"; + private static final String SERVICE_SCOPE_REQUEST = "prototype"; + private static final String PARAMETER_SPLIT_CHAR = "class.parameter.split.char"; + private static final String DEFAULT_PARAMETER_SPLIT_CHAR = ","; + private static final String SPACE_PARAMETER_SPLIT_CHAR = "space"; + private static final String JAXRS_APPLICATION_PARAM = "javax.ws.rs.Application"; + + private ClassLoader classLoader; + private final Application application; + + public CustomCXFNonSpringJaxrsServlet(Application app) { + + this.application = app; + } + + public CustomCXFNonSpringJaxrsServlet(Application app, DestinationRegistry destinationRegistry, Bus bus) { + + super(destinationRegistry, bus); + this.application = app; + } + + @Override + public void init(ServletConfig servletConfig) throws ServletException { + + super.init(servletConfig); + if (this.getApplication() != null) { + this.createServerFromApplication(servletConfig); + } else { + String applicationClass = servletConfig.getInitParameter(JAXRS_APPLICATION_PARAM); + if (applicationClass != null) { + this.createServerFromApplication(applicationClass, servletConfig); + } else { + String splitChar = this.getParameterSplitChar(servletConfig); + JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean(); + bean.setBus(this.getBus()); + String address = servletConfig.getInitParameter(SERVICE_ADDRESS_PARAM); + if (address == null) { + address = "/"; + } + + bean.setAddress(address); + bean.setStaticSubresourceResolution(this.getStaticSubResolutionValue(servletConfig)); + String modelRef = servletConfig.getInitParameter(USER_MODEL_PARAM); + if (modelRef != null) { + bean.setModelRef(modelRef.trim()); + } + + this.setDocLocation(bean, servletConfig); + this.setSchemasLocations(bean, servletConfig); + this.setAllInterceptors(bean, servletConfig, splitChar); + this.setInvoker(bean, servletConfig); + Map, Map>> resourceClasses = this.getServiceClasses(servletConfig, + modelRef != null, splitChar); + Map, ResourceProvider> resourceProviders = this.getResourceProviders(servletConfig, + resourceClasses); + List providers = this.getProviders(servletConfig, splitChar); + bean.setResourceClasses(new ArrayList<>(resourceClasses.keySet())); + bean.setProviders(providers); + + for (Map.Entry, ResourceProvider> entry : resourceProviders.entrySet()) { + bean.setResourceProvider(entry.getKey(), entry.getValue()); + } + + this.setExtensions(bean, servletConfig); + List features = this.getFeatures(servletConfig, splitChar); + bean.getFeatures().addAll(features); + bean.create(); + } + } + } + + protected String getParameterSplitChar(ServletConfig servletConfig) { + + String param = servletConfig.getInitParameter(PARAMETER_SPLIT_CHAR); + return !StringUtils.isEmpty(param) && SPACE_PARAMETER_SPLIT_CHAR.equals(param.trim()) ? " " + : DEFAULT_PARAMETER_SPLIT_CHAR; + } + + protected boolean getStaticSubResolutionValue(ServletConfig servletConfig) { + + String param = servletConfig.getInitParameter(STATIC_SUB_RESOLUTION_PARAM); + return param != null && Boolean.parseBoolean(param.trim()); + } + + protected void setExtensions(JAXRSServerFactoryBean bean, ServletConfig servletConfig) { + + bean.setExtensionMappings(CastUtils.cast(parseMapSequence(servletConfig.getInitParameter(EXTENSIONS_PARAM)))); + bean.setLanguageMappings(CastUtils.cast(parseMapSequence(servletConfig.getInitParameter(LANGUAGES_PARAM)))); + Map properties = CastUtils.cast(parseMapSequence( + servletConfig.getInitParameter(PROPERTIES_PARAM)), String.class, Object.class); + + //Custom impl to allow property values to be defined as system properties + for (Map.Entry entry : properties.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue().toString(); + + if (value.startsWith("{systemProperties")) { + int begin = value.indexOf("'"); + int end = value.lastIndexOf("'"); + String propertyKey = value.substring(begin + 1, end); + String systemPropValue = System.getProperty(propertyKey); + if (systemPropValue != null && !systemPropValue.isEmpty()) { + properties.put(key, systemPropValue); + } + } + } + + if (!properties.isEmpty()) { + bean.getProperties(true).putAll(properties); + } + } + + protected void setAllInterceptors(JAXRSServerFactoryBean bean, ServletConfig servletConfig, + String splitChar) throws ServletException { + + this.setInterceptors(bean, servletConfig, OUT_INTERCEPTORS_PARAM, splitChar); + this.setInterceptors(bean, servletConfig, OUT_FAULT_INTERCEPTORS_PARAM, splitChar); + this.setInterceptors(bean, servletConfig, IN_INTERCEPTORS_PARAM, splitChar); + } + + protected void setSchemasLocations(JAXRSServerFactoryBean bean, ServletConfig servletConfig) { + + String schemas = servletConfig.getInitParameter(SCHEMAS_PARAM); + if (schemas != null) { + String[] locations = schemas.split(" "); + List list = new ArrayList<>(); + + for (String loc : locations) { + String theLoc = loc.trim(); + if (!theLoc.isEmpty()) { + list.add(theLoc); + } + } + + if (!list.isEmpty()) { + bean.setSchemaLocations(list); + } + } + } + + protected void setDocLocation(JAXRSServerFactoryBean bean, ServletConfig servletConfig) { + + String docLocation = servletConfig.getInitParameter(DOC_LOCATION_PARAM); + if (docLocation != null) { + bean.setDocLocation(docLocation); + } + } + + protected void setInterceptors(JAXRSServerFactoryBean bean, ServletConfig servletConfig, String paramName, + String splitChar) throws ServletException { + + String initParameter = servletConfig.getInitParameter(paramName); + if (initParameter != null) { + String[] values = initParameter.split(splitChar); + List> interceptors = new ArrayList<>(); + + for (String interceptorVal : values) { + Map> props = new HashMap<>(); + String theValue = this.getClassNameAndProperties(interceptorVal, props); + if (!theValue.isEmpty()) { + try { + Class intClass = this.loadClass(theValue, "Interceptor"); + Object object = intClass.getDeclaredConstructor().newInstance(); + this.injectProperties(object, props); + interceptors.add((Interceptor) object); + } catch (ServletException exception) { + throw exception; + } catch (Exception exception) { + LOG.warning("Interceptor class " + theValue + " can not be created"); + throw new ServletException(exception); + } + } + } + + if (!interceptors.isEmpty()) { + if (OUT_INTERCEPTORS_PARAM.equals(paramName)) { + bean.setOutInterceptors(interceptors); + } else if (OUT_FAULT_INTERCEPTORS_PARAM.equals(paramName)) { + bean.setOutFaultInterceptors(interceptors); + } else { + bean.setInInterceptors(interceptors); + } + } + } + } + + protected void setInvoker(JAXRSServerFactoryBean bean, ServletConfig servletConfig) throws ServletException { + + String initParameter = servletConfig.getInitParameter(INVOKER_PARAM); + if (initParameter != null) { + Map> props = new HashMap<>(); + String classNameAndProperties = this.getClassNameAndProperties(initParameter, props); + if (!classNameAndProperties.isEmpty()) { + try { + Class intClass = this.loadClass(classNameAndProperties, "Invoker"); + Object object = intClass.getDeclaredConstructor().newInstance(); + this.injectProperties(object, props); + bean.setInvoker((Invoker) object); + } catch (ServletException exception) { + throw exception; + } catch (Exception exception) { + LOG.warning("Invoker class " + classNameAndProperties + " can not be created"); + throw new ServletException(exception); + } + } + } + } + + protected Map, Map>> getServiceClasses(ServletConfig servletConfig, + boolean modelAvailable, String splitChar) + throws ServletException { + + String serviceBeans = servletConfig.getInitParameter(SERVICE_CLASSES_PARAM); + if (serviceBeans == null) { + if (modelAvailable) { + return Collections.emptyMap(); + } else { + throw new ServletException("At least one resource class should be specified"); + } + } else { + String[] classNames = serviceBeans.split(splitChar); + Map, Map>> classMapHashMap = new HashMap<>(); + + for (String cName : classNames) { + Map> props = new HashMap<>(); + String theName = this.getClassNameAndProperties(cName, props); + if (!theName.isEmpty()) { + Class cls = this.loadClass(theName); + classMapHashMap.put(cls, props); + } + } + + if (classMapHashMap.isEmpty()) { + throw new ServletException("At least one resource class should be specified"); + } else { + return classMapHashMap; + } + } + } + + protected List getFeatures(ServletConfig servletConfig, String splitChar) + throws ServletException { + + String featuresList = servletConfig.getInitParameter(FEATURES_PARAM); + if (featuresList == null) { + return Collections.emptyList(); + } else { + String[] classNames = featuresList.split(splitChar); + List features = new ArrayList<>(); + + for (String cName : classNames) { + Map> props = new HashMap<>(); + String classNameAndProperties = this.getClassNameAndProperties(cName, props); + if (!classNameAndProperties.isEmpty()) { + Class cls = this.loadClass(classNameAndProperties); + if (Feature.class.isAssignableFrom(cls)) { + features.add((Feature) this.createSingletonInstance(cls, props, servletConfig)); + } + } + } + return features; + } + } + + protected List getProviders(ServletConfig servletConfig, String splitChar) throws ServletException { + + String providersList = servletConfig.getInitParameter(PROVIDERS_PARAM); + if (providersList == null) { + return Collections.emptyList(); + } else { + String[] classNames = providersList.split(splitChar); + List providers = new ArrayList<>(); + + for (String cName : classNames) { + Map> props = new HashMap<>(); + String theName = this.getClassNameAndProperties(cName, props); + if (!theName.isEmpty()) { + Class cls = this.loadClass(theName); + providers.add(this.createSingletonInstance(cls, props, servletConfig)); + } + } + return providers; + } + } + + private String getClassNameAndProperties(String cName, Map> props) { + + String className = cName.trim(); + int ind = className.indexOf(40); + if (ind != -1 && className.endsWith(")")) { + props.putAll(parseMapListSequence(className.substring(ind + 1, className.length() - 1))); + className = className.substring(0, ind).trim(); + } + + return className; + } + + protected static Map> parseMapListSequence(String sequence) { + + if (sequence != null) { + sequence = sequence.trim(); + Map> sequenceList = new HashMap<>(); + String[] pairs = sequence.split(" "); + + for (String pair : pairs) { + String thePair = pair.trim(); + if (!thePair.isEmpty()) { + String[] values = thePair.split("="); + String key; + String value; + if (values.length == 2) { + key = values[0].trim(); + value = values[1].trim(); + } else { + key = thePair; + value = ""; + } + + List keysList = sequenceList.computeIfAbsent(key, k -> new LinkedList<>()); + (keysList).add(value); + } + } + return sequenceList; + } else { + return Collections.emptyMap(); + } + } + + protected Map, ResourceProvider> getResourceProviders(ServletConfig servletConfig, Map, + Map>> resourceClasses) throws ServletException { + + String scope = servletConfig.getInitParameter(SERVICE_SCOPE_PARAM); + if (scope != null && !SERVICE_SCOPE_SINGLETON.equals(scope) && !SERVICE_SCOPE_REQUEST.equals(scope)) { + throw new ServletException("Only singleton and prototype scopes are supported"); + } else { + boolean isPrototype = SERVICE_SCOPE_REQUEST.equals(scope); + Map, ResourceProvider> providerHashMap = new HashMap<>(); + + for (Map.Entry, Map>> entry : resourceClasses.entrySet()) { + Class entryKey = entry.getKey(); + providerHashMap.put(entryKey, isPrototype ? new PerRequestResourceProvider(entryKey) : + new SingletonResourceProvider(this.createSingletonInstance(entryKey, entry.getValue(), + servletConfig), true)); + } + return providerHashMap; + } + } + + protected boolean isAppResourceLifecycleASingleton(Application app, ServletConfig servletConfig) { + + String scope = servletConfig.getInitParameter(SERVICE_SCOPE_PARAM); + if (scope == null) { + scope = (String) app.getProperties().get(SERVICE_SCOPE_PARAM); + } + return SERVICE_SCOPE_SINGLETON.equals(scope); + } + + protected Object createSingletonInstance(Class cls, Map> props, ServletConfig sc) + throws ServletException { + + Constructor resourceConstructor = ResourceUtils.findResourceConstructor(cls, false); + if (resourceConstructor == null) { + throw new ServletException("No valid constructor found for " + cls.getName()); + } else { + boolean isApplication = Application.class.isAssignableFrom(resourceConstructor.getDeclaringClass()); + + try { + ProviderInfo provider; + if (resourceConstructor.getParameterTypes().length == 0) { + if (isApplication) { + provider = new ApplicationInfo((Application) resourceConstructor.newInstance(), this.getBus()); + } else { + provider = new ProviderInfo<>(resourceConstructor.newInstance(), this.getBus(), + false, true); + } + } else { + Map, Object> values = new HashMap<>(); + values.put(ServletContext.class, sc.getServletContext()); + values.put(ServletConfig.class, sc); + provider = ProviderFactory.createProviderFromConstructor(resourceConstructor, values, + this.getBus(), isApplication, true); + } + + Object instance = provider.getProvider(); + this.injectProperties(instance, props); + return isApplication ? provider : instance; + } catch (InstantiationException exception) { + throw new ServletException("Resource class " + cls.getName() + " can not be instantiated"); + } catch (IllegalAccessException exception) { + throw new ServletException("Resource class " + cls.getName() + " " + + "can not be instantiated due to IllegalAccessException"); + } catch (InvocationTargetException exception) { + throw new ServletException("Resource class " + cls.getName() + " " + + "can not be instantiated due to InvocationTargetException"); + } + } + } + + private void injectProperties(Object instance, Map> props) { + + if (props != null && !props.isEmpty()) { + Method[] methods = instance.getClass().getMethods(); + Map methodsMap = new HashMap<>(); + + for (Method method : methods) { + methodsMap.put(method.getName(), method); + } + + for (Map.Entry> entry : props.entrySet()) { + Method method = methodsMap.get("set" + StringUtils.capitalize(entry.getKey())); + if (method != null) { + Class type = method.getParameterTypes()[0]; + Object value; + if (InjectionUtils.isPrimitive(type)) { + value = PrimitiveUtils.read((String) ((List) entry.getValue()).get(0), type); + } else { + value = entry.getValue(); + } + InjectionUtils.injectThroughMethod(instance, method, value); + } + } + } + } + + protected void createServerFromApplication(String applicationNames, ServletConfig servletConfig) + throws ServletException { + + boolean ignoreApplicationPath = this.isIgnoreApplicationPath(servletConfig); + String[] classNames = applicationNames.split(this.getParameterSplitChar(servletConfig)); + if (classNames.length > 1 && ignoreApplicationPath) { + throw new ServletException("\"jaxrs.application.address.ignore\" parameter must be set to false for " + + "multiple Applications be supported"); + } else { + for (String cName : classNames) { + ApplicationInfo providerApp = this.createApplicationInfo(cName, servletConfig); + Application app = providerApp.getProvider(); + JAXRSServerFactoryBean bean = ResourceUtils.createApplication(app, ignoreApplicationPath, + this.getStaticSubResolutionValue(servletConfig), this.isAppResourceLifecycleASingleton(app, + servletConfig), this.getBus()); + String splitChar = this.getParameterSplitChar(servletConfig); + this.setAllInterceptors(bean, servletConfig, splitChar); + this.setInvoker(bean, servletConfig); + this.setExtensions(bean, servletConfig); + this.setDocLocation(bean, servletConfig); + this.setSchemasLocations(bean, servletConfig); + List providers = this.getProviders(servletConfig, splitChar); + bean.setProviders(providers); + List features = this.getFeatures(servletConfig, splitChar); + bean.getFeatures().addAll(features); + bean.setBus(this.getBus()); + bean.setApplicationInfo(providerApp); + bean.create(); + } + } + } + + protected boolean isIgnoreApplicationPath(ServletConfig servletConfig) { + + String ignoreParam = servletConfig.getInitParameter(IGNORE_APP_PATH_PARAM); + return ignoreParam == null || PropertyUtils.isTrue(ignoreParam); + } + + protected void createServerFromApplication(ServletConfig servletConfig) throws ServletException { + + Application app = this.getApplication(); + JAXRSServerFactoryBean bean = ResourceUtils.createApplication(app, this.isIgnoreApplicationPath(servletConfig), + this.getStaticSubResolutionValue(servletConfig), + this.isAppResourceLifecycleASingleton(app, servletConfig), this.getBus()); + String splitChar = this.getParameterSplitChar(servletConfig); + this.setAllInterceptors(bean, servletConfig, splitChar); + this.setInvoker(bean, servletConfig); + this.setExtensions(bean, servletConfig); + this.setDocLocation(bean, servletConfig); + this.setSchemasLocations(bean, servletConfig); + List providers = this.getProviders(servletConfig, splitChar); + bean.setProviders(providers); + List features = this.getFeatures(servletConfig, splitChar); + bean.getFeatures().addAll(features); + bean.setBus(this.getBus()); + bean.setApplication(this.getApplication()); + bean.create(); + } + + protected ApplicationInfo createApplicationInfo(String appClassName, ServletConfig servletConfig) + throws ServletException { + + Map> props = new HashMap<>(); + appClassName = this.getClassNameAndProperties(appClassName, props); + Class appClass = this.loadApplicationClass(appClassName); + ApplicationInfo appInfo = (ApplicationInfo) this.createSingletonInstance(appClass, props, servletConfig); + Map servletProps = new HashMap<>(); + ServletContext servletContext = servletConfig.getServletContext(); + Enumeration names = servletContext.getInitParameterNames(); + + String name; + while (names.hasMoreElements()) { + name = names.nextElement(); + servletProps.put(name, servletContext.getInitParameter(name)); + } + + names = servletConfig.getInitParameterNames(); + + while (names.hasMoreElements()) { + name = names.nextElement(); + servletProps.put(name, servletConfig.getInitParameter(name)); + } + + appInfo.setOverridingProps(servletProps); + return appInfo; + } + + protected Class loadApplicationClass(String appClassName) throws ServletException { + + return this.loadClass(appClassName, "Application"); + } + + protected Class loadClass(String cName) throws ServletException { + + return this.loadClass(cName, "Resource"); + } + + protected Class loadClass(String cName, String classType) throws ServletException { + + try { + Class aClass; + if (this.classLoader == null) { + aClass = ClassLoaderUtils.loadClass(cName, org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet.class); + } else { + aClass = this.classLoader.loadClass(cName); + } + + return aClass; + } catch (ClassNotFoundException exception) { + throw new ServletException("No " + classType + " class " + cName.trim() + " can be found", exception); + } + } + + public void setClassLoader(ClassLoader loader) { + + this.classLoader = loader; + } + + protected Application getApplication() { + + return this.application; + } + + private static class ApplicationImpl extends Application { + + private final Set applicationSingletons; + + ApplicationImpl(Set applicationSingletons) { + + this.applicationSingletons = applicationSingletons; + } + + @Override + public Set getSingletons() { + + return this.applicationSingletons; + } + } +} diff --git a/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/src/main/java/org/wso2/carbon/identity/servlet/mgt/CustomJacksonJsonProvider.java b/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/src/main/java/org/wso2/carbon/identity/servlet/mgt/CustomJacksonJsonProvider.java new file mode 100644 index 000000000000..62243ac61224 --- /dev/null +++ b/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/src/main/java/org/wso2/carbon/identity/servlet/mgt/CustomJacksonJsonProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 org.wso2.carbon.identity.servlet.mgt; + +import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; + +/** + * This class extends JacksonJsonProvider to set the serialization inclusion to NON_NULL. + */ +public class CustomJacksonJsonProvider extends JacksonJsonProvider { + + public CustomJacksonJsonProvider() { + + super(new NonNullSerializationMapper()); + } +} diff --git a/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/src/main/java/org/wso2/carbon/identity/servlet/mgt/NonNullSerializationMapper.java b/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/src/main/java/org/wso2/carbon/identity/servlet/mgt/NonNullSerializationMapper.java new file mode 100644 index 000000000000..19ba619cdf94 --- /dev/null +++ b/components/servlet-mgt/org.wso2.carbon.identity.servlet.mgt/src/main/java/org/wso2/carbon/identity/servlet/mgt/NonNullSerializationMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you 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 org.wso2.carbon.identity.servlet.mgt; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * This class extends ObjectMapper to set the serialization inclusion to NON_NULL. + */ +public class NonNullSerializationMapper extends ObjectMapper { + + // Add serial version UID to the class. + private static final long serialVersionUID = 1L; + + public NonNullSerializationMapper() { + + super(); + this.setSerializationInclusion(JsonInclude.Include.NON_NULL); + } +} diff --git a/components/servlet-mgt/pom.xml b/components/servlet-mgt/pom.xml new file mode 100644 index 000000000000..8358caaf1d64 --- /dev/null +++ b/components/servlet-mgt/pom.xml @@ -0,0 +1,42 @@ + + + + + org.wso2.carbon.identity.framework + identity-framework + 7.7.124-SNAPSHOT + ../../pom.xml + + + 4.0.0 + servlet-mgt + pom + WSO2 Carbon - Servlet Management + + This is a Carbon bundle that represent the Servlet Management Module. + + http://wso2.org + + + org.wso2.carbon.identity.servlet.mgt + + + diff --git a/features/servlet-mgt/org.wso2.carbon.identity.servlet.mgt.server.feature/pom.xml b/features/servlet-mgt/org.wso2.carbon.identity.servlet.mgt.server.feature/pom.xml new file mode 100644 index 000000000000..8a2bdce1d7e6 --- /dev/null +++ b/features/servlet-mgt/org.wso2.carbon.identity.servlet.mgt.server.feature/pom.xml @@ -0,0 +1,140 @@ + + + + + + org.wso2.carbon.identity.framework + servlet-management-feature + 7.7.124-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.wso2.carbon.identity.servlet.mgt.server.feature + pom + Servlet Management Server Feature + http://wso2.org + This feature contains the core bundles required for Servlet Management Framework + + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.servlet.mgt + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + copy + package + + copy + + + + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.servlet.mgt + ${project.version} + jar + true + ${basedir}/src/main/resources/runtimes/cxf3 + + + + + + + + org.wso2.maven + carbon-p2-plugin + ${carbon.p2.plugin.version} + + + 4-p2-feature-generation + package + + p2-feature-gen + + + org.wso2.carbon.identity.servlet.mgt.server + ../../etc/feature.properties + + + org.wso2.carbon.p2.category.type:server + + + + + + + + maven-resources-plugin + + + copy-resources + generate-resources + + copy-resources + + + src/main/resources + + + resources + + p2.inf + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.1 + + + clean_target + install + + + + + + + + + run + + + + + + + + diff --git a/features/servlet-mgt/org.wso2.carbon.identity.servlet.mgt.server.feature/resources/p2.inf b/features/servlet-mgt/org.wso2.carbon.identity.servlet.mgt.server.feature/resources/p2.inf new file mode 100644 index 000000000000..14cbb6d66784 --- /dev/null +++ b/features/servlet-mgt/org.wso2.carbon.identity.servlet.mgt.server.feature/resources/p2.inf @@ -0,0 +1,5 @@ +instructions.configure = \ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../lib/); \ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../lib/runtimes/); \ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../../lib/runtimes/cxf3/); \ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.identity.servlet.mgt.server_${feature.version}/runtimes/cxf3/,target:${installFolder}/../../../lib/runtimes/cxf3/,overwrite:true);\ diff --git a/features/servlet-mgt/pom.xml b/features/servlet-mgt/pom.xml new file mode 100644 index 000000000000..288b41fd16bc --- /dev/null +++ b/features/servlet-mgt/pom.xml @@ -0,0 +1,38 @@ + + + + + + org.wso2.carbon.identity.framework + identity-framework + 7.7.124-SNAPSHOT + ../../pom.xml + + + 4.0.0 + servlet-management-feature + pom + WSO2 Carbon - Servlet Management Feature + http://wso2.org + + + org.wso2.carbon.identity.servlet.mgt.server.feature + + + diff --git a/pom.xml b/pom.xml index 379dffdcec9a..bc0cd3c4de13 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,7 @@ components/action-mgt components/ai-services-mgt components/certificate-mgt + components/servlet-mgt features/extension-mgt components/consent-server-configs-mgt features/security-mgt @@ -112,6 +113,7 @@ features/action-mgt features/ai-services-mgt features/certificate-mgt + features/servlet-mgt @@ -1710,6 +1712,11 @@ cxf-rt-rs-service-description ${apache.cxf-rt-rs-service-description.version} + + org.apache.cxf + cxf-rt-frontend-jaxrs + ${apache.cxf-rt-frontend-jaxrs.version} + org.codehaus.jettison jettison @@ -1735,6 +1742,11 @@ org.wso2.carbon.identity.ai.service.mgt ${project.version} + + org.wso2.carbon.identity.framework + org.wso2.carbon.identity.servlet.mgt + ${project.version} + org.wso2.orbit.org.apache.commons commons-compress @@ -1825,7 +1837,7 @@ 9.0.11 - 2.7.16 + 3.6.4 3.3.7 1.4.0 5.1.1.RELEASE