Dynamic list constraint not updating in alfresco on a datalist - dynamic

I tried to create a dynamic list constraint. The data in the drop down is not getting refreshed when an item is added to the database.
ListOfValuesQueryConstraint.java
package org.alfresco.ryden;
import java.util.ArrayList;
import java.util.List;
import java.io.Serializable;
import java.sql.*;
import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint;
import org.alfresco.web.bean.generator.BaseComponentGenerator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.faces.model.SelectItem;
public class ListOfValuesQueryConstraint extends ListOfValuesConstraint implements Serializable {
private static Log logger = LogFactory.getLog(BaseComponentGenerator.class);
private static final long serialVersionUID=1;
private List allowedLabels;
public void setAllowedValues(List allowedValues) {}
public void setCaseSensitive(boolean caseSensitive) {}
public void initialize() {
super.setCaseSensitive(false);
this.loadDB();
}
public List getAllowedValues() {
this.loadDB();
return super.getAllowedValues(); // In earlier post there is no return statement..
//return this.getAllowedValues();
}
public List getAllowedLabels() {
return this.allowedLabels;
}
public void setAllowedLabels(List allowedLabels) {
this.allowedLabels=allowedLabels;
}
public List getSelectItemList() {
List result = new ArrayList(this.getAllowedValues().size());
for(int i=0;i<this.getAllowedValues().size();i++) {
result.add(new SelectItem((Object)this.getAllowedValues().get(i),this.allowedLabels.get(i)));
}
return result;
}
protected void loadDB() {
String driverName = "com.mysql.jdbc.Driver";
String serverName = "localhost:3307";
String mydatabase = "propertyrecord";
String username = "propertyrecord";
String password = "rydenproperty";
List av = new ArrayList();
List al=new ArrayList();
try {
Connection connection = null;
Class.forName(driverName);
String url = “jdbc:mysql://” + serverName + “/” + mydatabase;
connection = DriverManager.getConnection(url, username, password);
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(“select propertyRef from propertyrecord”);
while (rs.next()) {
av.add(rs.getString(“propertyRef”));
al.add(rs.getString(“propertyRef”));
System.out.println(“value of prop pavani “+rs.getString(“propertyRef”));
logger.debug(“value of prop pavani “+rs.getString(“propertyRef”));
}
rs=null;
}
catch (Exception e) {}
super.setAllowedValues(av);
this.setAllowedLabels(al);
}
}
CustomListComponentGenerator.java
package org.alfresco.ryden;
import java.util.List;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectOne;
import javax.faces.context.FacesContext;
import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint;
import org.alfresco.service.cmr.dictionary.Constraint;
import org.alfresco.service.cmr.dictionary.ConstraintDefinition;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.web.bean.generator.TextFieldGenerator;
import org.alfresco.web.ui.repo.component.property.PropertySheetItem;
import org.alfresco.web.ui.repo.component.property.UIPropertySheet;
import org.apache.log4j.Logger;
import org.alfresco.ryden.ListOfValuesQueryConstraint;
public class CustomListComponentGenerator extends TextFieldGenerator {
private static Logger log = Logger.getLogger(CustomListComponentGenerator.class);
// private String tutorialQuery =
// “( TYPE:\”{http://www.alfresco.org/model/content/1.0}content\” AND
// (#\\{http\\://www.alfresco.org/model/content/1.0\\}name:\”tutorial\”
// TEXT:\”tutorial\”))”
// ;
private boolean autoRefresh = false;
public boolean isAutoRefresh() {
return autoRefresh;
}
/**
* This gets set from faces-config-beans.xml, and allows some drop downs to
* be automaticlaly refreshable (i.e. country), and others not (i.e. city).
*/
public void setAutoRefresh(boolean autoRefresh) {
this.autoRefresh = autoRefresh;
}
#Override
#SuppressWarnings(“unchecked”)
protected UIComponent createComponent(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem item) {
UIComponent component = super.createComponent(context, propertySheet, item);
log.info(“********************** ” + item + ” >” + component + ” >” + (component instanceof UISelectOne) + ” ” + isAutoRefresh());
if (component instanceof UISelectOne && isAutoRefresh()) {
component.getAttributes().put(“onchange”, “submit()”);
}
return component;
}
/**
* Retrieves the list of values constraint for the item, if it has one
*
* #param context
* FacesContext
* #param propertySheet
* The property sheet being generated
* #param item
* The item being generated
* #return The constraint if the item has one, null otherwise
*/
protected ListOfValuesConstraint getListOfValuesConstraint(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem item) {
ListOfValuesConstraint lovConstraint = null;
log.info(“propertySheet: ” + propertySheet.getNode() + ” item: ” + item.getName());
// get the property definition for the item
PropertyDefinition propertyDef = getPropertyDefinition(context, propertySheet.getNode(), item.getName());
if (propertyDef != null) {
// go through the constaints and see if it has the
// list of values constraint
List constraints = propertyDef.getConstraints();
for (ConstraintDefinition constraintDef : constraints) {
Constraint constraint = constraintDef.getConstraint();
//log.info(“constraint: ” + constraint);
if (constraint instanceof ListOfValuesQueryConstraint) {
//Node currentNode = (Node) propertySheet.getNode();
// This is a workaround for the fact that constraints do not
// have a reference to Node.
//((ListOfValuesQueryConstraint) constraint).setNode(currentNode);
lovConstraint = (ListOfValuesQueryConstraint) constraint;
break;
}
if (constraint instanceof ListOfValuesConstraint) {
lovConstraint = (ListOfValuesConstraint) constraint;
break;
}
}
}
return lovConstraint;
}
}
custom-model.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- Definition of Property Base Model -->
<model name="cdl:customdatalist" xmlns="http://www.alfresco.org/model/dictionary/1.0">
<!-- Optional meta-data about the model -->
<description>Custom Data Model</description>
<author>Lalitha Akella</author>
<version>1.0</version>
<!-- Imports are required to allow references to definitions in other models -->
<imports>
<!-- Import Alfresco Dictionary Definitions -->
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
<!-- Import Alfresco Content Domain Model Definitions -->
<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
<import uri="http://www.alfresco.org/model/datalist/1.0" prefix="dl"/>
</imports>
<!-- Introduction of new namespaces defined by this model -->
<namespaces>
<namespace uri="cdl.model" prefix="cdl"/>
</namespaces>
<constraints>
<constraint name="cdl:PropertyRef" type="org.alfresco.ryden.ListOfValuesQueryConstraint" >
<parameter name="allowedValues">
<list>
</list>
</parameter>
<parameter name="caseSensitive"><value>true</value></parameter>
</constraint>
</constraints>
<types>
<type name="cdl:applicationform">
<title>Custom Application Form</title>
<parent>dl:dataListItem</parent>
<properties>
<property name="cdl:applicationpropertyRef">
<title>Property Reference</title>
<type>d:text</type>
<mandatory>true</mandatory>
<constraints>
<constraint ref="cdl:PropertyRef" />
</constraints>
</property>
<property name="cdl:applicationpropAddress">
<title>Property Address</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:apcreateddate">
<title>Created Date</title>
<type>d:date</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:apcreatedby">
<title>Created By</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:applicationstatus">
<title>Application Status</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:applicationlink">
<title>Application Workflow Link</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
</properties>
<associations>
<association name="cdl:applicationassignee">
<title>Assignee</title>
<source>
<mandatory>true</mandatory>
<many>true</many>
</source>
<target>
<class>cm:person</class>
<mandatory>true</mandatory>
<many>false</many>
</target>
</association>
<association name="cdl:applicationattachments">
<title>Attachments</title>
<source>
<mandatory>true</mandatory>
<many>true</many>
</source>
<target>
<class>cm:cmobject</class>
<mandatory>true</mandatory>
<many>true</many>
</target>
</association>
</associations>
</type>
<type name="cdl:terminationform">
<title>Custom Termination Form</title>
<parent>dl:dataListItem</parent>
<properties>
<property name="cdl:terminationpropertyRef">
<title>Property Reference</title>
<type>d:text</type>
<mandatory>true</mandatory>
<constraints>
<constraint ref="cdl:PropertyRef" />
</constraints>
</property>
<property name="cdl:trcreateddate">
<title>Created Date</title>
<type>d:date</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:trcreatedby">
<title>Created By</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:terminationstatus">
<title>Termination Status</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
<property name="cdl:terminationlink">
<title>Termination Workflow Link</title>
<type>d:text</type>
<mandatory>false</mandatory>
</property>
</properties>
<associations>
<association name="cdl:terminationassignee">
<title>Assignee</title>
<source>
<mandatory>true</mandatory>
<many>true</many>
</source>
<target>
<class>cm:person</class>
<mandatory>true</mandatory>
<many>false</many>
</target>
</association>
<association name="cdl:terminationattachments">
<title>Attachments</title>
<source>
<mandatory>true</mandatory>
<many>true</many>
</source>
<target>
<class>cm:cmobject</class>
<mandatory>true</mandatory>
<many>true</many>
</target>
</association>
</associations>
</type>
</types>
</model>
web-client-config-custom.xml
<config evaluator="node-type" condition="cdl:assignationform">
<property-sheet>
<show-property name="cdl:assignationpropertyRef" component-generator="CustomListComponentGenerator" />
</property-sheet>
</config>
faces-config-beans.xml
<managed-bean>
<description>
Bean that generates a custom generator component
</description>
<managed-bean-name>
CustomListComponentGenerator
</managed-bean-name>
<managed-bean-class>
org.alfresco.ryden.CustomListComponentGenerator
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>autoRefresh</property-name>
<value>true</value>
</managed-property>
</managed-bean>
I don't know whether I should be changing any other files or some thing is wrong in the code above.
I am new To alfresco. Any help is deeply appreciated.
Thanks,
Pavani

