History
I am generating Java classes from a schema not under my control. I am exporting it to a jar that is a dependency for a web service project. This works correctly. I attempted to add a custom binding to marshal and unmarshal xs:date and xs:dateTime as instances of org.joda.DateTime instead of XMLGregorianCalendar. I am able to generate the jar, compile it within the webservice, and deploy without issue. Once deployed however, the XML requests that previously worked, now throw the following exception:
unexpected element (uri:"", local:"ElementName"). Expected elements are <{schemaNamespace}ElementName>
This exception does not occur at the root element. Several earlier instances of xs:date and xs:dateTime are parsed without issue, the exception first appears on a complex type that contains an xs:date.
Here's an example of the XML structure:
<soap:Envelope xmlns:ns="webServiceNamespace">
<soap:Body>
<request>
<root>
<child1>
<Date /> <--Works fine.
<childList>
<childListElement> <-- Exception thrown on this element.
<Date />
</childListElement>
</childList>
</child1>
</root>
</request>
</soap:Body>
</soap:Envelope>
The webServiceNamespace and schemaNamespace are different, but previously only the one was required in the soap envelope.
Question
Why, after adding a custom binding, is the parser asking me to put a namespace on only a handful of parent elements who have a child affected by the custom binding ?
BindingAdapter
package myPackage;
#XmlTransient
public class JodaDateTimeAdapter extends XmlAdapter<String, DateTime> {
private static final DateTimeFormatter XML_DATE_FORMAT = ISODateTimeFormat.dateTimeNoMillis();
#Override
public DateTime unmarshal(String date) throws Exception {
return XML_DATE_FORMAT.parseDateTime(date);
}
#Override
public String marshal(DateTime dateTime) throws Exception {
return XML_DATE_FORMAT.print(dateTime);
}
}
Binding.xjb
<?xml version="1.0" encoding="UTF-8"?>
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
jaxb:extensionBindingPrefixes="xjc"
version="2.1" >
<jaxb:globalBindings>
<xjc:javaType name="org.joda.time.DateTime" xmlType="xs:dateTime" adapter="myPackage.JodaDateTimeAdapter" />
<xjc:javaType name="org.joda.time.DateTime" xmlType="xs:date" adapter="myPackage.JodaDateAdapter" />
</jaxb:globalBindings>
</jaxb:bindings>
POM.xml(relevant bits anyway)
<project>
...
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>1.6</version>
<executions>
...
<execution>
<id>generateSchema</id>
<goals>
<goal>xjc</goal>
</goals>
<configuration>
<packageName>myPackage</packageName>
...
<extension>true</extension>
<arguments>-no-header -Xxew</arguments>
<bindingDirectory>src/main/bindings</bindingDirectory>
<bindingFiles>JodaBinding.xjb</bindingFiles>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.github.jaxb-xew-plugin</groupId>
<artifactId>jaxb-xew-plugin</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
Comments
I want to stress that this works perfectly fine without the binding, as far as I can tell the generated class files are identical (except for the extra annotations specific to wrappers). The xml used in the SOAP request is valid for the WSDL generated by the web service, and the WSDL is the same whether or not I use a custom binding. Only elements who have are a member of a collection and have a child with a date require the schema namespace. Lastly, once I provide the namespaces in my request, the response puts the request object in the webServiceNamespace and all the children other than the schema-defined root in the schemaNamespace. Previously the entire response was in the webServiceNamespace.
Clearly the addition of the custom binding is doing something wonky (technical term) with the namespace resolution, but I'm not well versed enough in the topic to make any headway.
Suggestions ?
Update: Removing the XEW plugin for pretty-collections generation did not affect this issue.
Update: This is an example of a class containing a date before the binding and after. This is the only property to change:
Before
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "QuestionAnswerType", propOrder = {
"question",
"answer",
"questionDate"
})
public class QuestionAnswerType {
#XmlElement(name = "Question")
protected String question;
#XmlElement(name = "Answer")
protected String answer;
#XmlElement(name = "QuestionDate")
#XmlSchemaType(name = "date")
protected XMLGregorianCalendar questionDate;
...
After
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "QuestionAnswerType", propOrder = {
"question",
"answer",
"questionDate"
})
public class QuestionAnswerType {
#XmlElement(name = "Question")
protected String question;
#XmlElement(name = "Answer")
protected String answer;
#XmlElement(name = "QuestionDate", type = String.class)
#XmlJavaTypeAdapter(JodaDateAdapter.class)
#XmlSchemaType(name = "date")
protected DateTime questionDate;
...
Update: This works without issue if I execute the build against a schema with no namespace. Obviously not the fix, but a workaround for now.
Related
Trying to re-partition my dataframe in order to achieve parallelism. It was suggested to each partition size should be less than 128MB , in-order to achieve it I need to calculate how much the size of each row in my dataframe. So how to calculate/find how much each row size in my dataframe?
Thank you.
As discussed in the link that I have mentionned in my first comment, you can use java.lang.instrument
The solution that I propose is in Java, Maven and with Spark 2.4.0
You must have the following structure, otherwise you will have to adapt your pom.xml to your structure:
src
--main
----java
------size
--------Sizeof.java
------spark
--------SparkJavaTest.java
----resources
------META-INF
--------MANIFEST.MF
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.formation.SizeOf</groupId>
<artifactId>SizeOf</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifestFile>
src/main/resources/META-INF/MANIFEST.MF
</manifestFile>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>
spark.SparkJavaTest
</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>2.4.0</version>
</dependency>
</dependencies>
</project>
Sizeof
package size;
import java.lang.instrument.Instrumentation;
final public class Sizeof {
private static Instrumentation instrumentation;
public static void premain(String args, Instrumentation inst) {
instrumentation = inst;
}
public static long sizeof(Object o) {
return instrumentation.getObjectSize(o);
}
}
SparkJavaTest
package spark;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import size.Sizeof;
public class SparkJavaTest {
public static SparkSession spark = SparkSession
.builder()
.appName("JavaSparkTest")
.master("local")
.getOrCreate();
public static void main(String[] args) {
Dataset<Row> ds = spark.read().option("header",true).csv("sample.csv");
ds.show(false);
// Get the size of a Dataset
System.out.println("size of ds " + Sizeof.sizeof(ds));
JavaRDD dsToJavaRDD = ds.toJavaRDD();
// Get the size of a JavaRDD
System.out.println("size of rdd" + Sizeof.sizeof(dsToJavaRDD));
}
}
MANIFEST.MF
Manifest-Version: 1.0
Premain-Class: size.Sizeof
Main-Class: spark.SparkJavaTest
After that, you clean and package :
mvn clean package
Then you can run and get the size of your objects:
java -javaagent:target/SizeOf-1.0-SNAPSHOT-jar-with-dependencies.jar -jar target/SizeOf-1.0-SNAPSHOT-jar-with-dependencies.jar
I would like to use some XML schema files from Kotlin enum classes.
When I generate classes from an xml schema, this is the output:
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for LogLevelType.
*
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* <simpleType name="LogLevelType">
* <restriction base="{http://www.w3.org/2001/XMLSchema}string">
* <enumeration value="ERROR"/>
* <enumeration value="WARNING"/>
* <enumeration value="INFO"/>
* <enumeration value="DEBUG"/>
* </restriction>
* </simpleType>
* </pre>
*
*/
#XmlType(name = "LogLevelType", namespace = "http://www.brabantia.com/XMLSchema/Logging")
#XmlEnum
public enum LogLevelType {
ERROR,
WARNING,
INFO,
DEBUG;
public String value() {
return name();
}
public static LogLevelType fromValue(String v) {
return valueOf(v);
}
}
So I figured that when I would create an enum class and add the same annotations, I would be able to do the same in reverse.
import javax.xml.bind.annotation.XmlEnum
import javax.xml.bind.annotation.XmlType
#XmlType(name = "ErrorCategoryType", namespace = "http://www.brabantia.com/XMLSchema/Enum")
#XmlEnum
enum class ErrorCategoryType {
FUNCTIONAL,
TECHNICAL;
fun value(): String = name
fun fromValue(v: String) = valueOf(v)
}
In my pom file I have the following configuration:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>2.3.1</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/generated-sources/jaxb</outputDirectory>
<!-- The package of your generated sources -->
<packageName>com.brabantia.common.schema</packageName>
<sources>
<source>${project.basedir}/src/main/resources/BrabantiaMain.xsd</source>
</sources>
</configuration>
</execution>
<execution>
<id>schemagen</id>
<goals>
<goal>schemagen</goal>
</goals>
<configuration>
<outputDirectory>${project.basedir}/src/main/resources/Generated</outputDirectory>
<sources>
<source>${project.basedir}/src/main/kotlin/com/brabantia/common/xmlschemas/enums</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
The Java classes are generated using xjc, but when I try to generate the xml schemas using schemagen, the following error is displayed:
Failed to execute goal org.codehaus.mojo:jaxb2-maven-plugin:2.3.1:schemagen (default-cli) on project xml-schemas: Execution default-cli of goal org.codehaus.mojo:jaxb2-maven-plugin:2.3.1:schemagen failed: syntax error #[1,41] in file:/C:/projects/INTEGRATION_FACTORY/XML_SCHEMAS/src/main/kotlin/com/brabantia/common/xmlschemas/enum/ErrorCategoryType.kt -> [Help 1]
How can I generate the XML schemas from the Kotlin enum classes?
I have the following XDS that represents various metadata item types:
<xs:simpleType name="BooleanTypeKey">
<xs:restriction base="xs:string">
<xs:enumeration value="isValue1" />
<xs:enumeration value="isValue2" />
<xs:enumeration value="isValue3" />
</xs:restriction>
</xs:simpleType>
<xs:complexType name="BooleanType">
<xs:simpleContent>
<xs:extension base="xs:boolean">
<xs:attribute name="key" type="BooleanTypeKey" use="required" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>
Using the following xjc to produce my classes:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>myId</id>
<goals>
<goal>xjc</goal>
</goals>
<configuration>
<packageName>com.myorg.packagename.model</packageName>
<schemaDirectory>/src/main/resources/xsd</schemaDirectory>
<bindingDirectory>/src/main/resources/xjb</bindingDirectory>
<outputDirectory>${project.build.directory}/generated/jaxb</outputDirectory>
<extension>true</extension>
<enableIntrospection>true</enableIntrospection>
<arguments>-Xequals -xhasCode -xtoString</arguments>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics</artifactId>
<version>0.6.4</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
This produces the following class for the BooleanType object:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "BooleanType", propOrder = {
"value"
})
public class BooleanType implements Equals, HashCode, ToString
{
#XmlValue
protected Boolean value;
#XmlAttribute(name = "key", required = true)
protected BooleanTypeKey key;
//... getters and setters ...
public boolean equals(ObjectLocator thisLocator, ObjectLocator thatLocator, Object object, EqualsStrategy strategy) {
//... basic object checks ...
final BooleanType that = ((BooleanType) object);
{
// This is NOT part of the generated code
// Why does the next 2 lines look like they do??? It should only read:
// boolean lhsValue = this.isValue();
// The fact that there is a check for *true*
// (constant value check... really???)
// Not only is this completely useless, but it causes dead code production (the else part (false)).
boolean lhsValue;
lhsValue = (true?this.isValue():false);
boolean rhsValue;
rhsValue = (true?that.isValue():false);
//...more code ...
}
//...more code ...
}
}
Can someone tell me if I can avoid producing such code, I am using a faulty version of xjc? A version that is too old?
Thanks,
i'm using xjc:superClass and an #javax.xml.bind.annotation.XmlTransient annotation on the super class.
i'm trying to switch over to Moxy and I get this error:
'Exception Description: Property [aspectStyleBlock] in class [com.aplia.q4.document.ValueStyle] references a class [com.aplia.q4.domain.AbstractBlock] that is marked transient, which is not allowed.
- with linked exception:
[Exception [EclipseLink-50057] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.JAXBException
' .
This all works fine under jaxb 2.1
If I remove the XmlTransient annotation on the superclass, I instead get this error:
'ava.lang.RuntimeException: javax.xml.bind.JAXBException
- with linked exception:
[java.lang.NullPointerException]
....
'Caused by: javax.xml.bind.JAXBException
- with linked exception:
[java.lang.NullPointerException]
at org.eclipse.persistence.jaxb.JAXBContext$TypeMappingInfoInput.createContextState(JAXBContext.java:825)
at org.eclipse.persistence.jaxb.JAXBContext.<init>(JAXBContext.java:136)
at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:142)
at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:129)
at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:93)
at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:83)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:202)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:331)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:522)
at com.aplia.q4.service.hosting.impl.JAXBAdapter.<init>(JAXBAdapter.java:35)
... 27 more
Caused by: java.lang.NullPointerException
at org.eclipse.persistence.jaxb.compiler.MappingsGenerator.generateChoiceCollectionMapping(MappingsGenerator.java:686)
at org.eclipse.persistence.jaxb.compiler.MappingsGenerator.generateMapping(MappingsGenerator.java:434)
at org.eclipse.persistence.jaxb.compiler.MappingsGenerator.generateMappings(MappingsGenerator.java:1996)
at org.eclipse.persistence.jaxb.compiler.MappingsGenerator.generateMappings(MappingsGenerator.java:1957)
at org.eclipse.persistence.jaxb.compiler.MappingsGenerator.generateProject(MappingsGenerator.java:193)
at org.eclipse.persistence.jaxb.compiler.Generator.generateProject(Generator.java:174)
at org.eclipse.persistence.jaxb.JAXBContext$TypeMappingInfoInput.createContextState(JAXBContext.java:830)
at org.eclipse.persistence.jaxb.JAXBContext$TypeMappingInfoInput.createContextState(JAXBContext.java:823)
... 41 more'
I have about 100 classes generated from schema (I don't control the schema). I tried to use http://jaxb2-commons.dev.java.net/basic/inheritance to implement an interface instead of xjc:superclass, but it will require me to list out all of my 100 classes, since bindings xpath can't match on multiple nodes. Reluctant to do so unless it is the only way to fix the problem.
This is blocking my efforts to convert to Moxy impl.
Details about my setup & why i chose to add the XmlTransient annotation:
from https://sites.google.com/site/codingkb/java-2/jaxb/jaxb-4
" causes #XmlValue is not allowed on a class that derives another class
You'd like your autogenerated JAXB classes to extend a common parent class... So you add something like this to your XSD:
Unfortunately, when you go to compile, you get an error message:
Exception in thread "main" com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
#XmlValue is not allowed on a class that derives another class.
The solution is to add an annotation to the base class:
#javax.xml.bind.annotation.XmlTransient
public class JAXBSuperClass
{
...
}"
My pom file (relevant parts only):
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.1.13</version>
<scope>runtime</scope>
</dependency>
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>0.7.4</version>
<executions>
<execution>
<id>generate-q4-document-jaxb</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<extension>true</extension>
<args>
<arg>-Xboolean-getter</arg>
<arg>-Xinheritance</arg>
</args>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics</artifactId>
<version>0.5.3</version>
</plugin>
<plugin>
<groupId>com.nebulent.xjc</groupId>
<artifactId>boolean-getter</artifactId>
<version>1.0</version>
</plugin>
</plugins>
<schemaDirectory>${project.build.directory}/generated-sources/schema</schemaDirectory>
<bindingDirectory>${basedir}/src/main/resources/jaxb-bindings</bindingDirectory>
<forceRegenerate>true</forceRegenerate>
</configuration>
</execution>
</executions>
</plugin>
let me know if you need a schema, or any other info.
thanks in advance!
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
There are a couple issues related to your question:
Issue #1 - MOXy exception
Could you provide a small sample that demonstrates the issue that you are seeing?
UPDATE 1: I believe the issue that you are seeing is related to the following EclipseLink bug:
http://bugs.eclipse.org/378901
UPDATE 2: We have fixed the bug in both the EclipseLink 2.3.3 (available May 10 download) and 2.4.0 (available May 11 download) streams. You download these from the following location:
http://www.eclipse.org/eclipselink/downloads/nightly.php
Issue #2 - #XmlValue on a Subclass
The JAXB RI can not handle this use case:
Exception in thread "main" com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
#XmlValue is not allowed on a class that derives another class.
this problem is related to the following location:
at public java.lang.String forum10437666.Child.getValue()
at forum10437666.Child
at com.sun.xml.bind.v2.runtime.IllegalAnnotationsException$Builder.check(IllegalAnnotationsException.java:102)
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:472)
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:302)
at com.sun.xml.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1140)
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:154)
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:121)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:202)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:363)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:522)
at forum10437666.ValueDemo.main(ValueDemo.java:11)
EclipseLink JAXB (MOXy) can as long as the parent class does not map any of its fields/properties to XML elements:
Parent
The parent class only contains mappings to XML attributes.
package forum10437666;
import javax.xml.bind.annotation.XmlAttribute;
public class Parent {
private int id;
#XmlAttribute
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
Child
Child extends Parent and has a property mapped with #XmlValue.
package forum10437666;
import javax.xml.bind.annotation.XmlValue;
public class Child extends Parent {
private String value;
#XmlValue
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
ValueDemo
package forum10437666;
import javax.xml.bind.*;
import javax.xml.namespace.QName;
public class ValueDemo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Child.class);
Child child = new Child();
child.setId(123);
child.setValue("ABC");
JAXBElement<Child> je = new JAXBElement<Child>(new QName("child"), Child.class, child);
Marshaller marshaller = jc.createMarshaller();
marshaller.marshal(je, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8"?>
<child xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="123">ABC</child>
I'm trying to get a Custom Security Realm in Glassfish working (i tried 3.0.1 final and 3.1 B33). I read nearly all tutorials about this, but it doesn't not work on my System. I'm getting the error
Login failed: javax.security.auth.login.LoginException: unable to find LoginModule class: com.company.security.realm.CustomLoginModule
when trying to login.
Here is what i did:
I created a little Maven project, which contains the needed Realm class, CustomRealm, and the corresponding LoginModule, CustomLoginModule.
My pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>security.realm</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Custom JDBCRealm</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.glassfish.security</groupId>
<artifactId>security</artifactId>
<version>3.1-b33</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<optimise>true</optimise>
<debug>true</debug>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
My Custom Realm class:
package com.company.security.realm;
import com.sun.appserv.security.AppservRealm;
import com.sun.enterprise.security.auth.realm.BadRealmException;
import com.sun.enterprise.security.auth.realm.InvalidOperationException;
import com.sun.enterprise.security.auth.realm.NoSuchRealmException;
import com.sun.enterprise.security.auth.realm.NoSuchUserException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;
public class CustomRealm extends AppservRealm
{
Vector<String> groups = new Vector<String>();
private String jaasCtxName;
private String startWith;
#Override
public void init(Properties properties)
throws BadRealmException, NoSuchRealmException {
jaasCtxName = properties.getProperty("jaas-context", "customRealm");
startWith = properties.getProperty("startWith", "z");
groups.add("dummy");
}
#Override
public String getAuthType()
{
return "Custom Realm";
}
public String[] authenticate(String username, char[] password)
{
// if (isValidLogin(username, password))
return (String[]) groups.toArray();
}
#Override
public Enumeration getGroupNames(String username)
throws InvalidOperationException, NoSuchUserException
{
return groups.elements();
}
#Override
public String getJAASContext()
{
return jaasCtxName;
}
public String getStartWith()
{
return startWith;
}
}
My LoginModule class:
package com.company.security.realm;
import com.sun.appserv.security.AppservPasswordLoginModule;
import com.sun.enterprise.security.auth.login.common.LoginException;
import java.util.Set;
import org.glassfish.security.common.PrincipalImpl;
public class CustomLoginModule extends AppservPasswordLoginModule
{
#Override
protected void authenticateUser() throws LoginException
{
_logger.info("CustomRealm : authenticateUser for " + _username);
final CustomRealm realm = (CustomRealm)_currentRealm;
if ( (_username == null) || (_username.length() == 0) || !_username.startsWith(realm.getStartWith()))
throw new LoginException("Invalid credentials");
String[] grpList = realm.authenticate(_username, getPasswordChar());
if (grpList == null) {
throw new LoginException("User not in groups");
}
_logger.info("CustomRealm : authenticateUser for " + _username);
Set principals = _subject.getPrincipals();
principals.add(new PrincipalImpl(_username));
this.commitUserAuthentication(grpList);
}
}
I compiled this Maven project and copyied the resulting JAR-file to the Glassfish/lib directory. Then i added the Security Realm "customRealm" to my Glassfish with asadmin:
asadmin create-auth-realm
--classname com.company.security.realm.CustomRealm
--property jaas-context=customRealm:startWith=a customRealm
I also referenced the LoginModule class for the JAAS context of my Custom Realm, therefore i inserted this into the login.conf of my domain:
customRealm {
com.company.security.realm.CustomLoginModule required;
};
Although this LoginModule SHOULD BE on the Glassfish classpath, as it's classfile is packaged in the JAR that i put into the Glassfish/lib-dir, it cannot be found when i try to login. For login, i build a simple JSF-project, which calls the HttpServletRequest-login-method of Servlet 3.0.
When trying to login i'm getting the following Exception:
2010-12-24T14:41:31.613+0100|WARNING|glassfish3.0.1|
javax.enterprise.system.container.web.com.sun.web.security|_ThreadID=25;
_ThreadName=Thread-1;|Web login failed: Login failed:
javax.security.auth.login.LoginException: unable to find LoginModule class:
com.company.security.realm.CustomLoginModule
Anybody got an idea what i can do that Glassfish loads the LoginModule-class?
Got it. Seems like newer Glassfish versions require that the Security Realm and the LoginModule are packaged as an OSGi module, which should then be copied into glassfish/modules.
Therefore i changed my pom.xml to create an OSGi bundle, which contains both the CustomRealm and the CustomLoginModule.
Here it is:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>security.realm</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<name>Custom JDBCRealm OSGi</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.glassfish.security</groupId>
<artifactId>security</artifactId>
<version>3.1-b33</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<optimise>true</optimise>
<debug>true</debug>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Export-Package>
${project.groupId}.${project.artifactId};version=${project.version}
</Export-Package>
<Import-Package>
com.sun.appserv.security,
org.glassfish.security.common,
com.sun.enterprise.security.auth.realm,
com.sun.enterprise.security.auth.login.common,
java.util,
javax.security.auth
</Import-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
EDIT
Found a good additional resource here:
http://blogs.oracle.com/nithya/entry/modularized_osgi_custom_realms_in
, where the Realm and it's LoginModule is build as a hk2-jar.
This was driving me crazy, I've finally cracked creating a custom realm in glassfish 3.1, turns out that of course its really easy. The following documentation is the key: http://docs.oracle.com/cd/E18930_01/html/821-2418/beabo.html ... note that it has changed somewhat from the answers above.