Thursday, October 27, 2011

JQuery and Spring

I have found that the proposed way of serializing and deserializing JSON to a controller commonly has ObjectMapper problems. I prefer to be totally in control of the POST body of the JSON message so I use this pattern instead:

First The JQuery Part, this method posts a JQUERY json model which is the options for a query, and then places the JSON results into a selectable list.

Now we build the controller that will parse that json request and create a JSON View back. This does the work of parsing the JSON and then generating a model that we sent to a template. Yes a template. Great. Now lets set up the MVC config and view and make sure that we map the extension ".json" to a velocity view. We will need a been that is extension away so we have total control over how our view serializes. Make sure to include the Velocity configuration. This pattern is about control. The view itself: And this is the custom resolver that lets us make sure we can serialize the way we want based on extension. Lets make our Velocity template now:

Friday, October 21, 2011

Find In Files

Find in files

find . | xargs grep 'string' -sl

find '.' -print0 | xargs -0 grep 'saveGeoPlace' -sl

Sunday, May 01, 2011

Apache Configs for AJP Load Balancing

Ok this configuration is going to save you hours of noodling if you just follow it to the T. lets say you have a WAR you want to deploy wars, and you want it to be the ROOT level. Well then take that foobar.war and deploy it as ROOT.war on your tomcat instance. Don't argue about this unless you want to spend many many hours on rewrite rules.

Then just follow this example below for your sites available in /etc/apache2, make sure rewrite and proxy are enabled in mods enabled... You can have more tomcat instances in your balancer if you wish.

<VirtualHost *:80>
 ServerName webtier.mydomain.com
 ServerAlias webtier.mydomain.com

 <Proxy balancer://mydomainCluster>
  BalancerMember ajp://10.211.79.225:8009 route=node1 loadfactor=1 
  ProxySet lbmethod=byrequests
    ProxySet stickysession=JSESSIONID
 </Proxy>
     ProxyPass / balancer://mydomainCluster stickysession=JSESSIONID
     ProxyPassReverse / balancer://mydomainCluster
     ProxyPreserveHost On

 # DON'T TURN ProxyRequests ON!  Bad things will happen
 # http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#access
 # http://www.akadia.com/services/prevent_abuse_proxy.html
 ProxyRequests off

 # Necessary to have mod_proxy_html do the rewriting
 RequestHeader      unset  Accept-Encoding

 # Rewrite the URLs to proxy ("[P]") into the Tomcat server
 RewriteEngine     on
 RewriteRule ^(/.*)      balancer://mydomainCluster$1    [P]

 # Be prepared to rewrite the HTML/CSS files as they come back
 # from Tomcat
 SetOutputFilter proxy-html

 # Rewrite JavaScript and CSS files in addition to HTML files
 ProxyHTMLExtended on

 # Output Strict XHTML (add "Legacy" to the end of the line below
 # to output Transitional XHTML)
 ProxyHTMLDoctype XHTML

 # alert, emerg.
 LogLevel warn
 ErrorLog /var/log/apache2/error.log
 CustomLog /var/log/apache2/access.log combined
 ServerSignature On
 Header unset Cache-Control
 Header set Cache-Control "max-age=3600, public"
</VirtualHost>

This came a full day of apache research, but there was one article that helped me make the final leap with the rewrite stuff.

http://dltj.org/article/apache-httpd-and-tomcat/

Friday, March 25, 2011

One of My Faves

Converting a Collection to an Array

// Create an array containing the elements in a list
Object[] objectArray = list.toArray();
MyClass[] array = (MyClass[])list.toArray(new MyClass[list.size()]);

// Create an array containing the elements in a set
objectArray = set.toArray();
array = (MyClass[])set.toArray(new MyClass[set.size()]);

// Create an array containing the keys in a map
objectArray = map.keySet().toArray();
array = (MyClass[])map.keySet().toArray(new MyClass[set.size()]);

// Create an array containing the values in a map
objectArray = map.values().toArray();
array = (MyClass[])map.values().toArray(new MyClass[set.size()]);

Sunday, February 20, 2011

JMS Template and Spring 3.0 Transactions

Ok Here is how you do it...