Try the following and change to as needed, as it works
ListOfCountriesQueryConstraint.java
package org.spectrum.customConstraints;
import java.util.ArrayList;
import java.util.List;
import java.sql.*;
import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint;
import org.alfresco.web.bean.generator.BaseComponentGenerator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.Serializable;
import javax.faces.model.SelectItem;
public class ListOfCountriesQueryConstraint extends ListOfValuesConstraint implements Serializable {
private static Log logger = LogFactory.getLog(BaseComponentGenerator.class);
private static final long serialVersionUID = 1;
private List<String> allowedLabels;
#Override
public void setAllowedValues(List allowedValues) {
}
#Override
public void setCaseSensitive(boolean caseSensitive) {
}
#Override
public void initialize() {
super.setCaseSensitive(false);
this.loadDB();
}
#Override
public List getAllowedValues() {
this.loadDB();
return super.getAllowedValues();
}
public List<String> getAllowedLabels() {
return this.allowedLabels;
}
public void setAllowedLabels(List<String> allowedLabels) {
this.allowedLabels = allowedLabels;
}
public List<SelectItem> getSelectItemList() {
List<SelectItem> result = new ArrayList<SelectItem>(this.getAllowedValues().size());
for (int i = 0; i < this.getAllowedValues().size(); i++) {
result.add(new SelectItem((Object) this.getAllowedValues().get(i), this.allowedLabels.get(i)));
}
return result;
}
protected void loadDB() {
String driverName = "org.gjt.mm.mysql.Driver";
String serverName = "alfrescotest";
String mydatabase = "alfresco_custom";
String username = "root";
String password = "support";
List<String> av = new ArrayList<String>();
List<String> al = new ArrayList<String>();
try {
Connection connection = null;
Class.forName(driverName);
String url = "jdbc:mysql://" + serverName + "/" + mydatabase;
connection = DriverManager.getConnection(url, username, password);
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("select country from countries");
while (rs.next()) {
av.add(rs.getString("country"));
al.add(rs.getString("country"));
}
} catch (Exception e) {
}
super.setAllowedValues(av);
this.setAllowedLabels(al);
}
}
custom-model.xml
<constraint name="sp:country" type="org.spectrum.customConstraints.ListOfCountriesQueryConstraint">
<parameter name="allowedValues">
<list>
</list>
</parameter>
<parameter name="caseSensitive"><value>true</value></parameter>
</constraint>
Make sure to copy the compile java to tomcat/webapps/alfresco/WEB-INF/classes/org/xxx/

Related

Jackson UnrecognizedProperyException when parsing XML

