JAX-WS custom object received in request is null - jaxb

I've used JAX-WS before, but have not passed a custom object as a parameter before. I'm using GlassFish 3.1, NetBeans 7.3 and created services through NetBeans JAX-WS wizard.
My problem is when custom object (Criteria) passed to service is received as null on the server. I can pass default types like int successfully.
#WebService(serviceName = "ECLService")
#Stateless()
public class ECLService {
#EJB
PersistenceImpl persistence;
#WebMethod(operationName = "listRevisions")
public List<Revision> listRevisions(#WebParam(name="criteria")Criteria criteria) {
System.out.println("criteria is "+(criteria ==null ? "null":" not null"));
List<Revision> revisions = persistence.getRevisions(criteria);
return revisions;
}
}
Criteria.java
#XmlRootElement
public class Criteria implements Serializable {
private static final long serialVersionUID = 1L;
public static final String LIST_TYPE = "criteria.key.listtype";
public static final String TYPE_ALL = "criteria.value.all";
public static final String TYPE_ERROR = "criteria.value.error";
public static final String TYPE_ARCHIVE = "criteria.value.archive";
public static final String TYPE_APPROVAL = "criteria.value.approval";
private Map<String, String> parameters;
public Map<String, String> getParameters() {
return parameters;
}
public String getParameter(String key) {
if (parameters==null || key==null) {
return null;
} else {
return parameters.get(key);
}
}
public void setParameters(Map<String, String> parameters) {
this.parameters = parameters;
}
public void setParameter(String key, String value) {
if (parameters==null) {
parameters = new HashMap<String,String>();
}
parameters.put(key, value);
}
public void setType(String type) {
setParameter(LIST_TYPE, type);
}
public String getType() {
return getParameter(LIST_TYPE);
}
#Override
public int hashCode() {
int hash = 7;
hash = 43 * hash + (this.parameters != null ? this.parameters.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Criteria other = (Criteria) obj;
if (this.parameters != other.parameters && (this.parameters == null || !this.parameters.equals(other.parameters))) {
return false;
}
return true;
}
}
Is there anything I'm missing like annotation or something?
Message sent looked like this:
<?xml version="1.0" ?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body><ns2:listRevisions xmlns:ns2="http://webservice.ecl.abc.com/"><ns2:criteria>
<type>TYPE_ALL</type></ns2:criteria></ns2:listRevisions></S:Body></S:Envelope>
HTTP/1.1 200 OK
server: grizzly/1.9.50
Content-Type: text/xml;charset=utf-8
Transfer-Encoding: chunked
Date: Fri, 17 May 2013 07:37:15 GMT
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body>
<ns2:listRevisionsResponse xmlns:ns2="http://webservice.ecl.abc.com/"/></S:Body>
</S:Envelope>

Try adding targetNamespace to WebParam annotation, with the namespace of the schema.
I faced the same issue. The root element was defaulted to the service namespace instead of schema namespace. I added targetNamespace to WebParam and that helped resolve this issue.
#WebResult(name="status")
public String send(#WebParam(name="event",targetNamespace="http://efw/event/schema") EventType event);

getParameter/setParameter methods does not follow JavaBean convension. If you don't want not follow this convension use #XmlAccessorType(XmlAccessType.FIELD) at the class level - JAXB would look at fields, not methods.
getParameter/setParameters work with Map type, it can be done, but with some additional work (JAXB java.util.Map binding)
getType/setType are the only "proper" methods pair, so they are properly treated by JAXB

I can't comment yet (too low reputation) but I resolved this problem. My solution was to change the name of the #WebParam so that it has not the name of the Class:
public List<Revision> listRevisions(#WebParam(name="crit")Criteria criteria) {
instead of
public List<Revision> listRevisions(#WebParam(name="criteria")Criteria criteria) {
I don't know why it happens though. Maybe someone can give an insight.

Related

Getter in an interface with default method JSF

I have an interface with the following default method:
default Integer getCurrentYear() {return DateUtil.getYear();}
I also have a controller that implements this interface, but it does not overwrite the method.
public class NotifyController implements INotifyController
I'm trying to access this method from my xhtml like this:
#{notifyController.currentYear}
However when I open the screen the following error occurs:
The class 'br.com.viasoft.controller.notify.notifyController' does not have the property 'anoAtual'
If I access this method from an instance of my controller, it returns the right value, however when I try to access it from my xhtml as a "property" it occurs this error.
Is there a way to access this interface property from a reference from my controller without having to implement the method?
This may be considered as a bug, or one might argue it is a decision to not support default methods as properties.
See in JDK8 java.beans.Introspector.getPublicDeclaredMethods(Class<?>)
or in JDK13 com.sun.beans.introspect.MethodInfo.get(Class<?>)
at line if (!method.getDeclaringClass().equals(clz))
And only the super class (recursively upto Object, but not the interfaces) are added, see java.beans.Introspector.Introspector(Class<?>, Class<?>, int) when setting superBeanInfo.
Solutions:
Use EL method call syntax (i.e. not property access): #{notifyController.getCurrentYear()} in your case.
Downside: You have to change the JSF code and must consider for each use if it may be a default method. Also refactoring forces changes that are not recognized by the compiler, only during runtime.
Create an EL-Resolver to generically support default methods. But this should use good internal caching like the standard java.beans.Introspector to not slow down the EL parsing.
See "Property not found on type" when using interface default methods in JSP EL for a basic example (without caching).
If only a few classes/interfaces are affected simply create small BeanInfo classes.
The code example below shows this (basing on your example).
Downside: A separate class must be created for each class (that is used in JSF/EL) implementing such an interface.
See also: Default method in interface in Java 8 and Bean Info Introspector
=> static getBeanInfo() in the interface with default methods
=> simple+short BeanInfo class for each class extending the interface
interface INotifyController {
default Integer getCurrentYear() { ... }
default boolean isAHappyYear() { ... }
default void setSomething(String param) { ... }
/** Support for JSF-EL/Beans to get default methods. */
static java.beans.BeanInfo[] getBeanInfo() {
try {
java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(INotifyController.class);
if (info != null) return new java.beans.BeanInfo[] { info };
} catch (java.beans.IntrospectionException e) {
//nothing to do
}
return null;
}
}
public class NotifyController implements INotifyController {
// your class implementation
...
}
// must be a public class and thus in its own file
public class NotifyControllerBeanInfo extends java.beans.SimpleBeanInfo {
#Override
public java.beans.BeanInfo[] getAdditionalBeanInfo() {
return INotifyController.getBeanInfo();
}
}
I found it will be fixed in Jakarta EE 10.
https://github.com/eclipse-ee4j/el-ri/issues/43
Before Jakarta EE 10 you can use custom EL Resolver.
package ru.example.el;
import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ELResolver;
import java.beans.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DefaultMethodELResolver extends ELResolver {
private static final Map<Class<?>, BeanProperties> properties = new ConcurrentHashMap<>();
#Override
public Object getValue(ELContext context, Object base, Object property) {
if (base == null || property == null) {
return null;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
Method method = beanProperty.getReadMethod();
if (method == null) {
throw new ELException(String.format("Read method for property '%s' not found", property));
}
Object value;
try {
value = method.invoke(base);
context.setPropertyResolved(base, property);
} catch (Exception e) {
throw new ELException(String.format("Read error for property '%s' in class '%s'", property, base.getClass()), e);
}
return value;
}
return null;
}
#Override
public Class<?> getType(ELContext context, Object base, Object property) {
if (base == null || property == null) {
return null;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
context.setPropertyResolved(true);
return beanProperty.getPropertyType();
}
return null;
}
#Override
public void setValue(ELContext context, Object base, Object property, Object value) {
if (base == null || property == null) {
return;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
Method method = beanProperty.getWriteMethod();
if (method == null) {
throw new ELException(String.format("Write method for property '%s' not found", property));
}
try {
method.invoke(base, value);
context.setPropertyResolved(base, property);
} catch (Exception e) {
throw new ELException(String.format("Write error for property '%s' in class '%s'", property, base.getClass()), e);
}
}
}
#Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
if (base == null || property == null) {
return false;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
context.setPropertyResolved(true);
return beanProperty.isReadOnly();
}
return false;
}
#Override
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
return null;
}
#Override
public Class<?> getCommonPropertyType(ELContext context, Object base) {
return Object.class;
}
private BeanProperty getBeanProperty(Object base, Object property) {
return properties.computeIfAbsent(base.getClass(), BeanProperties::new)
.getBeanProperty(property);
}
private static final class BeanProperties {
private final Map<String, BeanProperty> propertyByName = new HashMap<>();
public BeanProperties(Class<?> cls) {
try {
scanInterfaces(cls);
} catch (IntrospectionException e) {
throw new ELException(e);
}
}
private void scanInterfaces(Class<?> cls) throws IntrospectionException {
for (Class<?> ifc : cls.getInterfaces()) {
processInterface(ifc);
}
Class<?> superclass = cls.getSuperclass();
if (superclass != null) {
scanInterfaces(superclass);
}
}
private void processInterface(Class<?> ifc) throws IntrospectionException {
BeanInfo info = Introspector.getBeanInfo(ifc);
for (PropertyDescriptor propertyDescriptor : info.getPropertyDescriptors()) {
String propertyName = propertyDescriptor.getName();
BeanProperty beanProperty = propertyByName
.computeIfAbsent(propertyName, key -> new BeanProperty(propertyDescriptor.getPropertyType()));
if (beanProperty.getReadMethod() == null && propertyDescriptor.getReadMethod() != null) {
beanProperty.setReadMethod(propertyDescriptor.getReadMethod());
}
if (beanProperty.getWriteMethod() == null && propertyDescriptor.getWriteMethod() != null) {
beanProperty.setWriteMethod(propertyDescriptor.getWriteMethod());
}
}
for (Class<?> parentIfc : ifc.getInterfaces()) {
processInterface(parentIfc);
}
}
public BeanProperty getBeanProperty(Object property) {
return propertyByName.get(property.toString());
}
}
private static final class BeanProperty {
private final Class<?> propertyType;
private Method readMethod;
private Method writeMethod;
public BeanProperty(Class<?> propertyType) {
this.propertyType = propertyType;
}
public Class<?> getPropertyType() {
return propertyType;
}
public boolean isReadOnly() {
return getWriteMethod() == null;
}
public Method getReadMethod() {
return readMethod;
}
public void setReadMethod(Method readMethod) {
this.readMethod = readMethod;
}
public Method getWriteMethod() {
return writeMethod;
}
public void setWriteMethod(Method writeMethod) {
this.writeMethod = writeMethod;
}
}
}
You should register EL Resolver in faces-config.xml.
<?xml version="1.0" encoding="utf-8"?>
<faces-config version="2.3" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd">
<name>el_resolver</name>
<application>
<el-resolver>ru.example.el.DefaultMethodELResolver</el-resolver>
</application>
</faces-config>
since this bug is related to JDK, you'll have to create a delegate method in the class that needs the property.

broadleaf override controller method

I'm new on broadleaf.
I have problem, I wanted to obscure the method that removes my order in admin:
I create controller :
public class NewOrderController extends AdminBasicEntityController {
private static final Logger LOGGER = Logger.getLogger(NewOrderController.class);
protected static final String SECTION_KEY = "order";
#Override
protected String getSectionKey(Map<String, String> pathVars) {
if (super.getSectionKey(pathVars) != null) {
return super.getSectionKey(pathVars);
}
return SECTION_KEY;
}
#Override
#RequestMapping(
value = {"/{id}/delete"},
method = {RequestMethod.POST}
)
public String removeEntity(HttpServletRequest request, HttpServletResponse response, Model model, Map<String, String> pathVars, String id, EntityForm entityForm, BindingResult result, RedirectAttributes ra) throws Exception {
LOGGER.info("wywołanie nadpisane metody: " + NewOrderController.class.toString());
return "String";
}
}
in applicationContext-admin.xml
add :
All the time it calls me the not overwritten method.
When you create a controller, the bean must be in the servlet context, not in the root context. If you are modifying applicationContext-admin.xml then you are actually adding the bean to the root context.
Add your bean to applicationContext-servlet-admin.xml or add a new <component-scan> entry into applicationContext-servlet-admin.xml to scan your new bean.
One more thing: you likely do not want to override the entire AdminBasicEntityController and it looks like you just want to override /order/* methods. In that case, you should annotate your controller with #Controller and add an #RequestMapping for your section key like this:
#Controller
#RequestMapping("/" + SECTION_KEY)
public class NewOrderController extends AdminBasicEntityController {
private static final Logger LOGGER = Logger.getLogger(NewOrderController.class);
protected static final String SECTION_KEY = "order";
#Override
protected String getSectionKey(Map<String, String> pathVars) {
if (super.getSectionKey(pathVars) != null) {
return super.getSectionKey(pathVars);
}
return SECTION_KEY;
}
#Override
#RequestMapping(
value = {"/{id}/delete"},
method = {RequestMethod.POST}
)
public String removeEntity(HttpServletRequest request, HttpServletResponse response, Model model, Map<String, String> pathVars, String id, EntityForm entityForm, BindingResult result, RedirectAttributes ra) throws Exception {
LOGGER.info("wywołanie nadpisane metody: " + NewOrderController.class.toString());
return "String";
}
}

How to convert null string to empty string in retrofit2?

Before moving to retrofit2, I was using volley. To set my pojo value, I was using optString and optInt to retrieve value from JSONObject response. This will replace my null string to empty string and I don't need to add null checks for the same. How will do the same with retrofit2.
Example Json:
{
company_name : null
}
Pojo class
public class company implements Serializable{
#SerializedName("company_name")
private String companyName;
public String getCompanyName(){
return companyName;
}
public void setCompanyName(String companyName){
this.companyName = companyName;
}
String companyName = getCompany();
cause NullPointerException.
Is there any way to convert every null string to empty string like optString.?
Make String type adapter similar
public class StringAdapter extends TypeAdapter<String> {
public String read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
//here is the point of interest
//instead of return null;
return "";
}
return reader.nextString();
}
public void write(JsonWriter writer, String value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
writer.value(value);
}
}
for more details read this
about like this; the ternary operator used is the same as if:
public void setCompanyName(String value){
this.companyName = (value==null ? "" : value);
}
or try the Jackson Converter alike JsonParserUtil.toPojo(...) annotated with #JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL) then there shouldn't be any NULL values passed on - while the setter still would crash on NULL values, unless properly sanitized.

Custom enum marshaling with JAXB and moxy

I am having some trouble with custom enum marshaling with Moxy and JSON. My use case is that I have a large object model that includes enumerations that normally should provide a normal enumerated value, a "code", and a description. The source of this data has only the "code", so I need to be able to unmarshal instances of these enums using only the code (e.g.
{"companyCode":{"code":"PI"}}.
However, I should also be able to marshal and unmarshal all three fields:
{"companyCode":
{"value":"Private",
"code":"PI","description":
"Private Ins"
}
}
I am using an adapter that looks like this:
public class CodeEnumXmlAdapter<E extends Enum<E> & CodeEnum> extends XmlAdapter<CodeEnumImpl,E> {
public static <T extends Enum<T> & CodeEnum> T getFromName(Class<T> clazz, String name) {
if (name == null) return null;
T[] values = clazz.getEnumConstants();
for (T t : values) {
if (name.equals(t.name())) {
return t;
}
}
return null;
}
public static <T extends Enum<T> & CodeEnum> T getFromCode(Class<T> clazz, String code) {
if (code == null) return null;
T[] values = clazz.getEnumConstants();
for (T t : values) {
if (code.equals(t.getCode())) {
return t;
}
}
return null;
}
public static <T extends Enum<T> & CodeEnum> T getFromString(Class<T> clazz, String aString) {
if (aString == null) return null;
T[] values = clazz.getEnumConstants();
for (T t : values) {
if (aString.equals(t.getCode()) || aString.equals(t.name()) || aString.equals(t.getDescription())) {
return t;
}
}
return null;
}
#Override
public E unmarshal(CodeEnumImpl value) throws Exception {
if (value == null) return null;
String valueString = value.getValue();
if (valueString == null)
valueString = value.getCode();
if (valueString == null)
valueString = value.getDescription();
if (valueString == null)
return null;
Type generic = ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
return getFromString((Class<E>)generic, valueString);
}
#Override
public CodeEnumImpl marshal(E value) throws Exception {
return value == null ? null : new CodeEnumImpl(value);
}
}
This converts from a an enum like this:
import org.apache.commons.lang3.StringUtils;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlJavaTypeAdapter(CompanyCode.Adapter.class)
public enum CompanyCode implements CodeEnum {
// Changed "Commmercial" to "Client" based on inputs from ...Greg, Tamil
Client("CM", "Client"), Medicare("MC", "Medicare"), Medicaid("MD",
"Medicaid"), Private("PI", "Private Ins"), Patient("PT", "Patient");
private String code;
private String description;
private CompanyCode(String code, String label) {
this.code = code;
this.description = label;
}
public String getDescription() {
return description;
}
public String getCode() {
return code;
}
public static CompanyCode fromCode(String code) {
if (StringUtils.isEmpty(code)) {
return null;
}
for (CompanyCode freq : values()) {
if (freq.getCode().equalsIgnoreCase(code)) {
return freq;
}
}
throw new IllegalArgumentException("Invalid CompanyCode code: " + code);
}
public String toString() {
return description;
}
public static class Adapter extends CodeEnumXmlAdapter<CompanyCode> {}
}
and uses and intermediate type like this:
import javax.xml.bind.annotation.XmlElement;
/**
* Created by Jeffrey Hoffman on 6/24/2015.
*/
public class CodeEnumImpl {
String value;
String description;
String code;
public CodeEnumImpl() {
}
public <E extends Enum<E> & CodeEnum> CodeEnumImpl(E value) {
if (value != null) {
this.value = value.name();
this.description = value.getDescription();
this.code = value.getCode();
}
}
#XmlElement
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
#XmlElement
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#XmlElement
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
#Override
public String toString() {
return value == null ? null : value.toString();
}
}
This is working fine with straight XML and JAXB. However, when I try to use Moxy, I get an exception like this:
Exception Description: The object [Private Ins], of class [class
com.labcorp.phoenix.biz.enums.CompanyCode], could not be converted to
[class java.lang.Object]. Internal Exception: Exception
[EclipseLink-115] (Eclipse Persistence Services -
2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DescriptorException Exception
Description: No conversion value provided for the attribute [Private].
Mapping:
org.eclipse.persistence.oxm.mappings.XMLDirectMapping[companyCode-->companyCode/text()]
Descriptor: XMLDescriptor(com.labcorp.phoenix.eligibility.Root -->
[DatabaseTable(root)]) at
org.eclipse.persistence.exceptions.ConversionException.couldNotBeConverted(ConversionException.java:87)
at
org.eclipse.persistence.internal.jaxb.XMLJavaTypeConverter.convertObjectValueToDataValue(XMLJavaTypeConverter.java:178)
at
org.eclipse.persistence.oxm.mappings.XMLDirectMapping.convertObjectValueToDataValue(XMLDirectMapping.java:511)
at
org.eclipse.persistence.oxm.mappings.XMLDirectMapping.getFieldValue(XMLDirectMapping.java:330)
at
org.eclipse.persistence.internal.oxm.XMLDirectMappingNodeValue.marshalSingleValue(XMLDirectMappingNodeValue.java:62)
at
org.eclipse.persistence.internal.oxm.XMLDirectMappingNodeValue.marshal(XMLDirectMappingNodeValue.java:58)
at
org.eclipse.persistence.internal.oxm.NodeValue.marshal(NodeValue.java:102)
at
org.eclipse.persistence.internal.oxm.record.ObjectMarshalContext.marshal(ObjectMarshalContext.java:59)
at
org.eclipse.persistence.internal.oxm.XPathNode.marshal(XPathNode.java:393)
at
org.eclipse.persistence.internal.oxm.XPathNode.marshal(XPathNode.java:368)
at
org.eclipse.persistence.internal.oxm.XPathObjectBuilder.buildRow(XPathObjectBuilder.java:238)
at
org.eclipse.persistence.internal.oxm.TreeObjectBuilder.buildRow(TreeObjectBuilder.java:118)
at
org.eclipse.persistence.internal.oxm.TreeObjectBuilder.buildRow(TreeObjectBuilder.java:1)
at
org.eclipse.persistence.internal.oxm.XMLMarshaller.marshal(XMLMarshaller.java:743)
at
org.eclipse.persistence.internal.oxm.XMLMarshaller.marshal(XMLMarshaller.java:1124)
at
org.eclipse.persistence.internal.oxm.XMLMarshaller.marshal(XMLMarshaller.java:869)
... 7 more Caused by: Exception [EclipseLink-115] (Eclipse
Persistence Services - 2.5.0.v20130507-3faac2b):
org.eclipse.persistence.exceptions.DescriptorException
It seems like a bug in moxy, because my adapter converts to a non-enum type, so there should not be a nestedConverter that deals with enums.
I managed to reproduce your issue with 2.5.0. It's most probably bug which has been fixed already. Unable to find the bug in Eclipse Bugzilla, but the same code works correctly with 2.6.0. Are you able to upgrade to latest MOXy?

Nesting Maps in Java

I want to store many details (like name, email, country) of the particular person using the same key in hashtable or hashmap in java?
hashMap.put(1, "Programmer");
hashMap.put(2, "IDM");
hashMap.put(3,"Admin");
hashMap.put(4,"HR");
In the above example, the 1st argument is a key and 2nd argument is a value, how can i add more values to the same key?
You can achieve what you're talking about using a map in each location of your map, but it's a little messy.
Map<String, Map> people = new HashMap<String, Map>();
HashMap<String, String> person1 = new HashMap<String, String>();
person1.put("name", "Jones");
person1.put("email", "jones#jones.com");
//etc.
people.put("key", person1);
//...
people.get("key").get("name");
It sounds like what you might really want, though, is to define a Person class that has multiple properties:
class Person
{
private String name;
private String email;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
//plus getters and setters for other properties
}
Map<String, Person> people = new HashMap<String, Person>();
person1 = new Person();
person1.setName("Jones");
people.put("key", person1);
//...
people.get("key").getName();
That's the best I can do without any information about why you're trying to store values in this way. Add more detail to your question if this is barking up the wrong tree.
I think what you are asking
let us assume you we want to store String page, int service in the key and an integer in the value.
Create a class PageService with the required variables and define your HashMap as
Hashmap hmap = .....
Inside pageService, what you need to do is override the equals() and hashcode() methods. Since when hashmap is comparing it checks for hashcode and equals.
Generating hashcode and equals is very easy in IDEs. For example in eclipse go to Source -> generate hashcode() and equals()
public class PageService {
private String page;
private int service;
public PageService(String page, int service) {
super();
this.page = page;
this.service = service;
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
public int getService() {
return service;
}
public void setService(int service) {
this.service = service;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((page == null) ? 0 : page.hashCode());
result = prime * result + service;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PageService other = (PageService) obj;
if (page == null) {
if (other.getPage() != null)
return false;
} else if (!page.equals(other.getPage()))
return false;
if (service != other.getService())
return false;
return true;
}
}
The following class is very generic. You can nest ad infinitum. Obviously you can add additional fields and change the types for the HashMap. Also note that the tabbing in the toString method should be smarter. The print out is flat.
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class HierarchicalMap
{
private String key;
private String descriptor;
private Map<String,HierarchicalMap>values=new HashMap<String,HierarchicalMap>();
public String getKey()
{
return key;
}
public void setKey(String key)
{
this.key = key;
}
public void addToSubMap(String key, HierarchicalMap subMap)
{
values.put(key, subMap);
}
public String getDescriptor()
{
return descriptor;
}
public void setDescriptor(String descriptor)
{
this.descriptor = descriptor;
}
public HierarchicalMap getFromSubMap(String key)
{
return values.get(key);
}
public Map<String,HierarchicalMap> getUnmodifiableSubMap()
{
return Collections.unmodifiableMap(values);
}
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("HierarchicalMap: ");
sb.append(key);
sb.append(" | ");
sb.append(descriptor);
Iterator<String> itr=values.keySet().iterator();
while(itr.hasNext())
{
String key= itr.next();
HierarchicalMap subMap=this.getFromSubMap(key);
sb.append("\n\t");
sb.append(subMap.toString());
}
return sb.toString();
}

Resources