Configure you broker that you are going to embed. This can easily run outside the container as well.

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amq="http://activemq.apache.org/schema/core"
 xmlns:jms="http://www.springframework.org/schema/jms" xmlns:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="
  http://www.springframework.org/schema/jms 
        http://www.springframework.org/schema/jms/spring-jms.xsd 
  http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://activemq.apache.org/schema/core 
  http://activemq.apache.org/schema/core/activemq-core-5.2.0.xsd">
  
  <!-- Allows us to use system properties as variables in this configuration file --> 
  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> 
  
  
  <!--  lets create an embedded ActiveMQ Broker -->
 <amq:broker id="broker" useJmx="false" persistent="false">
  <amq:managementContext>
   <amq:managementContext connectorPort="1199" jmxDomainName="org.apache.activemq"/>
  </amq:managementContext>
  <amq:networkConnectors>
   <amq:networkConnector name="pool" uri="static:(tcp://localhost:61617)"/>
  </amq:networkConnectors>  
  <amq:transportConnectors>
   <amq:transportConnector uri="tcp://localhost:61616" />
  </amq:transportConnectors>  
 </amq:broker>

</beans> 

Then inside a config file that you will actually deploy as part of the container...
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amq="http://activemq.apache.org/schema/core"
 xmlns:jms="http://www.springframework.org/schema/jms" xmlns:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="
  http://www.springframework.org/schema/jms 
        http://www.springframework.org/schema/jms/spring-jms.xsd 
  http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://activemq.apache.org/schema/core 
  http://activemq.apache.org/schema/core/activemq-core-5.2.0.xsd">

 <bean id="broker" class="org.apache.activemq.xbean.BrokerFactoryBean">
     <property name="config" value="classpath:activemq-cfg.xml" />
     <property name="start" value="true" />
 </bean>
 
 <bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
     <property name="connectionFactory">
       <bean class="org.apache.activemq.ActiveMQConnectionFactory">
         <property name="brokerURL">
           <value>tcp://localhost:61616</value>
         </property>
       </bean>
     </property>
   </bean> 
</beans>

Then define your config for the consumer and listener.
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amq="http://activemq.apache.org/schema/core"
 xmlns:jms="http://www.springframework.org/schema/jms" xmlns:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="
  http://www.springframework.org/schema/jms 
        http://www.springframework.org/schema/jms/spring-jms.xsd 
  http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://activemq.apache.org/schema/core 
  http://activemq.apache.org/schema/core/activemq-core-5.2.0.xsd">

 <!--  ActiveMQ destinations to use  -->
 <amq:queue id="expiredOfferUpdateDestination" physicalName="com.sightlyinc.ratecred.offer.update" />
 
  <!-- Spring JMS Template -->
 <bean id="expiredOfferUpdateJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  <property name="defaultDestination">
   <ref bean="expiredOfferUpdateDestination" />
  </property>
  <property name="connectionFactory" ref="jmsFactory"/>
 </bean>

 <!-- this is the Message Driven POJO (MDP) -->
 <bean id="expiredOfferUpdateMessageListener"
  class="com.sightlyinc.ratecred.admin.jms.UpdateAwardOfferMessageListener">
  <property name="sessionFactory"><ref bean="ApplicationSessionFactory"/></property>
  <property name="mapper"><ref bean="jacksonMapper"/></property>
 </bean>
 
 <!-- and this is the message listener container -->
 <bean id="expiredOfferUpdateContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
     <property name="connectionFactory" ref="jmsFactory"/>
     <property name="destination" ref="expiredOfferUpdateDestination"/>
     <property name="messageListener" ref="expiredOfferUpdateMessageListener" />
     <property name="sessionTransacted" value="true"/>
     <property name="transactionManager" ref="ApplicationTransactionManager"/>     
 </bean> 
</beans>

And then the implementations for the producer and the listener..

Producer
package com.sightlyinc.ratecred.admin.jms;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;

import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;

import com.sightlyinc.ratecred.model.Award;
import com.sightlyinc.ratecred.model.AwardType;
import com.sightlyinc.ratecred.model.Rater;

@Component("updateAwardOfferMessageProducer")
public class UpdateAwardOfferMessageProducer {
 
    static Logger logger = Logger.getLogger(UpdateAwardOfferMessageProducer.class);
    
    @Autowired
    @Qualifier("expiredOfferUpdateJmsTemplate")
    private JmsTemplate expiredOfferUpdateJmsTemplate;
    