I am trying to parse an XML where I generate the DTOs using maven-jaxb2-plugin from xsd file. But I get this exception and don't know why, everything seems alright.
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Publish_Date" (class com.compnay.package.SdnList$PublshInformation), not marked as ignorable (2 known properties: "publishDate", "recordCount"])
at [Source: (PushbackInputStream); line: 4, column: 44] (through reference chain: com.compnay.package.SdnList["publshInformation"]->com.compnay.package.domain.SdnList$PublshInformation["Publish_Date"])
Jaxb execution for the relevant xsd
<execution>
<id>tds</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<schemas>
<schema>
<url>xsd url</url>
</schema>
</schemas>
<generatePackage>com.company.domain</generatePackage>
<generateDirectory>${project.basedir}/domain/src/main/java</generateDirectory>
<episode>false</episode>
</configuration>
</execution>
Part of the XML file where I get the error.
<publshInformation>
<Publish_Date>08/06/2021</Publish_Date>
<Record_Count>9030</Record_Count>
</publshInformation>
Rest template Configuration
JacksonXmlModule module = new JacksonXmlModule();
module.setDefaultUseWrapper(false);
final XmlMapper xmlMapper = new XmlMapper(module);
xmlMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
// xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // Works when this is on
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(xmlMapper);
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_XML));
return new RestTemplateBuilder()
.setReadTimeout(Duration.ofMillis(readTimeout))
.setConnectTimeout(Duration.ofMillis(connectTimeout))
.messageConverters(converter)
.build();
Part of a Generated DTO
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"publshInformation",
"sdnEntry"
})
#XmlRootElement(name = "sdnList")
public class SdnList {
#XmlElement(required = true)
protected SdnList.PublshInformation publshInformation;
#XmlElement(required = true)
protected List<SdnList.SdnEntry> sdnEntry;
........
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType>
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <sequence>
* <element name="Publish_Date" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
* <element name="Record_Count" type="{http://www.w3.org/2001/XMLSchema}int" minOccurs="0"/>
* </sequence>
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"publishDate",
"recordCount"
})
public static class PublshInformation {
#XmlElement(name = "Publish_Date")
protected String publishDate;
#XmlElement(name = "Record_Count")
protected Integer recordCount;
........
}
}
I can make it work with using xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) but I don't want to lose other data. Can anyone help me figure it out why I get unrecognizedPropertyException? I will appreciate any pointers.
I guess you are doing something before the derealization which is making your input stream empty due to which you are getting this error. I used the provided XML and seems to work fine for me:
XML:
<publshInformation>
<Publish_Date>08/06/2021</Publish_Date>
<Record_Count>9030</Record_Count>
</publshInformation>
PublshInformation.class:
#XmlRootElement(name = "publshInformation")
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class PublshInformation {
#XmlElement(name = "Publish_Date")
private String Publish_Date;
#XmlElement(name = "Record_Count")
private Integer recordCount;
}
PublishMain.class:
public class PublishMain {
public static void main(String[] args) throws JAXBException, XMLStreamException, IOException {
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("publish.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
/* final Unmarshaller unmarshaller = JAXBContext.newInstance(PublshInformation.class).createUnmarshaller();
final PublshInformation publshInformation = unmarshaller.unmarshal(xmlStreamReader, PublshInformation.class).getValue();
System.out.println(publshInformation.toString());
Marshaller marshaller = JAXBContext.newInstance(PublshInformation.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(publshInformation, System.out);*/
System.out.println(inputStream);
final XmlMapper xmlMapper = new XmlMapper();
xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
PublshInformation jacksonPublish = xmlMapper.readValue(xmlStreamReader, PublshInformation.class);
System.out.println(jacksonPublish);
xmlMapper.writerWithDefaultPrettyPrinter().writeValue(System.out, jacksonPublish);
}
}
This would produce the result:
java.io.BufferedInputStream#73a28541
PublshInformation(Publish_Date=null, recordCount=null)
<PublshInformation>
<recordCount/>
<publish_Date/>
</PublshInformation>
The above code works even by using the pure JAXB. If you uncomment then it will do it using the JAXB. I used the latest jackson-dataformat-xml 2.12.4
Make your fields private.
Use the latest version of the Jackson
Ensure your input is not being used before which may become empty.
This should work I believe.

CallbackHandler in CXF 3.X & WSS4J 2.X

I'm trying to upgrade our current application to CXF 3 and WSS4J 2. This is causing me quite a headache.
The current application code for the client:
private void secureWebService( Client client, final Credentials credentials ) {
// set some WS-Security information
Map<String,Object> outProps = new HashMap<String,Object>();
outProps.put( WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN );
outProps.put( WSHandlerConstants.USER, credentials.getUsername() );
outProps.put( WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT );
// Callback used to retrieve password for given user.
outProps.put( WSHandlerConstants.PW_CALLBACK_REF, new CallbackHandler() {
#Override
public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
pc.setPassword( credentials.getPassword() );
}
});
WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor( outProps );
client.getOutInterceptors().clear();
client.getOutInterceptors().add( wssOut );
}
On the Server side...
public class ServerPasswordCallback implements CallbackHandler {
public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback)callbacks[0];
boolean result = false;
try {
LoginContext lc = new LoginContext( container, new CallbackHandler() {
public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
NameCallback nc = (NameCallback)callbacks[0];
nc.setName( myGetName() );
PasswordCallback pc2 = (PasswordCallback)callbacks[1];
String clientPasssword = pc.getPassword(); //Used to contain the password but is now NULL
pc2.setPassword( clientPasssword.toCharArray() );
}
} );
lc.login();
result = true;
} catch( LoginException le ) {
le.printStackTrace(); //current stack trace is a NULLPointerException since "clientPassword" is NULL
// We haven't authenticated, so false will be returned
} catch( SecurityException se ) {
throw new IOException( "Cannot create LoginContext. " + se.getMessage() );
}
return result;
}
}
My JAX-WS Endpoint Config:
<bean id="wss4jPasswordCallback" class="com.mycompany.webservice.security.ServerPasswordCallback"/>
<jaxws:endpoint id="customerEndpoint" implementor="#customerWebService" address="/Customer">
<jaxws:inInterceptors>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken"/>
<entry key="passwordType" value="PlainText"/>
<entry key="passwordCallbackRef">
<ref bean="wss4jPasswordCallback"/>
</entry>
</map>
</constructor-arg>
</bean>
<bean class="com.mycompany.webservice.security.Wss4jPrincipalInjectorInterceptor"/>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="com.mycompany.webservice.security.Wss4jPrincipalRemoverInterceptor"/>
</jaxws:outInterceptors>
<jaxws:outFaultInterceptors>
<bean class="com.mycompany.webservice.security.Wss4jPrincipalRemoverInterceptor"/>
</jaxws:outFaultInterceptors>
</jaxws:endpoint>
Specifically, the WSPasswordCallback object is now passing NULL rather than the password as it used to. From my reading, CXF just chose to stop doing this with insufficient documentation regarding what I would do for an upgrade path. What is an upgrade path for this?
Also, I've noticed that WSS4J is changing where it lives. It has moved from "org.apache.ws.security" to "org.apache.wss4j.common.ext". I have also updated all my constants to "org.apache.wss4j.dom.WSConstants" & "org.apache.wss4j.dom.handler.WSHandlerConstants" to get things to compile. This also has drastically changed the old "org.apache.ws.security.validate.Validator" class in "org.apache.commons.validator.Validator". The classes are quite different now. Maybe "org.apache.wss4j.dom.validate.KerberosTokenValidator" is the new replacement? Again, I could find no documentation for this fact.
Please note: This is all working code until moving to the new CXF and WSS4J version!
Due to the significant time I spent on this issue, I wanted to make sure I provided my solution. This may not be for everyone, but if your code looks like my question, this should get you on the right track.
First, what was the Validator class is now an interface after CXF 3. What I have working is the org.apache.wss4j.dom.validate.UsernameTokenValidator in place of what was org.apache.ws.security.validate.Validator. This critical piece of info was absent in my searches.
Therefore, if you are using CallbackHandler for doing custom authentication, you need to switch to the UsernameTokenValidator. Here is what my code now looks like.
JAX-WS Config:
<!-- Bean for custom authentication of web service -->
<bean id="UsernameTokenLDAPValidator" class="com.mycompany.webservice.security.UsernameTokenLDAPValidator"/>
<jaxws:endpoint id="customerEndpoint" implementor="#customerWebService" address="/Customer">
<jaxws:inInterceptors>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken"/>
<entry key="passwordType" value="PasswordText"/>
</map>
</constructor-arg>
</bean>
<bean class="com.mycompany.webservice.security.Wss4jPrincipalInjectorInterceptor"/>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="com.mycompany.webservice.security.Wss4jPrincipalRemoverInterceptor"/>
</jaxws:outInterceptors>
<jaxws:outFaultInterceptors>
<bean class="com.mycompany.webservice.security.Wss4jPrincipalRemoverInterceptor"/>
</jaxws:outFaultInterceptors>
<jaxws:properties>
<entry key="ws-security.enable.nonce.cache" value="false" />
<entry key="ws-security.enable.timestamp.cache" value="false" />
<entry key="ws-security.ut.validator" value-ref="UsernameTokenLDAPValidator"/>
</jaxws:properties>
</jaxws:endpoint>
NEW UsernameTokenLDAPValidator class
public class UsernameTokenLDAPValidator extends UsernameTokenValidator {
public Credential validate( Credential credential, RequestData request ) throws WSSecurityException {
UsernameToken userToken = credential.getUsernametoken();
final String userId = userToken.getName();
final String password = userToken.getPassword();
String securityDomainName = "SecurityDomainNameNameOfJBOSSConfig"; //<login-module>
LoginContext lc;
try {
lc = new LoginContext( securityDomainName, new CallbackHandler() {
public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
NameCallback nc = (NameCallback)callbacks[0];
nc.setName( userId );
PasswordCallback pc2 = (PasswordCallback)callbacks[1];
pc2.setPassword( password.toCharArray() );
}
} );
lc.login();
} catch( LoginException e ) {
throw new WSSecurityException( ErrorCode.FAILED_AUTHENTICATION, e );
}
return credential;
}
}
Notes:
I removed my old CallbackHandler class (ServerPasswordCallback) (in question)
I did not make any changes to the application code for the client (in question)
It sounds like you are upgrading from an old version of CXF that used WSS4J 1.5.x From WSS4J 1.6.x, the CallbackHandler is no longer supplied with the password, but must instead set the password on the Callback. See here:
http://coheigea.blogspot.ie/2011/02/usernametoken-processing-changes-in.html
Colm.

