Wednesday, December 31, 2008

Proxy solution: Load Google Maps for https for JSP/JAVA/Servlet

It is pretty challenge to display Google Maps in Https (SSL), as so
far Google only support to display map in Http (no secure site)

The issue: The IE will popup a warning message when accessing Google
map in SSL mode to indicate that the page contains both secure and
nonsecure items.

The solution: use proxy. It applies for both Enterprise user and non-
Enterprise user

Algorithm:
1. Call http://maps.google.com/maps?file=api&v=2&key= <your
key>" in your backend server. Make sure you pass in User Agent.
2. Get content type from the content you get with above URL.
3. if content type is text then
append all google links with yourProxyPath
send text content back to user.
else
send binary content back to user.
end

Sample code: written in JSP/Java/Servlet
1. In JSP where you have Google Map.
Create two keys: one for secure and one for non secure. For
enterprise user, both would be the same.
<%
String requestHttpsUrl, requestHttpUrl;
requestHttpsUrl = "http://maps.google.com/maps?
file=api&v=2&key=<secure key>";
requestHttpUrl="http://maps.google.com/maps?
file=api&v=2&key=<non secure key> ";
String scheme = request.getScheme();
%>

<% if ("https".equalsIgnoreCase(scheme)) { %>
<script src="/<yourProxyPath>?<%=requestHttpsUrl%>" type="text/
javascript"></script>

<% } else { %>
<script src="<%=requestHttpUrl%>" type="text/javascript"></
script>
<%}%>

Where yourProxyPath is the path for your Sevlet or other path where
you put proxy logic in. For me, it is /location/googleproxy

2. create Servlet as proxy, I used Httpclient as remote connector.
Note: for binary data, normally it is image, there are two ways to
deal with it.
-- redirect : response.sendRedirect(requestUrl);
-- construct ServletOutputStream