    @Autowired
    @Qualifier("jacksonMapper")
    private ObjectMapper jacksonMapper;

    public void generateMessage(Award award, AwardType awardType, Rater r) 
     throws JMSException, JsonGenerationException, JsonMappingException, IOException {
      logger.debug("generating message");     
         Map awardToSaveData = new HashMap();
         awardToSaveData.put("raterId", r.getId());
            awardToSaveData.put("award", award);
      
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
      
         jacksonMapper.writeValue(baos, awardToSaveData);
      
            final String text = baos.toString();

            expiredOfferUpdateJmsTemplate.send(new MessageCreator() {
                public Message createMessage(Session session) throws JMSException {
                    TextMessage message = session.createTextMessage(text);                 
                    return message;
                }
            });
    }
}

Listener
package com.sightlyinc.ratecred.admin.jms;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Map;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import com.noi.utility.spring.service.BLServiceException;
import com.sightlyinc.ratecred.model.Award;
import com.sightlyinc.ratecred.model.AwardType;
import com.sightlyinc.ratecred.model.Rater;

public class UpdateAwardOfferMessageListener implements MessageListener {
    static Logger logger = Logger.getLogger(UpdateAwardOfferMessageListener.class);
    private SessionFactory sessionFactory;
    private ObjectMapper mapper;
 
    /**
    * Implementation of <code>MessageListener</code>.
    */
    public void onMessage(Message message) {
        try {
            logger.debug("==== onMessage");
            if (message instanceof TextMessage) {
                TextMessage tm = (TextMessage)message;
                //deserialize the json
                String msgText = tm.getText();
                logger.debug(msgText);
                Map<String,Object> userData =
                mapper.readValue(
                new ByteArrayInputStream(
                msgText.getBytes()), Map.class);
                Long awardTypePk =
                new Long(userData.get("awardTypeId").toString());
                Long raterPk =
                new Long(userData.get("raterId").toString());
                AwardType awardType =   awardManagerService.findAwardTypeByPrimaryKey(awardTypePk);
                Map<String,Object> awardModel = (Map<String,Object>)userData.get("award");
                
            }
            } catch (JMSException e) {
            logger.error(e.getMessage(), e);
            } catch (IOException e) {
            logger.error(e.getMessage(), e);
            } catch (BLServiceException e) {
            logger.error(e.getMessage(), e);
            } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
            } finally {
        }
    }
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
    public void setMapper(ObjectMapper mapper) {
        this.mapper = mapper;
    }
}

Tuesday, January 11, 2011

Cache-Control and iPhone 4.1 Safari

If you want to use cookies in WebKit applications on iPhone 4.1 or earlier you will need to know about this little trick. Basically Safari for iPhone in 4.1 has caching issues with cookies and you will need to disable proxy caching and securePagesWithPragma to make those earlier browsers not have problems with cookies that you may want to use for OAuth.

Big Overview on Caching Headers

Here's the tomcat config:

<Context path="/" docBase="/root/webapps/ratecred_20110108" reloadable="true">
<Valve className="org.apache.catalina.authenticator.BasicAuthenticator"
disableProxyCaching="false" securePagesWithPragma="false"/>
</Context>


Also you ned to to check the headers generated from Apache, I found that apache was automatically setting the Cache-Control header to no-cache="set-cookie", this also cause specific problems with cookie caching in 4.1 versions of Safari on iPhone in order to accommodate this issue I would use the following command:

lwp-request -e -d http://foobar.com


This returns a list of headers for the request so I can see how apache is adding headers:

claygraham@h-34-0-36-125:~$ lwp-request -e -d http://ratecred.com
Cache-Control: no-cache="set-cookie"
Connection: Close
Date: Thu, 13 Jan 2011 05:59:33 GMT
Vary: Accept-Encoding
Content-Language: en-US
Content-Type: text/html;charset=UTF-8
Content-Type: text/html;charset=UTF-8
Client-Date: Thu, 13 Jan 2011 05:59:34 GMT
Client-Peer: 50.16.212.144:80
Client-Response-Num: 1
Client-Transfer-Encoding: chunked
Link: ; media="screen, projection"; rel="stylesheet"; type="text/css"
Link: ; media="print"; rel="stylesheet"; type="text/css"
Link: ; media="screen, projection"; rel="stylesheet"; type="text/css"
Link: ; rel="stylesheet"; type="text/css"
Link: ; /="/"; rel="stylesheet"; type="text/css"
Link: ; rel="stylesheet"; type="text/css"
Link: ; /="/"; rel="stylesheet"; type="text/css"
Link: ; /="/"; rel="shortcut icon"
Set-Cookie: JSESSIONID=73B7B4606A5F99D37378DFA21E475942; Path=/
Set-Cookie: AWSELB=3505DFB9122FAFC80483E17CBEB5E23D24546B00A71218A5BAE3B79F14317437BEAEDA7FEC501E9826163E4BE26AAE3DE36A6B6A375DB7C3AEF0E17F41A873610F8EA3D068;MAX-AGE=600
Title: RateCred Home
X-Meta-Description: RateCred Home Page, Rate places earn credit. What if you mashed up yelp, foursquare and twitter on Android phones?
X-Meta-Generator: RateCred
X-Meta-Google-Site-Verification: u9YkTj5gr6aeYBst1Aac-B_5cCvJe_Ataauqep_EwEE
X-Meta-Googlebot: index,follow
X-Meta-Refresh: 20
X-Meta-Robots: index,follow


so to modify the header I add to httpd.conf

Header unset Cache-Control
Header set Cache-Control "max-age=3600, public"

Monday, January 10, 2011

Article on Fuzzy Matches

Lexical parsing with match ranking. This is really good for fuzzy matches between multiple databases. I will probably add the code for this which I have written so come back.

http://www.catalysoft.com/articles/StrikeAMatch.html

Great Article on Writing JavaScript Plugins

Really love this article. I just wish he had a link to his libs javascript. Looks like this could be a good jQuery plugin.

http://peter.michaux.ca/articles/how-i-write-javascript-widgets

Sunday, January 09, 2011

Sencha Howto: Templated Ajax View

I am making this available as a HOWTO because I could not find an example that brings all of this together, but I think its something that many developers probably want to do. This is a single view with its own template driven from an Ajax request. A specific id is passed to the view to drive the request. Hope this helps.


ratecred.views.RatingView = Ext.extend(Ext.Panel, {
fullscreen: true,
dockedItems: {
xtype: 'publicToolbar',
},
layout: {
type: 'fit'
},
initComponent: function() {

var ratingId = this.ratingId;

var mainPanel = new Ext.Panel({
html: 'Rating View'
});

Ext.apply(this, {
items:[ mainPanel ]
});
ratecred.views.RatingView.superclass.initComponent.apply(this, arguments);

var ratingTemplate = new Ext.XTemplate(
'<div class="margin-10 rating">',
'<table width="100%">',
'<tr>',
'<td width="48" valign="top"><img src="{owner.raterImage.filename}" width="48" height="48" class="img-align-left"/></td>',
'<td valign="top"><div class="place-name">{place.name}</div></td>',
'<td width="48" valign="top"><div class="value text-16">{raterRating}</div></td>',
'</tr>',
'<tr>',
'<td colspan="3"><div class="notes">{notes}</div></td>',
'</tr>',
'<tpl for="attributes">',
'<tr>',
'<td colspan="3"><div class="attribute">{type}, {name}</div></td>',
'</tr>',
'</tpl>',
'<tpl for="compliments">',
'<tr>',
'<td colspan="3"><div class="compliment"><img src="{owner:this.getProfileImageUrl}" width="48" height="48" class="img-align-left"/> owner:{owner.userName} {note}</div></td>',
'</tr>',
'</tpl>',
'</table>',

'</div>',

{
compiled: true,
getProfileImageUrl: function(rater){
if(rater.raterImage.type=='S3REPO')
return 'http://media.servicetattler.com/web/iv/'+rater.raterImage.filename;
else
return rater.raterImage.filename;
},

}
);

Ext.getBody().mask('Loading...', 'x-mask-loading', false);

//rating id is set at create time
Ext.Ajax.request({
url: 'http://dev.ratecred.com/v2/rating/'+ratingId+'.json',
success: function(response, opts) {
var reader = new Ext.data.JsonReader({
model: 'Rating'
});
var data = reader.getResponseData(response);
ratingTemplate.overwrite(mainPanel.body, data);
Ext.getBody().unmask();
}
});

},
});

Ext.reg('ratingView', ratecred.views.RatingView);