Have users of activeMQ own a queue which name is the user's name

In my application, a user may create an account freely, and it needs to own a queue (or topic) to communicate 2 backend processes between them. I don't want to have to modify activemq's configuration every time that someone creates an account. I have already created a jaasAuthenticationPlugin and it works fine. Here is the relevant part of my activemq.xml file:
<plugins>
<!-- 'activemq-domain' defined in conf/login.conf -->
<jaasAuthenticationPlugin configuration="activemq-domain" />
<authorizationPlugin>
<map>
<authorizationMap>
<authorizationEntries>
<authorizationEntry queue="foobarQueue"
write="foobarGroup"
read="foobarGroup"
admin="foobarGroup"
/>
</authorizationEntries>
</authorizationMap>
</map>
</authorizationPlugin>
</plugins>
As you may deduct, the authentication plugin is authenticating a user (foobar in this example) and putting the user in the foobarGroup group. The AuthorizationEntry is granting read, write and admin privileges to the foobarQueue to this foobarGroup. This is working well, but now if I create a new user, I must come to this file and add a new AuthorizationEntry. Is it possible with a simple configuration line in the activemq.xml to do something like:
<authorizationEntry
queue="<% Username %>"
write="<% Username %>"
read="<% Username %>"
admin="<% Username %>"
/>
or should I write some JAAS authorization class to do that?
Finally I have written a class to handle the Authorization part. It was a bit difficult because documentation is difficult to find and I couldn't find any good example. Digging in the source code of the default LDAPAuthorizationMap was key. Anyway, the source for anyone interested:
package com.example.activemq;
import org.apache.activemq.advisory.AdvisorySupport;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.jaas.GroupPrincipal;
import org.apache.activemq.security.AuthorizationMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.HashSet;
import java.util.Set;
public class OwnedUserQueueAuthorizator implements AuthorizationMap {
private static final Log log =
LogFactory.getLog(OwnedUserQueueAuthorizator.class);
private boolean debug = false;
// the Destination will be the name of the user, and we should return that
// the group with user name has read,write and admin privileges to the
// topic/queue named like the username
// for temporary destinations, if null is returned, then everybody has
// permission.
public Set<GroupPrincipal> getTempDestinationAdminACLs() {
return null;
}
public Set<GroupPrincipal> getTempDestinationReadACLs() {
return null;
}
public Set<GroupPrincipal> getTempDestinationWriteACLs() {
return null;
}
// for persistent destinations
public Set<GroupPrincipal> getAdminACLs(ActiveMQDestination destination) {
if (debug) {
log.debug("getAdminACLs: " + destination.getPhysicalName());
}
return getACLs(destination);
}
public Set<GroupPrincipal> getReadACLs(ActiveMQDestination destination) {
if (debug) {
log.debug("getReadACLs: " + destination.getPhysicalName());
}
return getACLs(destination);
}
public Set<GroupPrincipal> getWriteACLs(ActiveMQDestination destination) {
if (debug) {
log.debug("getwriteACLs: " + destination.getPhysicalName());
}
return getACLs(destination);
}
private Set<GroupPrincipal> getACLs(ActiveMQDestination destination) {
Set<GroupPrincipal> result;
if (AdvisorySupport.isAdvisoryTopic(destination)) {
result = getACLsForAdvisory();
} else {
result = new HashSet<GroupPrincipal>();
// Destination should be something like UUID or UUID.whatever...,
// so we must add only the first component as the group principal
result.add(new GroupPrincipal(
destination.getDestinationPaths()[0])
);
}
if (debug) {
String s = "";
for (GroupPrincipal gp : result) {
s += ", " + gp.getName();
}
log.debug("groupPrincipals: " + "[" + s.substring(2) + "]");
}
return result;
}
private Set<GroupPrincipal> getACLsForAdvisory() {
Set<GroupPrincipal> result = new HashSet<GroupPrincipal>();
GroupPrincipal g = new GroupPrincipal("advisories");
result.add(g);
return result;
}
// Properties
// -------------------------------------------------------------------------
// if the <bean> definition in the activemq.xml has some
// <property name="foo" value="..." />
// defined, they will call this.setFoo($value), so, even if these get/set
// methods aren't called from here, they are really needed.
public void setDebug(String debug) {
this.debug = "true".equalsIgnoreCase(debug);
}
public String getDebug() {
return String.valueOf(debug);
}
}
The conf/activemq.xml file:
<beans ...>
...
<broker ...>
...
<plugins>
<!-- 'activemq-domain' defined in conf/login.conf -->
<jaasAuthenticationPlugin configuration="activemq-domain" />
<authorizationPlugin>
<map>
<bean id="OwnedUserQueueAuthorizationMap"
class="com.example.activemq.OwnedUserQueueAuthorizator"
xmlns="http://www.springframework.org/schema/beans">
<property name="debug" value="false"/>
</bean>
</map>
</authorizationPlugin>
</plugins>
...
</broker>
...
</beans>