public void service_httpclient_connection(HttpServletRequest request,
HttpServletResponse response) throws ServletException,
java.io.IOException {
String requestUrl = request.getQueryString();

String user_agent = request.getHeader("User-Agent");

HttpClient client = new HttpClient();
HttpMethod method = new GetMethod(requestUrl);

method.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
method.addRequestHeader("User-Agent", user_agent);

try {
// Execute the method.
client.getParams().setBooleanParameter(
HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, false);
int statusCode = client.executeMethod(method);

if (statusCode == HttpStatus.SC_OK) {
String contentType =
method.getResponseHeader("Content-type").getValue();

if (isTextContentType(contentType)) {
// google content is text

InputStream mapScript = method.getResponseBodyAsStream();
String resultScript = convertInputStreamToString(mapScript);
mapScript.close();

// replace http: with proxy
if (resultScript.indexOf("<yourProxyPath>") == -1) {
resultScript =
resultScript.replaceAll("http:","/
<yourProxyPath>?http:");
}

PrintWriter out = response.getWriter();
out.println(resultScript);
out.flush();
} else {
//google content is binary

// that is the easy way
response.sendRedirect(requestUrl);
}
}
} finally {
// Release the connection.
method.releaseConnection();
}

}

private boolean isTextContentType(String contentType) {
return (contentType.indexOf("text") != -1);

}

Also, you can replace response.sendRedirect(requestUrl); with
following code
byte[] retBytes = method.getResponseBody();
if (retBytes != null) {
response.setContentType(contentType);
response.setContentLength(retBytes.length);
ServletOutputStream output = response.getOutputStream();
output.write(retBytes);
output.flush();
output.close();

You can view example
http://www.allhotelmotel.com/location/httpsproxy.htm

Post in Google Map Group:
http://groups.google.com/group/Google-Maps-API/browse_thread/thread/992743110e3f811f/d8e33c1292655469?lnk=gst&q=michael+w#d8e33c1292655469

Java: pattern matching

private void testPattern() {
String url = "/h/d/hi/280/en/hd/atlcp?tags=true";
// Pattern p = Pattern.compile( "(/hld/hi/en/hd/)[^ ]*" );
// Pattern p = Pattern.compile( "(/h/d/[a-z][a-z]/[a-z][a-z]/hd/)[^ ]*"
// );
Pattern p = Pattern
.compile("(/h/d/[a-z][a-z]/\\d+/[a-z][a-z]/hd/)([^ ])+");
Matcher m = p.matcher(url);
boolean found = m.matches();
System.out.println(found);

if (found) {
System.out.println("m.groupCount()=" + m.groupCount());
System.out.println(m.group(2));
System.out.println(m.group());
}
}

convert String like 123,444.00 with comma to double

// First, check whether the String is valid integer

/**
* Valid if string is valid number
* 12345.55
* 12,345.55
*
* @param pValue
* @return
*/
public static boolean isValidNumber(String pValue) {
if (!isSet(pValue)) {
return false;
}

try {
NumberFormat nfDLocal = NumberFormat.getNumberInstance();
ParsePosition ppos = new ParsePosition(0);
nfDLocal.parse(pValue, ppos);

if (ppos.getIndex() == pValue.length()) {
return true;
}
else {
return false;
}
}
catch (Throwable e) {
return false;
}
}

// Seconde.
/**
* Convert string to double, make sure call isValidNumber first before calling this method
* @param pValue
* @return
*/
public static double toDouble(String pValue) {
NumberFormat nfDLocal = NumberFormat.getNumberInstance();
ParsePosition ppos = new ParsePosition(0);
Number num = nfDLocal.parse(pValue, ppos);

return num.doubleValue();
}

Resin 3.1.x -- Could not debug with resin 3.1.2

After migrating resin from 3.0.23 to 3.1.2, the setting for debug is no longer works.

Here is what we found:

Before 3.0.23, we passed debug setting to command line for resin start script:
-Xdebug -Xnoagent -J-Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=n

Then in Eclipse, goto Run->Open Debug Dialog-->Remote Application.
Click new and make sure the project is set to the application you want to debug. Connection type should be "Standard (Socket Attach)", and your connection host should be "localhost" with the port "8787".


After 3.1.x, resin change the approach to take Java argument inside resin.conf
So for debug after 3.1.x, do this in resin.conf

-Xdebug
-Xnoagent
-Djava.compiler=NONE
-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8787

Window XP, how to reboot a win XP box remotely

open DOS command window and type in:

SHUTDOWN -r -t 01

wait 10 to 15 minutes, you will get your remote machine rebooted.

Google Maps API - dynamically calculate zoom level for clusters of markers

If you have various points and want to display them all.
Here is one way to display it.



<table border="0" cellpadding="0" cellspacing="0" width="100%" >
<%-- 620 * 300 --%>
<tr>
<td valign="top"> <div id="map" style="width: 450px; height: 300px"></div> <td>
</tr>

</table>


<script type="text/javascript">
//<![CDATA[

var gmarkers = [];
var htmls = [];
var i = 1;
var minLatlng, maxLatlng;



/**
* Calculate boundary to contains all the markers around given center.
* find anti-symmetry again mapCenter of lat and lng
*
*/
function bestFitWithCenter(map, bounds, mapCenter) {
var swLL = bounds.getSouthWest();
var neLL = bounds.getNorthEast();


//leave margin each side
var marginRatio = 0.0001;

var minLat = Math.min(2*mapCenter.lat() - neLL.lat(), swLL.lat());
var maxLat = Math.max(2*mapCenter.lat() - swLL.lat(), neLL.lat());
var minLng = Math.min(2*mapCenter.lng() - neLL.lng(), swLL.lng());
var maxLng = Math.max(2*mapCenter.lng() - swLL.lng(), neLL.lng());

//GLog.write("minLat="+minLat + ", minLng="+minLng);
//GLog.write("maxLat="+maxLat + ", maxLng="+maxLng);


var minLatLng = new GLatLng(minLat-marginRatio, minLng-marginRatio);
var maxLatLng = new GLatLng(maxLat+marginRatio, maxLng+marginRatio);

bounds.extend(maxLatLng);
bounds.extend(minLatLng);

map.setZoom(map.getBoundsZoomLevel(bounds));
map.setCenter(mapCenter);
}

/**
* Display all the markers and calculate center of all markers
*
*/
function bestFit(map, bounds) {
map.setZoom(map.getBoundsZoomLevel(bounds));
map.setCenter(bounds.getCenter());
}

function onLoad0() {

if (GBrowserIsCompatible()) {
var bounds = new GLatLngBounds();
var map = new GMap2(document.getElementById("map"));
var mapCenter = new GLatLng(34.020545, -84.248765);
map.setCenter(mapCenter, 12);

var widthHeight = map.getSize().toString().split(',');
var width = widthHeight[0];
var heitht = widthHeight[1];
//GLog.write("width="+width + ", heitht="+heitht);

map.addControl(new GSmallMapControl());
map.addControl(new GScaleControl());
map.addControl(new GMapTypeControl());


//add center marker
var marker = new GMarker(new GLatLng(34.020545, -84.248765));
map.addOverlay(marker);
//GLog.write(map.getZoom());
bounds.extend(marker.getPoint());

marker = new GMarker(new GLatLng(34.02898, -84.25421));
map.addOverlay(marker);
bounds.extend(marker.getPoint());


marker = new GMarker(new GLatLng(34.04898, -84.24421));
map.addOverlay(marker);
bounds.extend(marker.getPoint());

marker = new GMarker(new GLatLng(34.11898, -83.99421));
map.addOverlay(marker);
bounds.extend(marker.getPoint());

marker = new GMarker(new GLatLng(34.01898, -84.22421));
map.addOverlay(marker);
bounds.extend(marker.getPoint());


//bestFitWithCenter(map, bounds, mapCenter);
bestFit(map, bounds);

GLog.write(map.getZoom());

}

}
//]]>
</script>

Resin 3.1.x -- could not reload jsp page once JSP file changes

After migrating to resin 3.1.2, we found that the changes in JSP was not taking affect after refresh .

Found out that
plays the trick.

Here is the result:
For development, 2s
For deployment, 1h or 1d would be appropriate.

Google Maps API - dynamically calculate zoom level for clusters of markers with given center

It happens when searching hotels for a specific address. For example, find all hotels around Atlanta Airport.
Through Google search, did not find a good solution, so I create one, following is the sample code.
For non JSP developer, you can remove JSP comments which inside of <%-- --%>



<table border="0" cellpadding="0" cellspacing="0" width="100%" >
<%-- 620 * 300 --%>
<tr>
<td valign="top"> <div id="map" style="width: 450px; height: 300px"></div> <td>
</tr>

</table>
<script type="text/javascript">
//<![CDATA[

var gmarkers = [];
var htmls = [];
var i = 1;
var minLatlng, maxLatlng;


/**
* Calculate boundary to contains all the markers around given center.
* find anti-symmetry again mapCenter of lat and lng
*
*/
function bestFitWithCenter(map, bounds, mapCenter) {
var swLL = bounds.getSouthWest();
var neLL = bounds.getNorthEast();


//leave margin each side
var marginRatio = 0.0001;

var minLat = Math.min(2*mapCenter.lat() - neLL.lat(), swLL.lat());
var maxLat = Math.max(2*mapCenter.lat() - swLL.lat(), neLL.lat());
var minLng = Math.min(2*mapCenter.lng() - neLL.lng(), swLL.lng());
var maxLng = Math.max(2*mapCenter.lng() - swLL.lng(), neLL.lng());

//GLog.write("minLat="+minLat + ", minLng="+minLng);
//GLog.write("maxLat="+maxLat + ", maxLng="+maxLng);


var minLatLng = new GLatLng(minLat-marginRatio, minLng-marginRatio);
var maxLatLng = new GLatLng(maxLat+marginRatio, maxLng+marginRatio);

bounds.extend(maxLatLng);
bounds.extend(minLatLng);

map.setZoom(map.getBoundsZoomLevel(bounds));
map.setCenter(mapCenter);
}

/**
* Display all the markers and calculate center of all markers
*
*/
function bestFit(map, bounds) {
map.setZoom(map.getBoundsZoomLevel(bounds));
map.setCenter(bounds.getCenter());
}

function onLoad0() {

if (GBrowserIsCompatible()) {
var bounds = new GLatLngBounds();
var map = new GMap2(document.getElementById("map"));
var mapCenter = new GLatLng(34.020545, -84.248765);
map.setCenter(mapCenter, 12);

var widthHeight = map.getSize().toString().split(',');
var width = widthHeight[0];
var heitht = widthHeight[1];
//GLog.write("width="+width + ", heitht="+heitht);

map.addControl(new GSmallMapControl());
map.addControl(new GScaleControl());
map.addControl(new GMapTypeControl());


//add center marker
var marker = new GMarker(new GLatLng(34.020545, -84.248765));
map.addOverlay(marker);
//GLog.write(map.getZoom());
bounds.extend(marker.getPoint());

marker = new GMarker(new GLatLng(34.02898, -84.25421));
map.addOverlay(marker);
bounds.extend(marker.getPoint());


marker = new GMarker(new GLatLng(34.04898, -84.24421));
map.addOverlay(marker);
bounds.extend(marker.getPoint());

marker = new GMarker(new GLatLng(34.11898, -83.99421));
map.addOverlay(marker);
bounds.extend(marker.getPoint());

marker = new GMarker(new GLatLng(34.01898, -84.22421));
map.addOverlay(marker);
bounds.extend(marker.getPoint());


bestFitWithCenter(map, bounds, mapCenter);

GLog.write(map.getZoom());

}

}
//]]>
</script>

java url encode, get rid of white space from url

Sometime we need to encode URL to the application/x-www-form-urlencoded MIME format.

public static String encode(String s,
String enc)
throws UnsupportedEncodingException
Translates a string into application/x-www-form-urlencoded format using a specific encoding scheme. This method uses the supplied encoding scheme to obtain the bytes for unsafe characters.


For example of encoding with UTF-8, using following code:
String encodeTargetUrl = URLEncoder.encode(targetUrl, "UTF-8");

Java, convert InputStream to String

Convert InputStream to String

public String converInputStreamToString(InputStream inputStream) throws IOException{
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();

byte[] b = new byte[4096];
int len = 0;

while ((len = inputStream.read(b)) > 0)
{
byteStream.write(b, 0, len);
}
String outputString = byteStream.toString("UTF-8");

return outputString;
}

Another method:
private String convertToString(InputStream is) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));

StringBuffer sbuf = new StringBuffer();
String line = null;
while ((line = reader.readLine()) != null) {
if (line.startsWith( "#" )) {
continue;
}

if (!StringUtils.isSet( line )) {
continue;
}

sbuf.append( line );
}

return sbuf.toString();
}

Welcome

Welcome to view my blogger.