Why is data getting stored with weird keys in Redis when using Jedis with Spring Data?

I am using Spring Data Redis with Jedis. I am trying to store a hash with key vc:${list_id}. I was able to successfully insert to redis. However, when I inspect the keys using the redis-cli, I don't see the key vc:501381. Instead I see \xac\xed\x00\x05t\x00\tvc:501381.
Why is this happening and how do I change this?
Ok, googled around for a while and found help at http://java.dzone.com/articles/spring-data-redis.
It happened because of Java serialization.
The key serializer for redisTemplate needs to be configured to StringRedisSerializer i.e. like this:
<bean
id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.server}"
p:port="${redis.port}"
p:use-pool="true"/>
<bean
id="stringRedisSerializer"
class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
/>
Now the key in redis is vc:501381.
Or like #niconic says, we can also set the default serializer itself to the string serializer as follows:
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:defaultSerializer-ref="stringRedisSerializer"
/>
which means all our keys and values are strings. Notice however that this may not be preferable, since you may want your values to be not just strings.
If your value is a domain object, then you can use Jackson serializer and configure a serializer as mentioned here i.e. like this:
<bean id="userJsonRedisSerializer" class="org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer">
<constructor-arg type="java.lang.Class" value="com.mycompany.redis.domain.User"/>
</bean>
and configure your template as:
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
p:valueSerialier-ref="userJsonRedisSerializer"
/>
It's a very old question, but my answer might be helpful for someone who got the same issue while working with Redis using Spring Boot. I was stuck on the same issue while storing hash type data in redis. I have written the required config file changes for the RedisTemplate.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = "com.redis")
public class AppCofiguration {
#Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory jedisConFactory = new JedisConnectionFactory();
jedisConFactory.setHostName("127.0.0.1");
jedisConFactory.setPort(6379);
return jedisConFactory;
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
final RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
// the following is not required
template.setHashValueSerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
return template;
}
}
If the data type is String then template.setHashValueSerializer(new StringRedisSerializer()); and template.setHashKeySerializer(new StringRedisSerializer()); are not required.
I know this question has been a while, but I did some research on this topic again recently, so I would like to share how this "semi-hashed" key is generated by going thru part of the spring source code here.
First of all, Spring leverages AOP to resolve annotations like #Cacheable, #CacheEvict or #CachePut etc. The advice class is CacheInterceptor from Spring-context dependency, which is a subclass of CacheAspectSupport (also from Spring-context). For the ease of this explanation, I would use #Cacheable as an example to go thru part of the source code here.
When the method annotated as #Cacheable is invoked, AOP would route it to this method protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) from CacheAspectSupport class, in which it would try to resolve this #Cacheable annotation. In turn, it leads to the invocation of this method public Cache getCache(String name) in the implementing CacheManager. For this explanation, the implementing CacheManage would be RedisCacheManager (from Spring-data-redis dependency).
If the cache was not hit, it will go ahead to create the cache. Below is the key methods from RedisCacheManager:
protected Cache getMissingCache(String name) {
return this.dynamic ? createCache(name) : null;
}
#SuppressWarnings("unchecked")
protected RedisCache createCache(String cacheName) {
long expiration = computeExpiration(cacheName);
return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,
cacheNullValues);
}
Essentially, it will instantiate an RedisCache object. To do this, it requires 4 parameters, namely, cacheName, prefix (this is the key parameter with regards to answering this question), redisOperation (aka, the configured redisTemplate), expiration (default to 0) and cacheNullValues (default to false). The constructor below shows more details about RedisCache.
/**
* Constructs a new {#link RedisCache} instance.
*
* #param name cache name
* #param prefix must not be {#literal null} or empty.
* #param redisOperations
* #param expiration
* #param allowNullValues
* #since 1.8
*/
public RedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations,
long expiration, boolean allowNullValues) {
super(allowNullValues);
Assert.hasText(name, "CacheName must not be null or empty!");
RedisSerializer<?> serializer = redisOperations.getValueSerializer() != null ? redisOperations.getValueSerializer()
: (RedisSerializer<?>) new JdkSerializationRedisSerializer();
this.cacheMetadata = new RedisCacheMetadata(name, prefix);
this.cacheMetadata.setDefaultExpiration(expiration);
this.redisOperations = redisOperations;
this.cacheValueAccessor = new CacheValueAccessor(serializer);
if (allowNullValues) {
if (redisOperations.getValueSerializer() instanceof StringRedisSerializer
|| redisOperations.getValueSerializer() instanceof GenericToStringSerializer
|| redisOperations.getValueSerializer() instanceof JacksonJsonRedisSerializer
|| redisOperations.getValueSerializer() instanceof Jackson2JsonRedisSerializer) {
throw new IllegalArgumentException(String.format(
"Redis does not allow keys with null value ¯\\_(ツ)_/¯. "
+ "The chosen %s does not support generic type handling and therefore cannot be used with allowNullValues enabled. "
+ "Please use a different RedisSerializer or disable null value support.",
ClassUtils.getShortName(redisOperations.getValueSerializer().getClass())));
}
}
}
So what the use of prefix in this RedisCache? --> As shown in the constructor about, it is used in this statement this.cacheMetadata = new RedisCacheMetadata(name, prefix);, and the constructor of RedisCacheMetadata below shows more details:
/**
* #param cacheName must not be {#literal null} or empty.
* #param keyPrefix can be {#literal null}.
*/
public RedisCacheMetadata(String cacheName, byte[] keyPrefix) {
Assert.hasText(cacheName, "CacheName must not be null or empty!");
this.cacheName = cacheName;
this.keyPrefix = keyPrefix;
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// name of the set holding the keys
this.setOfKnownKeys = usesKeyPrefix() ? new byte[] {} : stringSerializer.serialize(cacheName + "~keys");
this.cacheLockName = stringSerializer.serialize(cacheName + "~lock");
}
At this point, we know that some prefix parameter has been set to RedisCacheMetadata, but how exactly is this prefix used to form the key in Redis (e.g.,\xac\xed\x00\x05t\x00\tvc:501381 as you mentioned)?
Basically, the CacheInterceptor will subsequently move forward to invoke a method private RedisCacheKey getRedisCacheKey(Object key) from the above-mentioned RedisCache object, which returns an instance of RedisCacheKey by utilizing the prefix from RedisCacheMetadata and keySerializer from RedisOperation.
private RedisCacheKey getRedisCacheKey(Object key) {
return new RedisCacheKey(key).usePrefix(this.cacheMetadata.getKeyPrefix())
.withKeySerializer(redisOperations.getKeySerializer());
}
By reaching this point, the "pre" advice of CacheInterceptor is completed, and it would go ahead to execute the actual method annotated by #Cacheable. And after completing the execution of the actual method, it will do the "post" advice of CacheInterceptor, which essentially put the result to RedisCache. Below is the method of putting the result to redis cache:
public void put(final Object key, final Object value) {
put(new RedisCacheElement(getRedisCacheKey(key), toStoreValue(value))
.expireAfter(cacheMetadata.getDefaultExpiration()));
}
/**
* Add the element by adding {#link RedisCacheElement#get()} at {#link RedisCacheElement#getKeyBytes()}. If the cache
* previously contained a mapping for this {#link RedisCacheElement#getKeyBytes()}, the old value is replaced by
* {#link RedisCacheElement#get()}.
*
* #param element must not be {#literal null}.
* #since 1.5
*/
public void put(RedisCacheElement element) {
Assert.notNull(element, "Element must not be null!");
redisOperations
.execute(new RedisCachePutCallback(new BinaryRedisCacheElement(element, cacheValueAccessor), cacheMetadata));
}
Within the RedisCachePutCallback object, its callback method doInRedis() actually invoke a method to form the actual key in redis, and the method name is getKeyBytes() from RedisCacheKey instance. Below shows the details of this method:
/**
* Get the {#link Byte} representation of the given key element using prefix if available.
*/
public byte[] getKeyBytes() {
byte[] rawKey = serializeKeyElement();
if (!hasPrefix()) {
return rawKey;
}
byte[] prefixedKey = Arrays.copyOf(prefix, prefix.length + rawKey.length);
System.arraycopy(rawKey, 0, prefixedKey, prefix.length, rawKey.length);
return prefixedKey;
}
As we can see in the getKeyBytes method, it utilizes both the raw key (vc:501381 in your case) and prefix key (\xac\xed\x00\x05t\x00\t in your case).
Use StringRedisTemplate to replace RedisTemplate.
By default, RedisTemplate uses Java serialization, StringRedisTemplate uses StringRedisSerializer.
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
You have to serialize teh objects that you are sending it to redis. Below is the complete running example of it. It uses interface DomainObject as Serializable
Below are the steps
1) make your maven pom.xml with following jars
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.0</version>
</dependency>
2) make your configuration xml as follows
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<bean id="jeidsConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="localhost" p:port="6379" p:password="" />
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jeidsConnectionFactory" />
<bean id="imageRepository" class="com.self.common.api.poc.ImageRepository">
<property name="redisTemplate" ref="redisTemplate"/>
</bean>
</beans>
3) Make your classes as follows
package com.self.common.api.poc;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class RedisMainApp {
public static void main(String[] args) throws IOException {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("mvc-dispatcher-servlet.xml");
ImageRepository imageRepository = (ImageRepository) applicationContext.getBean("imageRepository");
BufferedImage img = ImageIO.read(new File("files/img/TestImage.png"));
BufferedImage newImg;
String imagestr;
imagestr = encodeToString(img, "png");
Image image1 = new Image("1", imagestr);
img = ImageIO.read(new File("files/img/TestImage2.png"));
imagestr = encodeToString(img, "png");
Image image2 = new Image("2", imagestr);
imageRepository.put(image1);
System.out.println(" Step 1 output : " + imageRepository.getObjects());
imageRepository.put(image2);
System.out.println(" Step 2 output : " + imageRepository.getObjects());
imageRepository.delete(image1);
System.out.println(" Step 3 output : " + imageRepository.getObjects());
}
/**
* Decode string to image
* #param imageString The string to decode
* #return decoded image
*/
public static BufferedImage decodeToImage(String imageString) {
BufferedImage image = null;
byte[] imageByte;
try {
BASE64Decoder decoder = new BASE64Decoder();
imageByte = decoder.decodeBuffer(imageString);
ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
image = ImageIO.read(bis);
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
return image;
}
/**
* Encode image to string
* #param image The image to encode
* #param type jpeg, bmp, ...
* #return encoded string
*/
public static String encodeToString(BufferedImage image, String type) {
String imageString = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ImageIO.write(image, type, bos);
byte[] imageBytes = bos.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
imageString = encoder.encode(imageBytes);
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
return imageString;
}
}
package com.self.common.api.poc;
public class Image implements DomainObject {
public static final String OBJECT_KEY = "IMAGE";
public Image() {
}
public Image(String imageId, String imageAsStringBase64){
this.imageId = imageId;
this.imageAsStringBase64 = imageAsStringBase64;
}
private String imageId;
private String imageAsStringBase64;
public String getImageId() {
return imageId;
}
public void setImageId(String imageId) {
this.imageId = imageId;
}
public String getImageName() {
return imageAsStringBase64;
}
public void setImageName(String imageAsStringBase64) {
this.imageAsStringBase64 = imageAsStringBase64;
}
#Override
public String toString() {
return "User [id=" + imageAsStringBase64 + ", imageAsBase64String=" + imageAsStringBase64 + "]";
}
#Override
public String getKey() {
return getImageId();
}
#Override
public String getObjectKey() {
return OBJECT_KEY;
}
}
package com.self.common.api.poc;
import java.io.Serializable;
public interface DomainObject extends Serializable {
String getKey();
String getObjectKey();
}
package com.self.common.api.poc;
import java.util.List;
import com.self.common.api.poc.DomainObject;
public interface Repository<V extends DomainObject> {
void put(V obj);
V get(V key);
void delete(V key);
List<V> getObjects();
}
package com.self.common.api.poc;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import com.self.common.api.poc.DomainObject;
public class ImageRepository implements Repository<Image>{
#Autowired
private RedisTemplate<String,Image> redisTemplate;
public RedisTemplate<String,Image> getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate<String,Image> redisTemplate) {
this.redisTemplate = redisTemplate;
}
#Override
public void put(Image image) {
redisTemplate.opsForHash()
.put(image.getObjectKey(), image.getKey(), image);
}
#Override
public void delete(Image key) {
redisTemplate.opsForHash().delete(key.getObjectKey(), key.getKey());
}
#Override
public Image get(Image key) {
return (Image) redisTemplate.opsForHash().get(key.getObjectKey(),
key.getKey());
}
#Override
public List<Image> getObjects() {
List<Image> users = new ArrayList<Image>();
for (Object user : redisTemplate.opsForHash().values(Image.OBJECT_KEY) ){
users.add((Image) user);
}
return users;
}
}
For more reference on sprinf jedis you can see http://www.javacodegeeks.com/2012/06/using-redis-with-spring.html
Sample Code is taken from http://javakart.blogspot.in/2012/12/spring-data-redis-hello-world-example.html

Nhibernate many-to-many with extra column,

I am little bit new to NHibernate. I wish to implement a web application using asp.net using C#.
I have following database schemas:
Database Schemas
Here is my NHibernate Mapping file. I am not sure whether my mapping is correct or not. Please correct me if I made it wrong.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="TelDir.Core.Domain.Status, TelDir.Core" table="tblStatus" lazy="false">
<id name="ID" column="StatusID" unsaved-value="0">
<generator class="identity" />
</id>
<property name="StatusCode" column="StatusCode" />
<property name="StatusName" column="StatusName" />
<!--
<set name="WorkOrderStatus" table="tblWorkOrderStatus" inverse="true">
<key column="StatusID" />
<one-to-many class="TelDir.Core.Domain.WorkOrderStatus, TelDir.Core" />
</set>
-->
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="TelDir.Core.Domain.WorkOrder, TelDir.Core" table="tblWorkOrder" lazy="false">
<id name="ID" column="WOID" unsaved-value="0">
<generator class="identity" />
</id>
<property name="WorkOrderRef" column="WORef" />
<property name="WorkOrderDesc" column="WODesc" />
<set name="WorkOrderStatus" table="tblWorkOrderStatus" inverse="true">
<key column="WOID" />
<one-to-many class="TelDir.Core.Domain.WorkOrderStatus, TelDir.Core" />
</set>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="TelDir.Core.Domain.WorkOrderStatus, TelDir.Core" table="tblWorkOrderStatus" lazy="false">
<composite-id>
<key-many-to-one name="WorkOrder" column="WOID"/>
<key-many-to-one name="Status" column="StatusID"/>
</composite-id>
<property name="LastModifyDateTime" column="LastModifiedOn" type="Timestamp" />
<property name="CreatedBy" column="CreatedBy" />
</class>
</hibernate-mapping>
And my POCO class are presented as below
public class Status : DomainObject<Int16>
{
private string _statuscode = "";
private string _statusname = "";
//private ISet<WorkOrderStatus> _workorder_status = new HashedSet<WorkOrderStatus>() ;
public Status() { }
public Status(string statusCode, string statusName) {
this._statuscode = statusCode;
this._statusname = statusName;
}
public string StatusCode {
get { return _statuscode ; }
set { _statuscode = value; }
}
public string StatusName
{
get { return _statusname; }
set { _statusname = value; }
}
/*
public ISet<WorkOrderStatus> WorkOrderStatus
{
get { return (_workorder_status); }
protected set { _workorder_status = value; }
}
*/
}
public class WorkOrder : DomainObject<long>
{
private string _workorder_ref = "";
private string _workorder_desc = "";
private ISet<WorkOrderStatus> _workorder_status = new HashedSet<WorkOrderStatus>();
public WorkOrder() { }
public WorkOrder(string wref, string wdecs) {
this._workorder_ref = wref;
this._workorder_desc = wdecs;
}
public string WorkOrderRef {
get { return _workorder_ref ; }
set { _workorder_ref = value; }
}
public string WorkOrderDesc
{
get { return _workorder_desc; }
set { _workorder_desc = value; }
}
public ISet<WorkOrderStatus> WorkOrderStatus
{
get { return (_workorder_status); }
protected set { _workorder_status = value; }
}
public void AddStatus(Status st, DateTime dt)
{
WorkOrderStatus obj = new WorkOrderStatus();
obj.WorkOrder = this;
obj.Status = st;
obj.LastModifyDateTime = dt;
_workorder_status.Add(obj);
}
}
public class WorkOrderStatus
{
private DateTime _lastmodifydt;
private WorkOrder _workorder;
private Status _status;
private int _createdby;
public WorkOrderStatus() {
}
public DateTime LastModifyDateTime{
get { return _lastmodifydt; }
set { _lastmodifydt = value; }
}
public WorkOrder WorkOrder
{
get { return _workorder; }
set { _workorder = value; }
}
public Status Status
{
get { return _status; }
set { _status = value; }
}
public int CreatedBy {
get { return _createdby; }
set { _createdby = value; }
}
public override bool Equals(object other)
{
//if (this == other) return true;
//WorkOrderStatus obj = other as WorkOrderStatus;
//if (obj == null) return false; // null or not a cat
//if (_lastmodifydt != obj._lastmodifydt ) return false;
//return true;
if (other == null)
return false;
WorkOrderStatus t = other as WorkOrderStatus;
if (t == null)
return false;
if (WorkOrder == t.WorkOrder && Status == t.Status && _lastmodifydt == t.LastModifyDateTime )
return true;
return false;
}
public override int GetHashCode()
{
unchecked
{
int result;
result = _lastmodifydt.GetHashCode();
result = 29 * result + WorkOrder.GetHashCode() + Status.GetHashCode();
return result;
}
//return (WorkOrder.ID + "|" + Status.ID + "|" + Status.StatusName).GetHashCode();
}
}
I want my data present in tables like this:
[tblWorkOrderStatus]
StatusID WOID LastModifiedOn CreatedBy
--------------------------------------------------------------------------
2 1 06/20/2012 09:45:40.209 1
[tblWorkOrder]
WOID WORef WODesc
-------------------------------------------
1 001 Test-001
[tblStatus]
StatusID StatusCode StatusName
-----------------------------------------------
1 'X001' OPEN
2 'X002' CLOSE
What should I do to add record to [tblWorkOrderStatus]?
I have written test code as following but I found no record add in association table [tblWorkOrderStatus], I dont know why it does not added.
WorkOrder Wo = new WorkOrder('001', 'Test-001');
daoFactory.GetWorkOrderDao().Save(Wo);
Status St = daoFactory.GetStatusDao().GetById(1, false);
//// Secode Methode
WorkOrderStatus _objWS = new WorkOrderStatus();
_objWS.WorkOrder = Wo;
_objWS.Status = St;
_objWS.LastModifyDateTime = DateTime.Now;
_objWS.CreatedBy = 1; //suppose 1 is current login UserID
Wo.WorkOrderStatus.Add(_objWS);
daoFactory.GetWorkOrderDao().Save(Wo);
I might missing something in POCO, NHibernate mapping file, or somewhere else. Could you please guide me to the right solution?
Best regards,
Here is my stacktrace :
" at System.ThrowHelper.ThrowKeyNotFoundException()\r\n
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)\r\n
at NHibernate.Engine.StatefulPersistenceContext.RemoveEntity(EntityKey key)\r\n
at NHibernate.Action.EntityDeleteAction.Execute()\r\n
at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)\r\n
at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)\r\n
at NHibernate.Engine.ActionQueue.ExecuteActions()\r\n
at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)\r\n
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)\r\n
at NHibernate.Impl.SessionImpl.Flush()\r\n
at NHibernate.Transaction.AdoTransaction.Commit()\r\n
at TelDir.Data.NHibernateSessionManager.CommitTransaction()
in E:\\OLD PC\\D\\WORKS\\PROJECT\\TelDIR\\Data\\NHibernateSessionManager.cs:line 120\r\n
at TelDir.Web.NHibernateSessionModule.CommitAndCloseSession(Object sender, EventArgs e)
in e:\\OLD PC\\D\\WORKS\\PROJECT\\TelDIR\\Web\\App_Code\\NHibernateSessionModule.cs:line 38\r\n
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()\r\n
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)"
The main issue is that there is no cascade on WorkOrder.WorkOrderStatus, so NHibernate will not know to persist changes it finds in that collection when you save.
I changed WorkOrder.hbm.xml so the set looks like this:
<set name="WorkOrderStatus" table="tblWorkOrderStatus" inverse="true" cascade="all-delete-orphan">
<key column="StatusID" />
<one-to-many class="TelDir.Core.Domain.WorkOrderStatus, TelDir.Core" />
</set>
And then this test passed:
// Arrange
var workorder = new WorkOrder("001", "Test-001");
var status = new Status("1", "Status-1");
workorder.AddStatus(status, DateTime.Now);
WorkOrderStatus expected;
// Act
using (ISession session = _factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
session.Save(status);
session.SaveOrUpdate(workorder);
tx.Commit();
}
using (ISession session = _factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
expected = session.Query<WorkOrderStatus>()
.Fetch(s => s.Status)
.Fetch(s => s.WorkOrder)
.FirstOrDefault();
}
// Assert
expected.Should().NotBeNull();
expected.Status.Should().Be(status);
expected.WorkOrder.Should().Be(workorder);
Removing
Uncomment the ISet<WorkOrderStatus> WorkOrderStatus property on Status. Also, uncomment <set name="WorkOrderStatus" ... in Status.hbm.xml, and add the attribute cascade="all-delete-orphan" like you did on WorkOrder.
Add to WorkOrder:
public void RemoveStatus(WorkOrderStatus item)
{
if (!WorkOrderStatus.Contains(item)) return;
item.Status.WorkOrderStatus.Remove(item);
WorkOrderStatus.Remove(item);
}
Now, this test should pass:
// Arrange
var workorder = new WorkOrder("001", "Test-001");
var status = new Status("1", "Status-1");
workorder.AddStatus(status, DateTime.Now);
WorkOrderStatus expected;
// Act
using (ISession session = _factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
session.Save(status);
session.SaveOrUpdate(workorder);
tx.Commit();
}
using (ISession session = _factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
expected = session.Query<WorkOrderStatus>()
.Fetch(s => s.Status)
.Fetch(s => s.WorkOrder)
.FirstOrDefault();
expected.WorkOrder.RemoveStatus(expected);
tx.Commit();
}
using (ISession session = _factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
expected = session.Query<WorkOrderStatus>()
.Fetch(s => s.Status)
.Fetch(s => s.WorkOrder)
.FirstOrDefault();
}
// Assert
expected.Should().BeNull();