Inital commit
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
/* Copyright (c) 2006 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.gbase.recipe;
|
||||
|
||||
import com.google.api.gbase.client.FeedURLFactory;
|
||||
import com.google.api.gbase.client.GoogleBaseService;
|
||||
import com.google.gdata.client.http.AuthSubUtil;
|
||||
import com.google.gdata.util.AuthenticationException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.MalformedURLException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Provides an authenticated GoogleBaseService
|
||||
* to the servlet that will process the request.
|
||||
*/
|
||||
public class AuthenticationFilter implements Filter {
|
||||
|
||||
/**
|
||||
* The request attribute that contains the authenticated service.
|
||||
*/
|
||||
static final String SERVICE_ATTRIBUTE = "googleBaseService";
|
||||
|
||||
/**
|
||||
* The parameter used by AuthSub to specify the authentication token.
|
||||
* Also used as the name of the http session attribute that contains
|
||||
* the session token.
|
||||
*/
|
||||
static final String TOKEN_PARAMETER = "token";
|
||||
|
||||
static final String TOKEN_COOKIE_NAME = "AuthSubSessionToken";
|
||||
|
||||
static final String DEFAULT_AUTHSUB_PROTOCOL = "https";
|
||||
static final String DEFAULT_AUTHSUB_HOSTNAME = "www.google.com";
|
||||
|
||||
protected String authsubProtocol;
|
||||
protected String authsubHostname;
|
||||
|
||||
protected FeedURLFactory urlFactory;
|
||||
|
||||
protected String applicationName;
|
||||
|
||||
/**
|
||||
* Developer key, used for identification against the Google Base data
|
||||
* API servers.
|
||||
*/
|
||||
protected String key;
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
servletContext = filterConfig.getServletContext();
|
||||
key = servletContext.getInitParameter(RecipeUtil.DEVELOPER_KEY_PARAMETER);
|
||||
if (key == null || "".equals(key.trim())) {
|
||||
String errorMessage = "No developer key specified.\n Please edit " +
|
||||
"web.xml and add your developer key in the \"key\" context " +
|
||||
"parameter. \n You can obtain a developer key at: \n\t" +
|
||||
"http://code.google.com/api/base/signup.html";
|
||||
System.err.println(errorMessage);
|
||||
throw new ServletException(errorMessage);
|
||||
}
|
||||
|
||||
urlFactory = (FeedURLFactory)
|
||||
servletContext.getAttribute(RecipeListener.FEED_URL_FACTORY_ATTRIBUTE);
|
||||
authsubProtocol = filterConfig.getInitParameter("authsubProtocol");
|
||||
if (authsubProtocol == null) {
|
||||
authsubProtocol = DEFAULT_AUTHSUB_PROTOCOL;
|
||||
}
|
||||
authsubHostname = filterConfig.getInitParameter("authsubHostname");
|
||||
if (authsubHostname == null) {
|
||||
authsubHostname = DEFAULT_AUTHSUB_HOSTNAME;
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
servletContext = null;
|
||||
urlFactory = null;
|
||||
applicationName = null;
|
||||
authsubProtocol = null;
|
||||
authsubHostname = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts or stops an authenticated session, depending on the value
|
||||
* of the {@value #TOKEN_PARAMETER} parameter, or provides the servlets
|
||||
* with an authenticated
|
||||
* {@link com.google.api.gbase.client.GoogleBaseService GoogleBaseService},
|
||||
* during an authenticated session.
|
||||
*/
|
||||
public void doFilter(ServletRequest request,
|
||||
ServletResponse response,
|
||||
FilterChain filterChain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
String oneTimeToken = httpRequest.getParameter(TOKEN_PARAMETER);
|
||||
String sessionToken = getSessionTokenCookie(httpRequest);
|
||||
|
||||
if (oneTimeToken != null) {
|
||||
if ("".equals(oneTimeToken)) {
|
||||
// Revoke the session token
|
||||
if (sessionToken != null) {
|
||||
stopAuthenticatedSession(httpRequest, httpResponse, sessionToken);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Convert the token to a session token and keep it
|
||||
try {
|
||||
startAuthenticatedSession(httpRequest, httpResponse, oneTimeToken);
|
||||
return;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new ServletException(e);
|
||||
} catch (AuthenticationException e) {
|
||||
// Log and then continue as if this token was not there.
|
||||
// (It was probably bookmarked.)
|
||||
servletContext.log("Invalid one-time token", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request a new token if we don't have one at this point
|
||||
if (sessionToken == null) {
|
||||
redirectToAuthSub(httpRequest, httpResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a service that authenticates using the session token
|
||||
GoogleBaseService service = new GoogleBaseService(
|
||||
applicationName, key, authsubProtocol, authsubHostname);
|
||||
service.setAuthSubToken(sessionToken);
|
||||
|
||||
// Make the service available to the servlet
|
||||
httpRequest.setAttribute(SERVICE_ATTRIBUTE, service);
|
||||
|
||||
// Execute the servlet
|
||||
try {
|
||||
filterChain.doFilter(request, response);
|
||||
} catch(ServletException e) {
|
||||
Throwable cause = e.getRootCause();
|
||||
if (cause instanceof AuthenticationException &&
|
||||
!response.isCommitted()) {
|
||||
// Token has been revoked. Re-run AuthSub.
|
||||
redirectToAuthSub(httpRequest, httpResponse);
|
||||
} else {
|
||||
// Let the exception be handled as usual.
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AuthSub session token from a cookie.
|
||||
*
|
||||
* @param httpRequest
|
||||
* @return session token or null
|
||||
*/
|
||||
String getSessionTokenCookie(HttpServletRequest httpRequest) {
|
||||
Cookie[] cookies = httpRequest.getCookies();
|
||||
if (cookies == null) {
|
||||
return null;
|
||||
}
|
||||
for (Cookie cookie : cookies) {
|
||||
if (cookie.getName().equals(TOKEN_COOKIE_NAME)) {
|
||||
return cookie.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes the specified session token and redirects the browser to
|
||||
* the context of the request.
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @param sessionToken a session token
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
private void stopAuthenticatedSession(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
String sessionToken)
|
||||
throws IOException, ServletException {
|
||||
if (sessionToken != null) {
|
||||
revokeSessionToken(sessionToken);
|
||||
clearSessionTokenCookie(request, response);
|
||||
}
|
||||
String url = request.getContextPath();
|
||||
response.sendRedirect(response.encodeRedirectURL(url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Force cookie expiration by sending an expired cookie to
|
||||
* the browser.
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
*/
|
||||
private void clearSessionTokenCookie(HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws ServletException {
|
||||
response.addCookie(newExpiredSessionTokenCookie(request));
|
||||
}
|
||||
|
||||
private Cookie newExpiredSessionTokenCookie(HttpServletRequest request)
|
||||
throws ServletException {
|
||||
Cookie cookie = newSessionTokenCookie(request, "");
|
||||
cookie.setMaxAge(0);
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitely revoke the session token.
|
||||
*
|
||||
* @param token
|
||||
* @throws IOException
|
||||
* @throws ServletException
|
||||
*/
|
||||
protected void revokeSessionToken(String token) throws IOException, ServletException
|
||||
{
|
||||
try {
|
||||
AuthSubUtil.revokeToken(authsubProtocol, authsubHostname, token, null);
|
||||
} catch (AuthenticationException e) {
|
||||
throw new ServletException(e);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchanges the single use token for a session token and
|
||||
* redirects the browser to the same address with the
|
||||
* token specification part removed.
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @param oneTimeToken a single use AuthSub token
|
||||
* @throws IOException
|
||||
* @throws GeneralSecurityException
|
||||
* @throws AuthenticationException if the one-time token is invalid
|
||||
*/
|
||||
private void startAuthenticatedSession(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
String oneTimeToken)
|
||||
throws IOException, GeneralSecurityException, AuthenticationException,
|
||||
ServletException {
|
||||
String sessionToken = exchangeForSessionToken(oneTimeToken);
|
||||
|
||||
// Store the authentication token in a cookie
|
||||
response.addCookie(newSessionTokenCookie(request, sessionToken));
|
||||
|
||||
// Redirect the browser to the same address
|
||||
// with the "token=value" part removed from the query string
|
||||
StringBuffer url = request.getRequestURL();
|
||||
String queryString = request.getQueryString();
|
||||
if (queryString != null) {
|
||||
queryString = queryString.replaceFirst("token=[^&]*&?", "");
|
||||
if (queryString.length() > 0) {
|
||||
url.append("?").append(queryString);
|
||||
}
|
||||
}
|
||||
response.sendRedirect(response.encodeRedirectURL(url.toString()));
|
||||
}
|
||||
|
||||
protected Cookie newSessionTokenCookie(HttpServletRequest request,
|
||||
String sessionToken)
|
||||
throws ServletException {
|
||||
Cookie cookie = new Cookie(TOKEN_COOKIE_NAME, sessionToken);
|
||||
// AuthSub session tokens effectively don't expire. Hang on to it.
|
||||
// If the AuthSub token is revoked, the filter will request
|
||||
// a new token.
|
||||
cookie.setMaxAge(365*24*60*60);
|
||||
cookie.setPath(request.getContextPath() + "/");
|
||||
try {
|
||||
cookie.setDomain(new URL(request.getRequestURL().toString()).getHost());
|
||||
} catch(MalformedURLException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
|
||||
// Cookie domain set automatically by the server.
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a one-time token into a reusable session token.
|
||||
* @param oneTimeToken
|
||||
* @throws IOException
|
||||
*/
|
||||
protected String exchangeForSessionToken(String oneTimeToken)
|
||||
throws IOException, GeneralSecurityException, AuthenticationException {
|
||||
return AuthSubUtil.exchangeForSessionToken(authsubProtocol,
|
||||
authsubHostname,
|
||||
oneTimeToken,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to the AuthSub authentication page.
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @throws IOException
|
||||
*/
|
||||
private void redirectToAuthSub(HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws IOException {
|
||||
StringBuffer next = request.getRequestURL();
|
||||
String queryString = request.getQueryString();
|
||||
if (queryString != null && !"".equals(queryString) ) {
|
||||
next.append("?").append(queryString);
|
||||
}
|
||||
String scope = new URL(urlFactory.getBaseURL(), "feeds").toExternalForm();
|
||||
String url = AuthSubUtil.getRequestUrl(authsubProtocol,
|
||||
authsubHostname,
|
||||
next.toString(),
|
||||
scope,
|
||||
false,
|
||||
true);
|
||||
response.sendRedirect(response.encodeRedirectURL(url));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/* Copyright (c) 2006 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.gbase.recipe;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
|
||||
/**
|
||||
* Methods called from the JSP for generating HTML code.
|
||||
*
|
||||
* This is the work normally done by a web framework.
|
||||
*/
|
||||
public class DisplayUtils {
|
||||
|
||||
/**
|
||||
* Prints checkboxes on two colums, with a text input field at the end.
|
||||
* The checked values will be displayed along with the values to be showed.
|
||||
*
|
||||
* The text input field can be used to input a custom value. It has the
|
||||
* same name as the checkbox input fields, so when the submitted values
|
||||
* are processed, you have to remove the empty value, in case this field
|
||||
* remains empty.
|
||||
*
|
||||
* @param out output writer
|
||||
* @param name name of the input field
|
||||
* @param values values to be shown
|
||||
* @param checked checked values
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void printCheckboxes(Writer out,
|
||||
String name,
|
||||
String[] values,
|
||||
Set<String> checked) throws IOException {
|
||||
Set<String> allValues = new TreeSet<String>();
|
||||
// The displayed values are the static values plus the checked values
|
||||
allValues.addAll(Arrays.asList(values));
|
||||
if (checked == null) {
|
||||
checked = new HashSet<String>();
|
||||
}
|
||||
allValues.addAll(checked);
|
||||
int middle = (allValues.size() + 2) / 2;
|
||||
int row = 0;
|
||||
out.write("<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">");
|
||||
out.write("<tr><td><ul class=\"inputlist\">");
|
||||
for (String value : allValues) {
|
||||
if (row == middle) {
|
||||
out.write("</ul></td><td><ul class=\"inputlist\">");
|
||||
}
|
||||
out.write("<li>");
|
||||
printCheckbox(out, name, value, checked.contains(value));
|
||||
out.write("</li>");
|
||||
row++;
|
||||
}
|
||||
out.write("<li><label>Other:<br/><input id=\"other");
|
||||
out.write(name);
|
||||
out.write("\" type=\"text\" name=\"");
|
||||
out.write(name);
|
||||
out.write("\" value=\"\" size=\"15\" class=\"txt\"></label></li>");
|
||||
out.write("</ul></td></tr></table>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a checkbox input field with a label.
|
||||
*
|
||||
* @param out output writer
|
||||
* @param name name of the field
|
||||
* @param value value of the field
|
||||
* @param checked true if the field is checked
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void printCheckbox(Writer out, String name, String value,
|
||||
boolean checked) throws IOException {
|
||||
value = escape(value);
|
||||
out.write("<label><input type=\"checkbox\" name=\"");
|
||||
out.write(name);
|
||||
out.write("\" value=\"");
|
||||
out.write(value);
|
||||
out.write("\"");
|
||||
if (checked) {
|
||||
out.write(" checked");
|
||||
}
|
||||
out.write("/> ");
|
||||
if (checked) {
|
||||
out.write("<b>");
|
||||
}
|
||||
out.write(value);
|
||||
if (checked) {
|
||||
out.write("</b>");
|
||||
}
|
||||
out.write("</label>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the items of the Collection and returns them in a StringBuffer,
|
||||
* separated by commas.
|
||||
*
|
||||
* @param list
|
||||
* @return list of the elements of the collection, sepparated by commas
|
||||
*/
|
||||
public static StringBuffer printList(Collection<String> list) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (list != null && list.size() > 0) {
|
||||
Iterator<String> iter = list.iterator();
|
||||
while (true) {
|
||||
sb.append(escape(iter.next()));
|
||||
if (iter.hasNext()) {
|
||||
sb.append(", ");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints <option> tags and set the selected option.
|
||||
*
|
||||
* @param out output writer
|
||||
* @param names option names, which are used both for
|
||||
* values and for labels
|
||||
* @param current name of the current option
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void printOptions(Writer out,
|
||||
String[] names,
|
||||
String current) throws IOException {
|
||||
for (int i=0; i<names.length; i++) {
|
||||
String name = names[i];
|
||||
printOption(out, name, name, name.equals(current));
|
||||
}
|
||||
printOption(out, "all", "", current==null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints one <option> tag and select it if necessary.
|
||||
*
|
||||
* @param out output writer
|
||||
* @param label option label
|
||||
* @param value option value
|
||||
* @param selected true if it's selected
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void printOption(Writer out,
|
||||
String label,
|
||||
String value,
|
||||
boolean selected) throws IOException {
|
||||
out.write("<option");
|
||||
if (selected) {
|
||||
out.write(" selected");
|
||||
}
|
||||
out.write(" value=\"");
|
||||
out.write(value);
|
||||
out.write("\">");
|
||||
out.write(escape(label));
|
||||
out.write("</option>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple HTML escaping of the String version of the specified Object.
|
||||
*
|
||||
* @param obj Object that has a meaningful toString() value
|
||||
* @return string with some escaped characters
|
||||
*/
|
||||
public static String escape(Object obj) {
|
||||
if (obj == null) {
|
||||
return "";
|
||||
}
|
||||
return escapeAndShorten(obj.toString(), -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple HTML escaping.
|
||||
*
|
||||
* Escapes < & and > and leaves the rest as it is.
|
||||
*
|
||||
* @param raw
|
||||
* @return string with some escaped characters
|
||||
*/
|
||||
public static String escape(String raw) {
|
||||
return escapeAndShorten(raw, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML data and shorted the result if necessary.
|
||||
*
|
||||
* If the text is escaped, <b>...</b> will be
|
||||
* appended.
|
||||
*
|
||||
* @param raw raw text to escape
|
||||
* @param maxLength maximum output length
|
||||
* @return HTML code
|
||||
*/
|
||||
public static String escapeAndShorten(String raw, int maxLength) {
|
||||
if (raw == null) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder retval = new StringBuilder();
|
||||
int length = raw.length();
|
||||
boolean shortened = false;
|
||||
if (maxLength != -1 && length > maxLength) {
|
||||
length = maxLength;
|
||||
shortened = true;
|
||||
}
|
||||
for (int i=0; i<length; i++) {
|
||||
char c = raw.charAt(i);
|
||||
switch (c) {
|
||||
case '<':
|
||||
retval.append("<");
|
||||
break;
|
||||
case '>':
|
||||
retval.append(">");
|
||||
break;
|
||||
case '&':
|
||||
retval.append("&");
|
||||
break;
|
||||
case '\'':
|
||||
retval.append("'");
|
||||
break;
|
||||
case '"':
|
||||
retval.append(""");
|
||||
break;
|
||||
default:
|
||||
retval.append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (shortened) {
|
||||
retval.append("<b>...</b>");
|
||||
}
|
||||
return retval.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
/* Copyright (c) 2006 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.gbase.recipe;
|
||||
|
||||
import com.google.api.gbase.client.AttributeHistogram;
|
||||
import com.google.api.gbase.client.FeedURLFactory;
|
||||
import com.google.api.gbase.client.GoogleBaseEntry;
|
||||
import com.google.api.gbase.client.GoogleBaseFeed;
|
||||
import com.google.api.gbase.client.GoogleBaseQuery;
|
||||
import com.google.api.gbase.client.GoogleBaseService;
|
||||
import com.google.api.gbase.client.MetadataEntryExtension;
|
||||
import com.google.api.gbase.client.AttributeHistogram.UniqueValue;
|
||||
import com.google.gdata.util.ServiceException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
/**
|
||||
* Holds the most used values of attributes in Google Base and
|
||||
* periodically refreshes them.
|
||||
*
|
||||
* A MostUsedValues object is focused on a GoogleBaseService and
|
||||
* a FeedURLFactory. One object can be used to analyse only the items that
|
||||
* match a specific query.
|
||||
*
|
||||
* The class currently works for TEXT attributes only.
|
||||
*
|
||||
* The cache() and clear() calls are synchronized,
|
||||
* so that the different Maps and Collections remain in sync.
|
||||
*
|
||||
* Typically, right after you create a MostUsedValues object, you call the
|
||||
* {@link #cache(long, int, ServletContext, String[])} method to set it up
|
||||
* to cache the most used values of your attributes. This creates
|
||||
* {@link java.util.TimerTask} objects that periodically refresh the cached
|
||||
* values. After this, you use {@link #getMostUsedValuesForAttribute(String)}
|
||||
* to get the most used values for the attributes you previously specified.
|
||||
* In the end, when you no longer need the cache, you can call
|
||||
* {@link #clear()} to stop the TimerTask objects and clear the cache.
|
||||
*/
|
||||
public class MostUsedValues {
|
||||
|
||||
protected static final String TEXT_TYPE = "(text)";
|
||||
|
||||
/**
|
||||
* The initial value of the max values limit when making a query;
|
||||
* the step used to increase that limit if some of the attributes
|
||||
* are not found.
|
||||
*/
|
||||
protected static final int STEP_MAXRESULTS = 25;
|
||||
|
||||
/**
|
||||
* The max of the max values limit when making a query.
|
||||
*/
|
||||
protected static final int MAX_MAXRESULTS = 200;
|
||||
|
||||
/**
|
||||
* The cached most used values.
|
||||
* This is a synchronized map.
|
||||
*/
|
||||
private java.util.Map<String, String[]> mostUsedValues;
|
||||
|
||||
/**
|
||||
* The timers that periodically refresh the cache.
|
||||
* The access to this object has to be synchronized.
|
||||
*/
|
||||
private Collection<Timer> timers;
|
||||
|
||||
private GoogleBaseService service;
|
||||
private FeedURLFactory urlFactory;
|
||||
private String queryString;
|
||||
|
||||
/**
|
||||
* Creates an empty MostUsedValues.
|
||||
*
|
||||
* @param service any GoogleBaseService used to retrieve attribute histograms
|
||||
* @param urlFactory a FeedURLFactory used to create the URLs of
|
||||
* the attribute histograms
|
||||
* @param queryString the query string used to filter the analyzed items,
|
||||
* for example one that focuses only on the items that
|
||||
* have a specific item type.
|
||||
*/
|
||||
public MostUsedValues(GoogleBaseService service,
|
||||
FeedURLFactory urlFactory,
|
||||
String queryString) {
|
||||
this.service = service;
|
||||
this.urlFactory = urlFactory;
|
||||
this.queryString = queryString;
|
||||
|
||||
mostUsedValues = new Hashtable<String, String[]>();
|
||||
timers = new ArrayList<Timer>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cached most used values of an attribute.
|
||||
*
|
||||
* @param attrName the name of the attribute
|
||||
* @return a cached list of the most used values
|
||||
*/
|
||||
public String[] getMostUsedValuesForAttribute(String attrName) {
|
||||
return mostUsedValues.get(attrName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the object to cache a limited number of the most used values of
|
||||
* each of the specified attributes; the cache is refreshed periodically.
|
||||
*
|
||||
* This method does not check if the specified attributes are already cached.
|
||||
* You have to make sure an attribute is specified only once in your calls.
|
||||
*
|
||||
* @param interval the cache refresh period, in millis
|
||||
* @param maxValues the maximum number of values to cache
|
||||
* @param servletContext a ServletContext to be used for logging
|
||||
* @param attrNames the names of the attributes to be cached
|
||||
*/
|
||||
synchronized public void cache(long interval,
|
||||
final int maxValues,
|
||||
ServletContext servletContext,
|
||||
final String... attrNames) {
|
||||
if (attrNames.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Timer timer = new Timer(true);
|
||||
TimerTask task = createRefresher(maxValues, servletContext, attrNames);
|
||||
task.run();
|
||||
timer.schedule(task, interval, interval);
|
||||
timers.add(timer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TimerTask that refreshes the cached most used values of
|
||||
* the specified attributes.
|
||||
*
|
||||
* @param maxValues how many values to cache for each attribute
|
||||
* @param servletContext servlet context for logging error messages
|
||||
* @param attrNames the names of the attributes
|
||||
* @return a TimerTask that refreshes the cache
|
||||
*/
|
||||
private TimerTask createRefresher(final int maxValues,
|
||||
final ServletContext servletContext,
|
||||
final String... attrNames) {
|
||||
TimerTask task = new TimerTask() {
|
||||
/**
|
||||
* Tells the MostUsedValues object that created this TimerTask to
|
||||
* refresh the cached most used values of some of the attributes.
|
||||
*/
|
||||
@Override public void run() {
|
||||
try {
|
||||
MostUsedValues.this.retrieveMostUsedValues(maxValues, attrNames);
|
||||
} catch (IOException e) {
|
||||
servletContext.log(e.getMessage(), e);
|
||||
} catch (ServiceException e) {
|
||||
servletContext.log(e.getMessage() + " " +
|
||||
e.getHttpErrorCodeOverride() + " " +
|
||||
e.getResponseContentType() + ": " +
|
||||
e.getResponseBody(), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the most used values for some attributes
|
||||
* for the items that match the query string
|
||||
* and stores a limited number of those values for each attribute.
|
||||
*
|
||||
* @param numValue maximum number of values to store
|
||||
* @param attrNames the names of the attributes
|
||||
*/
|
||||
protected void retrieveMostUsedValues(int numValue, final String... attrNames)
|
||||
throws ServiceException, IOException {
|
||||
URL url = urlFactory.getAttributesFeedURL();
|
||||
GoogleBaseQuery query = new GoogleBaseQuery(url);
|
||||
|
||||
StringBuffer queryString = createQueryString(attrNames);
|
||||
query.setGoogleBaseQuery(queryString.toString());
|
||||
query.setMaxValues(numValue);
|
||||
|
||||
int numResults = 0;
|
||||
int lastNumResults = 0;
|
||||
Collection<String> attrToRetrieve =
|
||||
new ArrayList<String>(Arrays.asList(attrNames));
|
||||
do {
|
||||
// Get the feed
|
||||
numResults += STEP_MAXRESULTS;
|
||||
query.setMaxResults(numResults);
|
||||
GoogleBaseFeed feed = service.query(query);
|
||||
if (lastNumResults == feed.getTotalResults()) {
|
||||
// No new entries to process
|
||||
break;
|
||||
}
|
||||
lastNumResults = feed.getTotalResults();
|
||||
// Extract the values from the entries
|
||||
Iterator<String> attrIter = attrToRetrieve.iterator();
|
||||
while (attrIter.hasNext()) {
|
||||
String attrName = attrIter.next();
|
||||
// Searching for the entry with the following name
|
||||
String entryTitle = attrName + TEXT_TYPE;
|
||||
for (GoogleBaseEntry entry : feed.getEntries()) {
|
||||
if (entryTitle.equals(entry.getTitle().getPlainText())) {
|
||||
extractValuesFromEntry(numValue, attrName, entry);
|
||||
attrIter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!attrToRetrieve.isEmpty() && numResults <= MAX_MAXRESULTS);
|
||||
|
||||
if (!attrToRetrieve.isEmpty()) {
|
||||
throw new ServiceException("The retrieved histograms do not contain" +
|
||||
"some of the attributes. The most used values of these attributes " +
|
||||
"have not been refreshed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query string extended so that it filters out the items
|
||||
* that do not have at least one of the specified attributes of type TEXT.
|
||||
*
|
||||
* @param attrNames the attributes we are interested in
|
||||
* @return an extended query string
|
||||
*/
|
||||
protected StringBuffer createQueryString(final String... attrNames) {
|
||||
StringBuffer queryString = new StringBuffer(this.queryString);
|
||||
queryString.append("(");
|
||||
queryString.append("[").append(attrNames[0]).append(TEXT_TYPE).append("]");
|
||||
for (int i = 1; i < attrNames.length; i++) {
|
||||
String attrName = attrNames[i];
|
||||
queryString.append("|[").append(attrName).append(TEXT_TYPE).append("]");
|
||||
}
|
||||
queryString.append(")");
|
||||
return queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches a limited number of the values of a GoogleBaseEntry.
|
||||
*
|
||||
* @param numValue maximum number of values to cache
|
||||
* @param attrName the name of the attribute that has the values
|
||||
* @param entry an entry with a MetadataEntryExtension
|
||||
*/
|
||||
private void extractValuesFromEntry(int numValue,
|
||||
String attrName,
|
||||
GoogleBaseEntry entry) {
|
||||
MetadataEntryExtension metadata = entry.getGoogleBaseMetadata();
|
||||
AttributeHistogram attributeHistogram = metadata.getAttributeHistogram();
|
||||
List<? extends UniqueValue> values = attributeHistogram.getValues();
|
||||
|
||||
int valuesCount = Math.min(numValue, values.size());
|
||||
String[] usedValues = new String[valuesCount];
|
||||
for (int i = 0; i < valuesCount; i++) {
|
||||
usedValues[i] = values.get(i).getValueAsString();
|
||||
}
|
||||
|
||||
updateMostUsedValue(attrName, usedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all refresh timers and clears the cache.
|
||||
*/
|
||||
synchronized public void clear() {
|
||||
for (Timer timer : timers) {
|
||||
timer.cancel();
|
||||
}
|
||||
timers.clear();
|
||||
mostUsedValues.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of cached attributes.
|
||||
*/
|
||||
public int size() {
|
||||
return mostUsedValues.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if no attribute is cached.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return mostUsedValues.isEmpty();
|
||||
}
|
||||
|
||||
public String getQueryString() {
|
||||
return queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches the most used values of an attribute.
|
||||
* @param attrName
|
||||
* @param stringValues
|
||||
*/
|
||||
protected void updateMostUsedValue(String attrName, String[] stringValues) {
|
||||
mostUsedValues.put(attrName, stringValues);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
/* Copyright (c) 2006 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.gbase.recipe;
|
||||
|
||||
import com.google.api.gbase.client.GoogleBaseEntry;
|
||||
import com.google.api.gbase.client.NumberUnit;
|
||||
import com.google.gdata.data.Content;
|
||||
import com.google.gdata.data.DateTime;
|
||||
import com.google.gdata.data.OtherContent;
|
||||
import com.google.gdata.data.Person;
|
||||
import com.google.gdata.data.TextConstruct;
|
||||
import com.google.gdata.data.TextContent;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* One recipe, ready to be displayed.
|
||||
* Just a data holding object.
|
||||
*/
|
||||
public class Recipe {
|
||||
public final static String RECIPE_ITEMTYPE = "recipes";
|
||||
|
||||
public final static String MAIN_INGREDIENT_ATTRIBUTE = "main ingredient";
|
||||
public final static String CUISINE_ATTRIBUTE = "cuisine";
|
||||
public final static String COOKING_TIME_ATTRIBUTE = "cooking time";
|
||||
|
||||
public final static String AUTHOR_UNKNOWN = "";
|
||||
|
||||
private final String id;
|
||||
private final DateTime postedOn;
|
||||
private final String postedBy;
|
||||
private final NumberUnit<Integer> cookingTime;
|
||||
private final String url;
|
||||
private final String title;
|
||||
private final String description;
|
||||
/** A never-null list that contains the main ingredients. */
|
||||
private final Set<String> mainIngredient;
|
||||
/** A never-null list that contains the cuisines. */
|
||||
private final Set<String> cuisine;
|
||||
|
||||
/**
|
||||
* Creates a recipe. The parameters can be null.
|
||||
*
|
||||
* @param id id generated by the GoogleBase server
|
||||
* @param title
|
||||
* @param url alternate url of the recipe;
|
||||
* if null, when the recipe is used to insert or to update
|
||||
* it is generated by the GoogleBase server
|
||||
* @param description
|
||||
* @param mainIngredient
|
||||
* @param cuisine
|
||||
* @param cookingTime
|
||||
*/
|
||||
public Recipe(String id, String title, String url,
|
||||
String description, Set<String> mainIngredient, Set<String> cuisine,
|
||||
NumberUnit<Integer> cookingTime) {
|
||||
if (mainIngredient == null) {
|
||||
mainIngredient = new HashSet<String>();
|
||||
}
|
||||
if (cuisine == null) {
|
||||
cuisine = new HashSet<String>();
|
||||
}
|
||||
this.id = id;
|
||||
|
||||
this.title = title;
|
||||
this.url = url;
|
||||
this.description = description;
|
||||
this.mainIngredient = mainIngredient;
|
||||
this.cuisine = cuisine;
|
||||
this.cookingTime = cookingTime;
|
||||
|
||||
this.postedOn = null;
|
||||
this.postedBy = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a recipe out of a GoogleBaseEntry.
|
||||
*
|
||||
* @param entry an entry that represents a recipe
|
||||
*/
|
||||
public Recipe(GoogleBaseEntry entry) {
|
||||
id = extractIdFromUrl(entry.getId());
|
||||
title = entry.getTitle().getPlainText();
|
||||
url = entry.getHtmlLink().getHref();
|
||||
String description = null;
|
||||
if (entry.getContent() != null) {
|
||||
Content content = entry.getContent();
|
||||
if (content instanceof TextContent) {
|
||||
description = ((TextContent)content).getContent().getPlainText();
|
||||
} else if (content instanceof OtherContent) {
|
||||
description = ((OtherContent)content).getText();
|
||||
}
|
||||
}
|
||||
this.description = description;
|
||||
mainIngredient = new HashSet<String>(entry.getGoogleBaseAttributes().
|
||||
getTextAttributeValues(MAIN_INGREDIENT_ATTRIBUTE));
|
||||
cuisine = new HashSet<String>(entry.getGoogleBaseAttributes().
|
||||
getTextAttributeValues(CUISINE_ATTRIBUTE));
|
||||
cookingTime = entry.getGoogleBaseAttributes().
|
||||
getIntUnitAttribute(COOKING_TIME_ATTRIBUTE);
|
||||
postedOn = entry.getPublished();
|
||||
|
||||
// if an entry has no author specified, will set it to empty string
|
||||
List<Person> authors = entry.getAuthors();
|
||||
postedBy = (authors.isEmpty() ? AUTHOR_UNKNOWN : authors.get(0).getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty recipe, the values are null or empty Sets.
|
||||
*/
|
||||
public Recipe() {
|
||||
this(null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public GoogleBaseEntry toGoogleBaseEntry(String idUrl)
|
||||
{
|
||||
GoogleBaseEntry entry = new GoogleBaseEntry();
|
||||
entry.getGoogleBaseAttributes().setItemType(RECIPE_ITEMTYPE);
|
||||
if (idUrl != null) {
|
||||
entry.setId(idUrl);
|
||||
}
|
||||
entry.setTitle(TextConstruct.create(TextConstruct.Type.TEXT, title, null));
|
||||
if (url != null) {
|
||||
entry.addHtmlLink(url, null, null);
|
||||
}
|
||||
if (description != null) {
|
||||
// If the original content was not TEXT, the formatting is lost
|
||||
entry.setContent(
|
||||
TextConstruct.create(TextConstruct.Type.TEXT, description, null));
|
||||
}
|
||||
for (String ingredient : mainIngredient) {
|
||||
entry.getGoogleBaseAttributes().addTextAttribute(
|
||||
MAIN_INGREDIENT_ATTRIBUTE, ingredient);
|
||||
}
|
||||
for (String cuisineItem : cuisine) {
|
||||
entry.getGoogleBaseAttributes().addTextAttribute(
|
||||
CUISINE_ATTRIBUTE, cuisineItem);
|
||||
}
|
||||
if (cookingTime != null) {
|
||||
entry.getGoogleBaseAttributes().addIntUnitAttribute(
|
||||
COOKING_TIME_ATTRIBUTE, cookingTime);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the id of the item from the given url.
|
||||
*
|
||||
* The id found in GoogleBaseEntry is a URL that contains the real, numerical
|
||||
* id of the item in Google Base. Parsing the URL is unfortunately the
|
||||
* only way of getting a numerical id given a GoogleBaseEntry.
|
||||
*
|
||||
* @param url a URL that ends with "/" [N] number
|
||||
*/
|
||||
private static String extractIdFromUrl(String url) {
|
||||
int lastSlash = url.lastIndexOf('/');
|
||||
if (lastSlash == -1 || lastSlash == (url.length()-1)) {
|
||||
throw new IllegalArgumentException("Id is in a strange format. " + url);
|
||||
}
|
||||
String oid = url.substring(lastSlash + 1);
|
||||
return oid;
|
||||
}
|
||||
|
||||
/** Checks whether there is a description for the recipe. */
|
||||
public boolean hasDescription() {
|
||||
return description != null;
|
||||
}
|
||||
|
||||
/** Checks whether there is a cooking time for the recipe. */
|
||||
public boolean hasCookingTime() {
|
||||
return cookingTime != null;
|
||||
}
|
||||
|
||||
/** Checks whether there are some cuisines for the recipe. */
|
||||
public boolean hasCuisine() {
|
||||
return cuisine.size() > 0;
|
||||
}
|
||||
|
||||
/** Checks whether there are some main ingredients for the recipe. */
|
||||
public boolean hasMainIngredient() {
|
||||
return mainIngredient.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date at which the recipe was posted, as a string.
|
||||
*
|
||||
* @param detailed set to true to get a full date and time
|
||||
*/
|
||||
public String getPostedOnAsString(boolean detailed) {
|
||||
Date date = new Date(postedOn.getValue());
|
||||
String template = detailed ? "MMMMM d, yyyy HH:mm z" : "MMM d";
|
||||
DateFormat format = new SimpleDateFormat(template);
|
||||
return format.format(date);
|
||||
}
|
||||
|
||||
/** Gets the host and protocol from the recipe URL. */
|
||||
public String getHostAndProtocol() throws MalformedURLException {
|
||||
URL urlObject = new URL(getUrl());
|
||||
return urlObject.getProtocol() + "://" + urlObject.getHost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when the title, description, mainIngredient and cuisine
|
||||
* attributes are not null nor empty.
|
||||
*/
|
||||
public boolean isComplete() {
|
||||
return title != null &&
|
||||
description != null &&
|
||||
mainIngredient.size() > 0 &&
|
||||
cuisine.size() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("Recipe[");
|
||||
appendNamedParameter(sb, "title", title);
|
||||
appendNamedParameter(sb, "id", id);
|
||||
appendNamedParameter(sb, "url", url);
|
||||
appendNamedParameter(sb, "description", description);
|
||||
appendNamedParameter(sb, "cookingTime", cookingTime);
|
||||
appendNamedParameter(sb, "postedOn", postedOn);
|
||||
appendNamedParameter(sb, "postedBy", postedBy);
|
||||
appendNamedCollection(sb, "mainIngredient", mainIngredient);
|
||||
appendNamedCollection(sb, "cuisine", cuisine);
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the name and the value of an Object to a StringBuffer.
|
||||
*
|
||||
* @param sb
|
||||
* @param name
|
||||
* @param value
|
||||
*/
|
||||
private static void appendNamedParameter(StringBuffer sb,
|
||||
String name,
|
||||
Object value) {
|
||||
if (value != null) {
|
||||
sb.append(name).append("=\"").append(value).append("\" ");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the name and the elements of a Collection to a StringBuffer.
|
||||
*
|
||||
* @param sb
|
||||
* @param name
|
||||
* @param collection
|
||||
*/
|
||||
private static void appendNamedCollection(
|
||||
StringBuffer sb,
|
||||
String name,
|
||||
Collection<String> collection) {
|
||||
if (collection.size() > 0) {
|
||||
sb.append(name).append("=(");
|
||||
for (String value : collection) {
|
||||
sb.append("\"").append(value).append("\", ");
|
||||
}
|
||||
sb.append(") ");
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the id generated by the server. */
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/** Gets the user assigned title. */
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the url of the recipe, as set by the customer.
|
||||
* If the customer sets no url, the server will generate one.
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/** Gets the user assigned description. */
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/** Returns a never-null Set with the main ingredients of the recipe. */
|
||||
public Set<String> getMainIngredient() {
|
||||
return mainIngredient;
|
||||
}
|
||||
|
||||
/** Returns a never-null Set with the cuisines the recipe belongs to. */
|
||||
public Set<String> getCuisine() {
|
||||
return cuisine;
|
||||
}
|
||||
|
||||
/** Gets the user assigned cooking time. */
|
||||
public NumberUnit<Integer> getCookingTime() {
|
||||
return cookingTime;
|
||||
}
|
||||
|
||||
/** Gets the server generated owner attribute of the recipe. */
|
||||
public String getPostedBy() {
|
||||
return postedBy;
|
||||
}
|
||||
|
||||
/** Gets the server generated date at which the recipe was posted. */
|
||||
public DateTime getPostedOn() {
|
||||
return postedOn;
|
||||
}
|
||||
|
||||
/** Returns true when the Recipe doesn't have an id. */
|
||||
public boolean isNew() {
|
||||
return id == null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/* Copyright (c) 2006 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.gbase.recipe;
|
||||
|
||||
import com.google.api.gbase.client.FeedURLFactory;
|
||||
import com.google.api.gbase.client.GoogleBaseEntry;
|
||||
import com.google.api.gbase.client.GoogleBaseService;
|
||||
import com.google.api.gbase.client.NumberUnit;
|
||||
import com.google.gdata.util.ServiceException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Inserts, updates or deletes a recipe,
|
||||
* depending on the "action" servlet initialization parameter.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class RecipeActionServlet extends HttpServlet {
|
||||
|
||||
public static final String DISPLAY_JSP = "/WEB-INF/recipeEdit.jsp";
|
||||
|
||||
private static final int ACTION_ADD = 0;
|
||||
private static final int ACTION_UPDATE = 1;
|
||||
private static final int ACTION_DELETE = 2;
|
||||
|
||||
protected FeedURLFactory urlFactory;
|
||||
|
||||
/** The operation this servlet has to perform. */
|
||||
protected int action = -1;
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig servletConfig) throws ServletException {
|
||||
super.init(servletConfig);
|
||||
ServletContext context = servletConfig.getServletContext();
|
||||
urlFactory = (FeedURLFactory)
|
||||
context.getAttribute(RecipeListener.FEED_URL_FACTORY_ATTRIBUTE);
|
||||
String action = servletConfig.getInitParameter("action");
|
||||
if ("add".equals(action)) {
|
||||
this.action = ACTION_ADD;
|
||||
} else if ("update".equals(action)) {
|
||||
this.action = ACTION_UPDATE;
|
||||
} else if ("delete".equals(action)) {
|
||||
this.action = ACTION_DELETE;
|
||||
} else {
|
||||
throw new ServletException("Unknown action: " + action);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAdd() { return ACTION_ADD == action; }
|
||||
private boolean isUpdate() { return ACTION_UPDATE == action; }
|
||||
private boolean isDelete() { return ACTION_DELETE == action; }
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
/** Inserts or updates the submitted recipe and redirects to recipeList. */
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
GoogleBaseService service = (GoogleBaseService) request.getAttribute(
|
||||
AuthenticationFilter.SERVICE_ATTRIBUTE);
|
||||
Recipe recipe = getPostedRecipe(request);
|
||||
if (!recipe.isComplete()) {
|
||||
String message = "<div class='errormessage'>Please fill out " +
|
||||
"all the mandatory fields.</div>";
|
||||
editRecipe(request, response, recipe, message);
|
||||
} else {
|
||||
try {
|
||||
if (isAdd()) {
|
||||
recipeAdd(service, recipe);
|
||||
} else if (isUpdate()) {
|
||||
recipeUpdate(service, recipe);
|
||||
} else {
|
||||
throw new ServletException("Unknown POST action: " + action);
|
||||
}
|
||||
} catch (ServiceException e) {
|
||||
RecipeUtil.logServiceException(this, e);
|
||||
RecipeUtil.forwardToErrorPage(request, response, e);
|
||||
return;
|
||||
}
|
||||
listOwnRecipes(response);
|
||||
}
|
||||
}
|
||||
|
||||
/** Redirect to the page that lists customer's recipes. */
|
||||
protected void listOwnRecipes(HttpServletResponse response)
|
||||
throws IOException {
|
||||
String redirectUrl = "recipeList";
|
||||
response.sendRedirect(response.encodeRedirectURL(redirectUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a recipe using the specified authenticated service.
|
||||
*
|
||||
* @param service an authenticated GoogleBaseService
|
||||
* @param recipe recipe to be inserted
|
||||
* @throws IOException
|
||||
* @throws ServiceException
|
||||
*/
|
||||
protected void recipeAdd(GoogleBaseService service,
|
||||
Recipe recipe)
|
||||
throws IOException, ServiceException {
|
||||
URL feedUrl = urlFactory.getItemsFeedURL();
|
||||
GoogleBaseEntry entry = recipe.toGoogleBaseEntry(null);
|
||||
service.insert(feedUrl, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a recipe using the specified authenticated service.
|
||||
*
|
||||
* The recipe must have a valid GoogleBase id.
|
||||
*
|
||||
* @param service an authenticted GoogleBaseService
|
||||
* @param recipe recipe to be updated
|
||||
* @throws ServiceException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void recipeUpdate(GoogleBaseService service,
|
||||
Recipe recipe)
|
||||
throws ServiceException, IOException {
|
||||
URL feedUrl = urlFactory.getItemsEntryURL(recipe.getId());
|
||||
GoogleBaseEntry entry = recipe.toGoogleBaseEntry(feedUrl.toString());
|
||||
service.update(feedUrl, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the specified authenticated service to delete a recipe.
|
||||
*
|
||||
* @param service an authenticated service
|
||||
* @param id the id of the recipe
|
||||
* @throws ServiceException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void recipeDelete(GoogleBaseService service,
|
||||
String id)
|
||||
throws ServiceException, IOException {
|
||||
URL feedUrl = urlFactory.getItemsEntryURL(id);
|
||||
service.delete(feedUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Recipe from the parameters submitted with the specified request.
|
||||
*
|
||||
* @param request http request to be processed
|
||||
* @return a submitted recipe
|
||||
*/
|
||||
static Recipe getPostedRecipe(HttpServletRequest request) {
|
||||
String id = getNullIfEmpty(request.getParameter(RecipeUtil.ID_PARAMETER));
|
||||
String title = getNullIfEmpty(request.getParameter(
|
||||
RecipeUtil.TITLE_PARAMETER));
|
||||
String url = getNullIfEmpty(request.getParameter(RecipeUtil.URL_PARAMETER));
|
||||
String description = getNullIfEmpty(request.getParameter(
|
||||
RecipeUtil.DESCRIPTION_PARAMETER));
|
||||
Set<String> mainIngredient = RecipeUtil.validateValues(
|
||||
request.getParameterValues(RecipeUtil.MAIN_INGREDIENT_PARAMETER));
|
||||
Set<String> cuisine = RecipeUtil.validateValues(
|
||||
request.getParameterValues(RecipeUtil.CUISINE_PARAMETER));
|
||||
NumberUnit<Integer> cookingTime;
|
||||
try {
|
||||
cookingTime = new NumberUnit<Integer>(
|
||||
new Integer(request.getParameter(RecipeUtil.COOKING_TIME_PARAMETER)),
|
||||
RecipeUtil.COOKING_TIME_UNIT);
|
||||
} catch (Exception e) {
|
||||
// If anything goes bad, we set cookingTime to null
|
||||
cookingTime = null;
|
||||
}
|
||||
Recipe recipe = new Recipe(id,
|
||||
title,
|
||||
url,
|
||||
description,
|
||||
mainIngredient,
|
||||
cuisine,
|
||||
cookingTime);
|
||||
return recipe;
|
||||
}
|
||||
|
||||
static private String getNullIfEmpty(String s) {
|
||||
return s != null && "".equals(s) ? null : s;
|
||||
}
|
||||
|
||||
|
||||
/** Shows the page for inserting or updating a recipe or deletes a recipe. */
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
GoogleBaseService service = (GoogleBaseService) request.getAttribute(
|
||||
AuthenticationFilter.SERVICE_ATTRIBUTE);
|
||||
String id = request.getParameter(RecipeUtil.ID_PARAMETER);
|
||||
try {
|
||||
if (isDelete()) {
|
||||
recipeDelete(service, id);
|
||||
listOwnRecipes(response);
|
||||
} else {
|
||||
// The recipe that will be used on the edit page
|
||||
// for inserting or updating.
|
||||
Recipe recipe = null;
|
||||
if (isAdd()) {
|
||||
recipe = new Recipe();
|
||||
} else if (isUpdate()) {
|
||||
URL entryUrl = urlFactory.getItemsEntryURL(id);
|
||||
GoogleBaseEntry entry = service.getEntry(entryUrl);
|
||||
recipe = new Recipe(entry);
|
||||
}
|
||||
if (recipe != null) {
|
||||
// Ready to add or update
|
||||
editRecipe(request, response, recipe, null);
|
||||
}
|
||||
}
|
||||
} catch (ServiceException e) {
|
||||
RecipeUtil.logServiceException(this, e);
|
||||
RecipeUtil.forwardToErrorPage(request, response, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@value RecipeUtil#RECIPE_ATTRIBUTE} attribute of the request to
|
||||
* contain the specified recipe and forwards the request to the
|
||||
* {@value #DISPLAY_JSP} page.
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @param recipe the recipe to be passed to the edit jsp page
|
||||
* @param message HTML code to be displayed at the top of the page, usually an
|
||||
* error message
|
||||
*/
|
||||
private void editRecipe(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Recipe recipe,
|
||||
String message)
|
||||
throws ServletException, IOException {
|
||||
request.setAttribute(RecipeUtil.RECIPE_ATTRIBUTE, recipe);
|
||||
request.setAttribute(RecipeUtil.MESSAGE_ATTRIBUTE,
|
||||
message == null ? "" : message);
|
||||
// Forward to the JSP
|
||||
request.getRequestDispatcher(DISPLAY_JSP).forward(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/* Copyright (c) 2006 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.gbase.recipe;
|
||||
|
||||
import com.google.api.gbase.client.FeedURLFactory;
|
||||
import com.google.api.gbase.client.GoogleBaseEntry;
|
||||
import com.google.api.gbase.client.GoogleBaseService;
|
||||
import com.google.gdata.util.ServiceException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Displays a recipe.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class RecipeDisplayServlet extends HttpServlet {
|
||||
|
||||
public static final String DISPLAY_JSP = "/WEB-INF/recipeDisplay.jsp";
|
||||
|
||||
protected FeedURLFactory urlFactory;
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig servletConfig) throws ServletException {
|
||||
super.init(servletConfig);
|
||||
ServletContext context = servletConfig.getServletContext();
|
||||
urlFactory = (FeedURLFactory)
|
||||
context.getAttribute(RecipeListener.FEED_URL_FACTORY_ATTRIBUTE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the page for displaying a Recipe.
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @throws ServletException
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
// This is a public page, so we use a simple, nonauthenticated service
|
||||
GoogleBaseService service = RecipeUtil.getGoogleBaseService(request,
|
||||
this.getServletContext());
|
||||
String id = request.getParameter(RecipeUtil.ID_PARAMETER);
|
||||
recipeDisplay(request, response, service, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a recipe and forwards the request
|
||||
* to the {@link #DISPLAY_JSP} jsp page that displays the recipe.
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @param service the service used to retrieve the recipe
|
||||
* @param id the id of the recipe
|
||||
*/
|
||||
private void recipeDisplay(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
GoogleBaseService service,
|
||||
String id)
|
||||
throws ServletException, IOException {
|
||||
GoogleBaseEntry entry;
|
||||
try {
|
||||
URL feedUrl = urlFactory.getSnippetsEntryURL(id);
|
||||
entry = service.getEntry(feedUrl, GoogleBaseEntry.class);
|
||||
} catch (ServiceException e) {
|
||||
RecipeUtil.logServiceException(this, e);
|
||||
RecipeUtil.forwardToErrorPage(request, response, e);
|
||||
return;
|
||||
}
|
||||
Recipe recipe = new Recipe(entry);
|
||||
request.setAttribute(RecipeUtil.RECIPE_ATTRIBUTE, recipe);
|
||||
RecipeSearch results = new RecipeSearch(service, urlFactory, false);
|
||||
RecipeUtil.setRecipeSearch(request, results);
|
||||
// Forward to the JSP
|
||||
request.getRequestDispatcher(DISPLAY_JSP).forward(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/* Copyright (c) 2006 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.gbase.recipe;
|
||||
|
||||
import com.google.api.gbase.client.FeedURLFactory;
|
||||
import com.google.api.gbase.client.GoogleBaseService;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
|
||||
/**
|
||||
* Creates objects needed by the servlets and makes them available by
|
||||
* setting them as attributes of the global servlet context.
|
||||
*
|
||||
* Makes sure the required initialization parameters are present.
|
||||
*
|
||||
*/
|
||||
public class RecipeListener implements ServletContextListener {
|
||||
|
||||
public static final String MOST_USED_VALUES_ATTRIBUTE = "mostUsedValues";
|
||||
|
||||
public static final String FEED_URL_FACTORY_ATTRIBUTE = "feedUrlFactory";
|
||||
|
||||
FeedURLFactory urlFactory;
|
||||
|
||||
protected MostUsedValues mostUsedValues;
|
||||
|
||||
/**
|
||||
* Creates an initialised MostUsedValues object and a FeedURLFactory
|
||||
* to be used by the servlets.
|
||||
* Makes sure the applicationName init parameter is set.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void contextInitialized(ServletContextEvent event) {
|
||||
ServletContext servletContext = event.getServletContext();
|
||||
|
||||
String applicationName =
|
||||
servletContext.getInitParameter(RecipeUtil.APPLICATION_NAME_PARAMETER);
|
||||
if (applicationName == null) {
|
||||
RuntimeException re =
|
||||
new RuntimeException("applicationName context parameter is missing");
|
||||
servletContext.log(re.getMessage(), re.getCause());
|
||||
throw re;
|
||||
}
|
||||
|
||||
String baseUrl = servletContext.getInitParameter("baseUrl");
|
||||
if (baseUrl == null) {
|
||||
urlFactory = FeedURLFactory.getDefault();
|
||||
} else {
|
||||
try {
|
||||
urlFactory = new FeedURLFactory(baseUrl);
|
||||
} catch (MalformedURLException e) {
|
||||
RuntimeException re =
|
||||
new RuntimeException("Cannot use the baseUrl context parameter", e);
|
||||
servletContext.log(re.getMessage(), re.getCause());
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
servletContext.setAttribute(FEED_URL_FACTORY_ATTRIBUTE, urlFactory);
|
||||
|
||||
String key = servletContext.getInitParameter(RecipeUtil.DEVELOPER_KEY_PARAMETER);
|
||||
|
||||
GoogleBaseService service = new GoogleBaseService(applicationName, key);
|
||||
mostUsedValues = new MostUsedValues(service,
|
||||
urlFactory,
|
||||
RecipeUtil.RECIPE_ITEMTYPE_QUERY);
|
||||
initMostUsedValues(mostUsedValues, servletContext);
|
||||
RecipeUtil.setMostUsedValues(servletContext, mostUsedValues);
|
||||
}
|
||||
|
||||
public void contextDestroyed(ServletContextEvent event) {
|
||||
mostUsedValues.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a MostUsedValues object to cache the most used values
|
||||
* of some attributes, suitable to be used in the web pages.
|
||||
*
|
||||
* @param mostUsedValues object to initialize
|
||||
* @param servletContext the servlet context used by mostUsedValues
|
||||
* to log exceptions
|
||||
*/
|
||||
public static void initMostUsedValues(MostUsedValues mostUsedValues,
|
||||
ServletContext servletContext) {
|
||||
long interval = 1000L * 60L * 60L; // 1 hour
|
||||
mostUsedValues.cache(interval, 14, servletContext,
|
||||
Recipe.CUISINE_ATTRIBUTE);
|
||||
mostUsedValues.cache(interval, 16, servletContext,
|
||||
Recipe.MAIN_INGREDIENT_ATTRIBUTE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
/* Copyright (c) 2006 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.gbase.recipe;
|
||||
|
||||
import com.google.api.gbase.client.FeedURLFactory;
|
||||
import com.google.api.gbase.client.GoogleBaseEntry;
|
||||
import com.google.api.gbase.client.GoogleBaseFeed;
|
||||
import com.google.api.gbase.client.GoogleBaseQuery;
|
||||
import com.google.api.gbase.client.GoogleBaseService;
|
||||
import com.google.gdata.util.ServiceException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A recipe search.
|
||||
*
|
||||
* There will be such an object in all cases, even
|
||||
* if no query has been run. This object is created
|
||||
* by RecipeSearchServlet and displayed by the JSP.
|
||||
*/
|
||||
public class RecipeSearch {
|
||||
private final GoogleBaseService service;
|
||||
private Set<String> mainIngredient;
|
||||
private Set<String> cuisine;
|
||||
private Integer cookingTime;
|
||||
private String query;
|
||||
|
||||
/** The query string, with the unsupported characters replaced with spaces.*/
|
||||
private String queryClean;
|
||||
|
||||
/** The index of the first retrieved recipe. */
|
||||
private int startIndex = 0;
|
||||
|
||||
/** Set to true to perform the search on the user's items. */
|
||||
private boolean ownItems;
|
||||
|
||||
/** Total number of results, -1 means the query hasn't been run yet. */
|
||||
protected int total = -1;
|
||||
|
||||
protected List<Recipe> recipes;
|
||||
|
||||
private static final int DEFAULT_MAX_RESULTS = 10;
|
||||
private int maxResults = DEFAULT_MAX_RESULTS;
|
||||
|
||||
private final FeedURLFactory urlFactory;
|
||||
|
||||
/**
|
||||
* Create a new search.
|
||||
*
|
||||
* @param service Google data API service
|
||||
* @param urlFactory feed URL factory to be used when creating a Query
|
||||
* @param ownItems true to show only the items of the authenticated user
|
||||
*/
|
||||
public RecipeSearch(GoogleBaseService service,
|
||||
FeedURLFactory urlFactory,
|
||||
boolean ownItems) {
|
||||
this.service = service;
|
||||
this.urlFactory = urlFactory;
|
||||
this.ownItems = ownItems;
|
||||
|
||||
mainIngredient = null;
|
||||
cuisine = null;
|
||||
cookingTime = null;
|
||||
query = null;
|
||||
queryClean = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the search has been run and there is at least one result.
|
||||
*
|
||||
* @return true if the search has been run and there is at least one result
|
||||
*/
|
||||
public boolean hasResults() {
|
||||
return recipes != null && ! recipes.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the result of the search, if it has been run.
|
||||
*
|
||||
* @return a list of Recipe which might be empty if the
|
||||
* search has been run, or null if the search has not
|
||||
* been run yet
|
||||
*/
|
||||
public List<Recipe> getRecipes() {
|
||||
return recipes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the search will be done for the authenticated user's items only.
|
||||
*
|
||||
* @return true if the search returns only the items that belong to the
|
||||
* authenticated user
|
||||
*/
|
||||
public boolean isOwnItems() {
|
||||
return ownItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies if we are searching the authenticated user's items.
|
||||
*
|
||||
* @param ownItems true to search only the authenticated user's items
|
||||
*/
|
||||
public void setOwnItems(boolean ownItems) {
|
||||
this.ownItems = ownItems;
|
||||
}
|
||||
|
||||
/** Gets the current main ingredients, or null. */
|
||||
public Set<String> getMainIngredientValues() {
|
||||
return mainIngredient;
|
||||
}
|
||||
|
||||
/** Sets the main ingredient. */
|
||||
public void setMainIngredientValues(String[] mainIngredient) {
|
||||
this.mainIngredient = RecipeUtil.validateValues(mainIngredient);
|
||||
}
|
||||
|
||||
/** Gets the current cuisines, or null. */
|
||||
public Set<String> getCuisineValues() {
|
||||
return cuisine;
|
||||
}
|
||||
|
||||
/** Sets the cuisine. */
|
||||
public void setCuisineValues(String[] cuisine) {
|
||||
this.cuisine = RecipeUtil.validateValues(cuisine);
|
||||
}
|
||||
|
||||
/** Gets the current (maximum) cooking time, or null. */
|
||||
public Integer getCookingTime() {
|
||||
return cookingTime;
|
||||
}
|
||||
|
||||
/** Sets the current maximum cooking time. */
|
||||
public void setCookingTime(Integer cookingTime) {
|
||||
this.cookingTime = cookingTime;
|
||||
}
|
||||
|
||||
/** Gets the current page length. */
|
||||
public int getMaxResults() {
|
||||
return maxResults;
|
||||
}
|
||||
|
||||
/** Sets the page length. */
|
||||
public void setMaxResults(int maxResults) {
|
||||
this.maxResults = maxResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of recipes that matched the query, which
|
||||
* might be larger than the page length.
|
||||
*
|
||||
* @return the total, or -1 if the total is unknown, either because
|
||||
* the query has not been run or because the total was not in
|
||||
* the result
|
||||
*/
|
||||
public int getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index of the first result to return.
|
||||
*
|
||||
* @return a positive value
|
||||
*/
|
||||
public int getStartIndex() {
|
||||
return startIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index of the first result to return.
|
||||
* @param startIndex a positive value
|
||||
*/
|
||||
public void setStartIndex(int startIndex) {
|
||||
this.startIndex = startIndex;
|
||||
}
|
||||
|
||||
/** Gets the current page. */
|
||||
public int getCurrentPage() {
|
||||
return startIndex / maxResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a description of the retrieved (current) interval, showing
|
||||
* the index of the first item and the index of the last item.
|
||||
*
|
||||
* @return short description of the current page interval
|
||||
*/
|
||||
public String getCurrentPageInterval() {
|
||||
return "" + (startIndex + 1) + " - " +
|
||||
Math.min(startIndex + maxResults, total);
|
||||
}
|
||||
|
||||
/** Gets the number of pages needed to contain all the results. */
|
||||
public int getTotalPages() {
|
||||
if (total == 0) {
|
||||
return 0;
|
||||
}
|
||||
/* Using total-1, because if we have 10 results and 10 items per page,
|
||||
* we still want one page.
|
||||
*/
|
||||
return (total - 1) / maxResults;
|
||||
}
|
||||
|
||||
/** Runs the query and fills the result. */
|
||||
public void runQuery() throws IOException, ServiceException {
|
||||
GoogleBaseQuery query = createQuery();
|
||||
System.out.println("Searching: " + query.getUrl());
|
||||
GoogleBaseFeed feed = service.query(query);
|
||||
List<Recipe> result = new ArrayList<Recipe>(maxResults);
|
||||
for (GoogleBaseEntry entry : feed.getEntries()) {
|
||||
result.add(new Recipe(entry));
|
||||
}
|
||||
this.recipes = result;
|
||||
total = feed.getTotalResults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GoogleBaseQuery that searches for recipes, according to
|
||||
* the various properties of the RecipeSearch.
|
||||
*
|
||||
* @return a query to be used for querying with a
|
||||
* {@link com.google.api.gbase.client.GoogleBaseService
|
||||
* GoogleBaseService}
|
||||
* @see com.google.api.gbase.client.GoogleBaseService#query(com.google.gdata.client.Query)
|
||||
*/
|
||||
private GoogleBaseQuery createQuery() {
|
||||
URL queryUrl;
|
||||
if (ownItems) {
|
||||
queryUrl = urlFactory.getItemsFeedURL();
|
||||
} else {
|
||||
queryUrl = urlFactory.getSnippetsFeedURL();
|
||||
}
|
||||
GoogleBaseQuery query = new GoogleBaseQuery(queryUrl);
|
||||
query.setMaxResults(maxResults);
|
||||
if (startIndex > 0) {
|
||||
// the first index is 1
|
||||
query.setStartIndex(startIndex + 1);
|
||||
}
|
||||
query.setGoogleBaseQuery(createQueryString());
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a full text query out of the values of the query, mainIngredient,
|
||||
* cuisine and cookingTime.
|
||||
*
|
||||
* @return a query to be used for setting the full text query of a
|
||||
* {@link com.google.api.gbase.client.GoogleBaseQuery}
|
||||
* @see com.google.api.gbase.client.GoogleBaseQuery#setFullTextQuery(String)
|
||||
*/
|
||||
private String createQueryString() {
|
||||
StringBuffer retval = new StringBuffer(RecipeUtil.RECIPE_ITEMTYPE_QUERY);
|
||||
if (queryClean != null) {
|
||||
retval.append(queryClean);
|
||||
}
|
||||
appendAttributeCondition(retval, "main ingredient", mainIngredient, true);
|
||||
appendAttributeCondition(retval, "cuisine", cuisine, false);
|
||||
if (cookingTime != null) {
|
||||
Collection<String> cookingTimes = new ArrayList<String>();
|
||||
cookingTimes.add("0.." + cookingTime + " min");
|
||||
cookingTimes.add("0.." + cookingTime + " minutes");
|
||||
appendAttributeCondition(retval, "cooking time", cookingTimes, false);
|
||||
}
|
||||
return retval.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a filtering condition to a full text query.
|
||||
* It is composed by simple [name: value] conditions
|
||||
* joined by and AND or an OR operation.
|
||||
*
|
||||
* @param sb a StringBuffer for creating a full text query
|
||||
* @param name name of the attributes
|
||||
* @param values values the attributes have to match
|
||||
* @param isAnd true if the attributes have to match all the values,
|
||||
* false if the attributes have to match at least one value
|
||||
*/
|
||||
private static void appendAttributeCondition(StringBuffer sb,
|
||||
String name,
|
||||
Collection<String> values,
|
||||
boolean isAnd) {
|
||||
if (values != null && !values.isEmpty()) {
|
||||
sb.append(" (");
|
||||
Iterator iter = values.iterator();
|
||||
while (iter.hasNext()) {
|
||||
sb.append("[").append(name).append(": ").append(iter.next()).append("]");
|
||||
if (iter.hasNext()) {
|
||||
sb.append(isAnd ? " " : "|");
|
||||
}
|
||||
}
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true when the current page is not the first page. */
|
||||
public boolean hasPreviousPage() {
|
||||
return getCurrentPage() > 0;
|
||||
}
|
||||
|
||||
/** Returns true when the current page is not the last one. */
|
||||
public boolean hasNextPage() {
|
||||
return getTotalPages() > getCurrentPage();
|
||||
}
|
||||
|
||||
/** Gets the expected number of recipes for the next page. */
|
||||
public int getNextPageSize() {
|
||||
return Math.min(maxResults, total - (getCurrentPage() + 1) * maxResults);
|
||||
}
|
||||
|
||||
/** Gets a description of the query used in the search. */
|
||||
public StringBuffer getFilterDescription() {
|
||||
StringBuffer retval = new StringBuffer();
|
||||
if (queryClean != null && ! "".equals(queryClean)) {
|
||||
retval.append("<b>keywords</b> are <b>").
|
||||
append(queryClean).
|
||||
append("</b> ");
|
||||
}
|
||||
addCollectionDescription(retval, cuisine, "cuisine", false);
|
||||
addCollectionDescription(retval, mainIngredient, "main ingredient", true);
|
||||
if (cookingTime != null) {
|
||||
if (retval.length() > 0) {
|
||||
retval.append("and ");
|
||||
}
|
||||
retval.append("<b>cooking time</b> is under <b>").
|
||||
append(cookingTime).
|
||||
append(" ").
|
||||
append(RecipeUtil.COOKING_TIME_UNIT).
|
||||
append("</b> ");
|
||||
}
|
||||
if (retval.length() > 0) {
|
||||
retval.insert(0, "where ");
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
private static void addCollectionDescription(StringBuffer buffer,
|
||||
Collection<String> collection,
|
||||
String name,
|
||||
boolean isAnd) {
|
||||
if (collection != null && ! collection.isEmpty()) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append("and ");
|
||||
}
|
||||
buffer.append("<b>").append(name).append("</b> is");
|
||||
Iterator<String> iter = collection.iterator();
|
||||
while (iter.hasNext()) {
|
||||
buffer.append(" <b>").append(iter.next()).append("</b> ");
|
||||
if (iter.hasNext()) {
|
||||
buffer.append(isAnd ? "and " : "or ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the query string.
|
||||
*
|
||||
* @param query the query string, as provided by user
|
||||
* @throws NullPointerException if the {@code query} is null.
|
||||
*/
|
||||
public void setQuery(String query) {
|
||||
if (query != null) {
|
||||
this.query = query;
|
||||
this.queryClean = RecipeUtil.cleanQueryString(query);
|
||||
} else {
|
||||
throw new NullPointerException("Query must not be null.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original query string, as specified in the
|
||||
* {@link #setQuery(String)} method.
|
||||
*/
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/* Copyright (c) 2006 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.gbase.recipe;
|
||||
|
||||
import com.google.api.gbase.client.FeedURLFactory;
|
||||
import com.google.api.gbase.client.GoogleBaseService;
|
||||
import com.google.gdata.util.ServiceException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
|
||||
/**
|
||||
* Setup a {@link RecipeSearch} object and optionally fill it.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class RecipeSearchServlet extends HttpServlet {
|
||||
|
||||
private static final String START_INDEX_PARAMETER = "startIndex";
|
||||
|
||||
private static final String MAX_RESULTS_PARAMETER = "maxResults";
|
||||
|
||||
private static final String QUERY_PARAMETER = "query";
|
||||
|
||||
public static final String DISPLAY_JSP = "/WEB-INF/recipeSearch.jsp";
|
||||
|
||||
protected boolean ownItems;
|
||||
|
||||
protected FeedURLFactory urlFactory;
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig servletConfig) throws ServletException {
|
||||
super.init(servletConfig);
|
||||
ServletContext context = servletConfig.getServletContext();
|
||||
urlFactory = (FeedURLFactory)
|
||||
context.getAttribute(RecipeListener.FEED_URL_FACTORY_ATTRIBUTE);
|
||||
String scope = servletConfig.getInitParameter("scope");
|
||||
ownItems = "own".equals(scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Runs a recipe search.
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @throws ServletException
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
GoogleBaseService service = RecipeUtil.getGoogleBaseService(request,
|
||||
this.getServletContext());
|
||||
RecipeSearch recipeSearch;
|
||||
try {
|
||||
if (request.getParameter("query") == null) {
|
||||
recipeSearch = new RecipeSearch(service, urlFactory, ownItems);
|
||||
} else {
|
||||
recipeSearch = createRecipeSearch(service, request);
|
||||
}
|
||||
recipeSearch.runQuery();
|
||||
} catch (RecipeValidationException rve) {
|
||||
// internal exception in the argument handling (possibly incorrect args)
|
||||
RecipeUtil.forwardToErrorPage(request, response, rve.getMessage());
|
||||
return;
|
||||
} catch (ServiceException se) {
|
||||
// exception comming from Google Base
|
||||
RecipeUtil.logServiceException(this, se);
|
||||
RecipeUtil.forwardToErrorPage(request, response, se);
|
||||
return;
|
||||
}
|
||||
|
||||
RecipeUtil.setRecipeSearch(request, recipeSearch);
|
||||
// Forward to the JSP
|
||||
request.getRequestDispatcher(DISPLAY_JSP).forward(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and fills in a {@link RecipeSearch} object based on the content
|
||||
* of the {@code request}.
|
||||
*
|
||||
* @param service the object used to connect to the Google Base service
|
||||
* @param request the representation of the Http request
|
||||
* @throws RecipeValidationException if a parameter from the request has an
|
||||
* invalid value (cooking time, start index, max results are not valid
|
||||
* numbers).
|
||||
*/
|
||||
private RecipeSearch createRecipeSearch(GoogleBaseService service,
|
||||
HttpServletRequest request)
|
||||
throws RecipeValidationException {
|
||||
RecipeSearch search = new RecipeSearch(service, urlFactory, ownItems);
|
||||
|
||||
String query = request.getParameter(QUERY_PARAMETER);
|
||||
if (isSet(query)) {
|
||||
search.setQuery(query);
|
||||
}
|
||||
|
||||
String[] mainIngredient = request.getParameterValues(
|
||||
RecipeUtil.MAIN_INGREDIENT_PARAMETER);
|
||||
if (isSet(mainIngredient)) {
|
||||
search.setMainIngredientValues(mainIngredient);
|
||||
}
|
||||
|
||||
String[] cuisine = request.getParameterValues(
|
||||
RecipeUtil.CUISINE_PARAMETER);
|
||||
if (isSet(cuisine)) {
|
||||
search.setCuisineValues(cuisine);
|
||||
}
|
||||
|
||||
String cookingTime = request.getParameter(
|
||||
RecipeUtil.COOKING_TIME_PARAMETER);
|
||||
if (isSet(cookingTime)) {
|
||||
try {
|
||||
search.setCookingTime(new Integer(cookingTime));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new RecipeValidationException(String.format(
|
||||
"Cooking time is not a number (%s).", cookingTime));
|
||||
}
|
||||
}
|
||||
|
||||
String startIndex = request.getParameter(START_INDEX_PARAMETER);
|
||||
if (isSet(startIndex)) {
|
||||
try {
|
||||
search.setStartIndex(Integer.parseInt(startIndex));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new RecipeValidationException(String.format(
|
||||
"Start index is not a number (%s).", startIndex));
|
||||
}
|
||||
}
|
||||
|
||||
String maxResults = request.getParameter(MAX_RESULTS_PARAMETER);
|
||||
if (isSet(maxResults)) {
|
||||
try {
|
||||
search.setMaxResults(Integer.parseInt(maxResults));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new RecipeValidationException(String.format(
|
||||
"Max results is not a number (%s).", maxResults));
|
||||
}
|
||||
}
|
||||
|
||||
search.setOwnItems(ownItems);
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
private boolean isSet(String parameter) {
|
||||
return parameter != null && !"".equals(parameter);
|
||||
}
|
||||
|
||||
private boolean isSet(String[] parameter) {
|
||||
return parameter != null && parameter.length > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
/* Copyright (c) 2006 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.gbase.recipe;
|
||||
|
||||
import com.google.api.gbase.client.GoogleBaseService;
|
||||
import com.google.api.gbase.client.ServiceErrors;
|
||||
import com.google.gdata.util.ServiceException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Contains the names of the HTML input fields used to edit a recipe.
|
||||
* Has methods that are generally useful, for example for logging
|
||||
* or validating values.
|
||||
* Has methods to extract values from servlet context init parameters.
|
||||
* Has methods used for passing objects from a servlet to a JSP.
|
||||
*/
|
||||
public class RecipeUtil {
|
||||
|
||||
public static final String ID_PARAMETER = "oid";
|
||||
public static final String APPLICATION_NAME_PARAMETER = "applicationName";
|
||||
public static final String TITLE_PARAMETER = "title";
|
||||
public static final String URL_PARAMETER = "url";
|
||||
public static final String DESCRIPTION_PARAMETER = "description";
|
||||
public static final String MAIN_INGREDIENT_PARAMETER = "mainIngredient";
|
||||
public static final String CUISINE_PARAMETER = "cuisine";
|
||||
public static final String COOKING_TIME_PARAMETER = "cookingTime";
|
||||
public static final String DEVELOPER_KEY_PARAMETER = "key";
|
||||
|
||||
public static final String COOKING_TIME_UNIT = "minutes";
|
||||
|
||||
public static final String RECIPE_ATTRIBUTE = "recipe";
|
||||
public static final String RECIPESEARCH_ATTRIBUTE = "recipeSearch";
|
||||
public static final String RECIPESEARCH_ERROR = "recipeSearchError";
|
||||
public static final String RECIPESEARCH_ERROR_DESCRIPTION =
|
||||
"recipeSearchErrorDescription";
|
||||
public static final String RECIPESEARCH_ERROR_OBJECT =
|
||||
"recipeSearchErrorService";
|
||||
public static final String MESSAGE_ATTRIBUTE = "message";
|
||||
|
||||
public static final String RECIPE_ITEMTYPE_QUERY =
|
||||
"[item type : recipe | recipes]";
|
||||
|
||||
/** Pattern used for finding the unsupported characters in the query string.*/
|
||||
private static final Pattern QUERY_REPLACE_PATTERN =
|
||||
Pattern.compile("\\p{Punct}");
|
||||
|
||||
public static final String ERROR_JSP = "/WEB-INF/recipeError.jsp";
|
||||
|
||||
/**
|
||||
* Builds a HashSet containing the specified values,
|
||||
* filtering the null and empty ones.
|
||||
*
|
||||
* @param values usually an array returned by request.getParameterValues()
|
||||
* @return a HashSet containing the nonempty values
|
||||
*/
|
||||
public static Set<String> validateValues(String[] values) {
|
||||
Set<String> valuesList = new HashSet<String>();
|
||||
if (values != null) {
|
||||
for (String value : values) {
|
||||
value = cleanQueryString(value);
|
||||
if (value != null && ! "".equals(value)) {
|
||||
valuesList.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return valuesList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans the {@code searchString} set by the user, by removing the special
|
||||
* punctuation characters not allowed directly in a query.
|
||||
*
|
||||
* @param searchString the string set by the user
|
||||
* @return the String to be used for executing the query to Google Base.
|
||||
*/
|
||||
public static String cleanQueryString(String searchString) {
|
||||
Matcher matcher = QUERY_REPLACE_PATTERN.matcher(searchString);
|
||||
return matcher.replaceAll(" ").trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an exception in a convenient format,
|
||||
* using the log method of a servlet context.
|
||||
*
|
||||
* @param servlet the servlet used to log the exception
|
||||
* @param e exception to be logged
|
||||
*/
|
||||
public static void logServiceException(HttpServlet servlet,
|
||||
ServiceException e) {
|
||||
if (e.getResponseBody() != null) {
|
||||
// Log the full error message and response code.
|
||||
servlet.log(e.getMessage() + " " +
|
||||
e.getHttpErrorCodeOverride() + " " +
|
||||
e.getResponseContentType() + ": " +
|
||||
e.getResponseBody(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a RecipeSearch as an attribute of a HttpServletRequest.
|
||||
*
|
||||
* @param request a request that will be passed to a JSP
|
||||
* @param results the RecipeSearch, executed or not
|
||||
*/
|
||||
public static void setRecipeSearch(HttpServletRequest request,
|
||||
RecipeSearch results) {
|
||||
request.setAttribute(RECIPESEARCH_ATTRIBUTE, results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets from a HttpServletRequest a RecipeSearch that was previously set
|
||||
* using {@link #setRecipeSearch}.
|
||||
* If it is missing, a NullPointerException is thrown.
|
||||
*
|
||||
* @param request a request passed from a Servlet
|
||||
* @return a non-null RecipeSearch
|
||||
*/
|
||||
public static RecipeSearch getRecipeSearch(HttpServletRequest request) {
|
||||
RecipeSearch results = (RecipeSearch)request.getAttribute(
|
||||
RECIPESEARCH_ATTRIBUTE);
|
||||
if (null == results) {
|
||||
throw new NullPointerException("recipe search results are missing");
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error message, or {@code null} if no error message was set in the
|
||||
* request.
|
||||
*
|
||||
* @param request the request in which the method will locate the error
|
||||
* @return the error message, or {@code null} if no message was set
|
||||
*/
|
||||
public static String getRecipeError(HttpServletRequest request) {
|
||||
return (String)request.getAttribute(RECIPESEARCH_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error message, or {@code null} if no error message was set in the
|
||||
* request.
|
||||
*
|
||||
* @param request the request in which the method will locate the error
|
||||
* @return the error message, or {@code null} if no message was set
|
||||
*/
|
||||
public static String getRecipeErrorDescription(HttpServletRequest request) {
|
||||
return (String)request.getAttribute(RECIPESEARCH_ERROR_DESCRIPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the errors obtained from Google Base, or {@code null} if no service
|
||||
* error was set in the request.
|
||||
*
|
||||
* @param request the request in which the method will locate the error
|
||||
* @return the service errors, or {@code null} if no service errors were set
|
||||
*/
|
||||
public static ServiceErrors getRecipeErrorObject(HttpServletRequest request) {
|
||||
return (ServiceErrors)request.getAttribute(RECIPESEARCH_ERROR_OBJECT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards the request to the error page, for displaying the specified
|
||||
* {@code errorMessage} and the {@code description}. No service errors will
|
||||
* be displayed.
|
||||
*
|
||||
* @param request the request object
|
||||
* @param response the response object
|
||||
* @param errorMessage the error message to be displayed
|
||||
* @param description the description of the error message, {@code null} if
|
||||
* no description should be displayed.
|
||||
* @throws ServletException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void forwardToErrorPage(HttpServletRequest request,
|
||||
HttpServletResponse response, String errorMessage)
|
||||
throws ServletException, IOException {
|
||||
request.setAttribute(RECIPESEARCH_ERROR, errorMessage);
|
||||
request.getRequestDispatcher(ERROR_JSP).forward(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards the request to the error page, for displaying the information
|
||||
* contained by the {@code se} parameter. This method registers the service
|
||||
* errors too, using a {@link ServiceErrors} object.
|
||||
*
|
||||
* @param request the request object
|
||||
* @param response the response object
|
||||
* @param se the service error containing the information for the error page
|
||||
* @throws ServletException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void forwardToErrorPage(HttpServletRequest request,
|
||||
HttpServletResponse response, ServiceException se)
|
||||
throws ServletException, IOException {
|
||||
request.setAttribute(RECIPESEARCH_ERROR, se.getMessage());
|
||||
request.setAttribute(RECIPESEARCH_ERROR_DESCRIPTION, se.getResponseBody());
|
||||
request.setAttribute(RECIPESEARCH_ERROR_OBJECT, new ServiceErrors(se));
|
||||
request.getRequestDispatcher(ERROR_JSP).forward(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the GoogleBaseService object created by {@link AuthenticationFilter}
|
||||
* or creates a new one if <code>AuthenticationFilter</code> has not been
|
||||
* applied yet.
|
||||
*
|
||||
* @param req
|
||||
* @param servletContext
|
||||
* @return a GoogleBaseService object
|
||||
*/
|
||||
public static GoogleBaseService getGoogleBaseService(HttpServletRequest req,
|
||||
ServletContext servletContext) {
|
||||
GoogleBaseService service;
|
||||
service = (GoogleBaseService) req.getAttribute(
|
||||
AuthenticationFilter.SERVICE_ATTRIBUTE);
|
||||
if (service == null) {
|
||||
service = new GoogleBaseService(
|
||||
servletContext.getInitParameter(APPLICATION_NAME_PARAMETER),
|
||||
servletContext.getInitParameter(DEVELOPER_KEY_PARAMETER));
|
||||
req.setAttribute(AuthenticationFilter.SERVICE_ATTRIBUTE, service);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a MostUsedValues that was previously set using
|
||||
* {@link #setMostUsedValues}.
|
||||
*
|
||||
* @param servletContext
|
||||
* @return a non-null initialized MostUsedValues
|
||||
*/
|
||||
public static MostUsedValues getMostUsedValues(ServletContext servletContext)
|
||||
throws ServletException {
|
||||
MostUsedValues mostUsedValues = (MostUsedValues)
|
||||
servletContext.getAttribute(RecipeListener.MOST_USED_VALUES_ATTRIBUTE);
|
||||
if (null == mostUsedValues) {
|
||||
throw new ServletException("Most used values cache is missing");
|
||||
}
|
||||
return mostUsedValues;
|
||||
}
|
||||
|
||||
public static void setMostUsedValues(ServletContext servletContext,
|
||||
MostUsedValues mostUsedValues) {
|
||||
servletContext.setAttribute(RecipeListener.MOST_USED_VALUES_ATTRIBUTE,
|
||||
mostUsedValues);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* Copyright (c) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.gbase.recipe;
|
||||
|
||||
/**
|
||||
* Exception thrown when an invalid parameter is received by the Recipe Demo
|
||||
* Application.
|
||||
*/
|
||||
public class RecipeValidationException extends Exception {
|
||||
|
||||
public RecipeValidationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="demolabel">
|
||||
This recipe book is a demo of the Google Base Data API. <br/><a href="http://code.google.com/apis/base">Learn more and download API tools</a>.
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div id="footer">
|
||||
<a href="http://code.google.com/apis/base">Google Base Data API</a> -
|
||||
<a href="http://code.google.com/apis/base/terms.html">Terms and Conditions</a> -
|
||||
<a href="http://base.google.com">Google Base</a> -
|
||||
<a href="http://www.google.com">Google home</a>
|
||||
<p id="copyright">© Google 2006</p>
|
||||
</div>
|
||||
@@ -0,0 +1,68 @@
|
||||
<%--
|
||||
Copyright (c) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
--%>
|
||||
<%@ page import="sample.gbase.recipe.DisplayUtils"%>
|
||||
<%@ page import="sample.gbase.recipe.Recipe"%>
|
||||
<%@ page import="sample.gbase.recipe.RecipeSearch"%>
|
||||
<%@ page import="sample.gbase.recipe.RecipeUtil"%>
|
||||
<%RecipeSearch searchResults = RecipeUtil.getRecipeSearch(request);%>
|
||||
<div id="leftnav">
|
||||
<div id="search">
|
||||
<form name="searchForm" action="recipe<%= searchResults.isOwnItems() ? "List" : "Search" %>" method="GET"><h2>Find a recipe</h2>
|
||||
<table border="0" cellpadding="0" cellspacing="0" id="advancedsearch">
|
||||
<tr><th colspan="2">With the word or phrase</th></tr>
|
||||
<tr>
|
||||
<td><input type="text" name="query" size="25" class="txt"
|
||||
value='<%=DisplayUtils.escape(searchResults.getQuery()) %>'>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Cuisine type</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%DisplayUtils.printCheckboxes(out,
|
||||
RecipeUtil.CUISINE_PARAMETER,
|
||||
RecipeUtil.getMostUsedValues(pageContext.getServletContext()).getMostUsedValuesForAttribute(Recipe.CUISINE_ATTRIBUTE),
|
||||
searchResults.getCuisineValues()); %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Main ingredient</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%DisplayUtils.printCheckboxes(out,
|
||||
RecipeUtil.MAIN_INGREDIENT_PARAMETER,
|
||||
RecipeUtil.getMostUsedValues(pageContext.getServletContext()).getMostUsedValuesForAttribute(Recipe.MAIN_INGREDIENT_ATTRIBUTE),
|
||||
searchResults.getMainIngredientValues()); %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Cooking time</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><input name="cookingTime" type="text" size="4"
|
||||
value='<%=DisplayUtils.escape(searchResults.getCookingTime()) %>'
|
||||
> <%=RecipeUtil.COOKING_TIME_UNIT %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
<button onclick="document.searchForm.startIndex.value='0'; document.searchForm.submit();"
|
||||
style="font-weight:bold; -moz-border-radius:3px;border: 2px outset #bbbbbb;font-size:124%; padding:2px 1em;">Search</button>
|
||||
<input type="hidden" name="startIndex" value="0"></form>
|
||||
</div>
|
||||
|
||||
<p id="poweredby">
|
||||
Powered by<br>
|
||||
<a href="http://base.google.com/"><img src="googleBase.gif" border="0" alt="Google Base" vspace="2"></a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,71 @@
|
||||
<%--
|
||||
Copyright (c) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
--%>
|
||||
<%@ page contentType="text/html; charset=UTF-8"%>
|
||||
<%@ page import="sample.gbase.recipe.DisplayUtils"%>
|
||||
<%@ page import="sample.gbase.recipe.Recipe"%>
|
||||
<%@ page import="sample.gbase.recipe.RecipeUtil"%>
|
||||
<html>
|
||||
<head>
|
||||
<title>Google Base API Demo: Recipe Book</title>
|
||||
<link rel="stylesheet" href="style.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<%@ include file="demoLabel.inc" %>
|
||||
|
||||
<%Recipe recipe = (Recipe) request.getAttribute(RecipeUtil.RECIPE_ATTRIBUTE); %>
|
||||
<h1><a href="recipeSearch" class="on">All Recipes</a> > <%=DisplayUtils.escape(recipe.getTitle()) %></h1>
|
||||
|
||||
<%@ include file="leftNav.jsp" %>
|
||||
<div id="body" class="main">
|
||||
<div id="singleitem">
|
||||
<h3 class="itemtitle"><%=DisplayUtils.escape(recipe.getTitle()) %></h3>
|
||||
<h4 class="postdate"><%=recipe.getPostedOnAsString(true) %></h4>
|
||||
<%if (recipe.hasCuisine()) { %>
|
||||
<h5>Cuisine: <%=DisplayUtils.printList(recipe.getCuisine()) %></h5><br/>
|
||||
<%} %>
|
||||
<%if (recipe.hasCookingTime()) { %>
|
||||
<h5>Cooking time: <%=recipe.getCookingTime().getValue() %> <%=DisplayUtils.escape(recipe.getCookingTime().getUnit()) %></h5>
|
||||
<%} %>
|
||||
|
||||
<%if (recipe.hasMainIngredient()) { %>
|
||||
<div class="ingredients">
|
||||
<ul>
|
||||
<%for (String ingredient : recipe.getMainIngredient()) { %>
|
||||
<li><%=DisplayUtils.escape(ingredient) %></li>
|
||||
<%} %>
|
||||
</ul>
|
||||
</div>
|
||||
<%} %>
|
||||
<br/>
|
||||
<div class="preparation">
|
||||
<%=DisplayUtils.escape(recipe.getDescription()) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br clear="all">
|
||||
|
||||
<%@ include file="demoLabel.inc" %>
|
||||
</div>
|
||||
|
||||
<%@ include file="footer.inc" %>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<%--
|
||||
Copyright (c) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
--%>
|
||||
<%@ page contentType="text/html; charset=UTF-8"%>
|
||||
<%@ page import="sample.gbase.recipe.DisplayUtils"%>
|
||||
<%@ page import="sample.gbase.recipe.Recipe"%>
|
||||
<%@ page import="sample.gbase.recipe.RecipeUtil"%>
|
||||
<html>
|
||||
<head>
|
||||
<title>Google Base API Demo: Recipe Book</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<style type="text/css">
|
||||
.main {
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<%@ include file="demoLabel.inc" %>
|
||||
|
||||
<%Recipe recipe = (Recipe) request.getAttribute(RecipeUtil.RECIPE_ATTRIBUTE); %>
|
||||
<%String message = (String) request.getAttribute(RecipeUtil.MESSAGE_ATTRIBUTE); %>
|
||||
<h1><a href="recipeList" class="on">My Recipes</a> > <%=recipe.isNew() ? "Add" : "Update" %> a recipe</h1>
|
||||
|
||||
<div id="body" class="main">
|
||||
|
||||
<div id="newitem">
|
||||
<%=message %>
|
||||
<form name="newitem" method="POST" action="recipe<%=recipe.isNew() ? "Add" : "Update?" + RecipeUtil.ID_PARAMETER + "=" + DisplayUtils.escape(recipe.getId()) %>"/>
|
||||
|
||||
<table cellpadding="0" cellspacing="0" border="0" id="createrecipe">
|
||||
<tr>
|
||||
<th>Recipe title *</th>
|
||||
<td><input type="text" name="<%=RecipeUtil.TITLE_PARAMETER %>"
|
||||
value="<%=DisplayUtils.escape(recipe.getTitle()) %>"
|
||||
class="txt" size="35"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Cuisine type *</th>
|
||||
<td>
|
||||
<%DisplayUtils.printCheckboxes(out,
|
||||
RecipeUtil.CUISINE_PARAMETER,
|
||||
RecipeUtil.getMostUsedValues(pageContext.getServletContext()).getMostUsedValuesForAttribute(Recipe.CUISINE_ATTRIBUTE),
|
||||
recipe.getCuisine()); %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
<th>Instructions *</th>
|
||||
<td><textarea name="<%=RecipeUtil.DESCRIPTION_PARAMETER %>"
|
||||
cols="40" rows="8"
|
||||
><%=DisplayUtils.escape(recipe.getDescription()) %></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Main ingredients *</th>
|
||||
<td>
|
||||
<%DisplayUtils.printCheckboxes(out,
|
||||
RecipeUtil.MAIN_INGREDIENT_PARAMETER,
|
||||
RecipeUtil.getMostUsedValues(pageContext.getServletContext()).getMostUsedValuesForAttribute(Recipe.MAIN_INGREDIENT_ATTRIBUTE),
|
||||
recipe.getMainIngredient()); %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>URL</th>
|
||||
<td><input type="text" name="<%=RecipeUtil.URL_PARAMETER %>"
|
||||
value="<%=DisplayUtils.escape(recipe.getUrl()) %>"
|
||||
class="txt" size="40"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Cooking time</th>
|
||||
<td><input type="text" name="<%=RecipeUtil.COOKING_TIME_PARAMETER %>"
|
||||
value="<%=recipe.hasCookingTime() ? recipe.getCookingTime().getValue() : "" %>"
|
||||
class="txt" size="4"> <%=RecipeUtil.COOKING_TIME_UNIT %></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
This recipe will be publicly viewable on the internet once you click "Publish". <br> <br>
|
||||
<input type="submit" value="Publish this recipe" style="font-weight:bold;">
|
||||
<button onclick="history.go(-1); return false;">Cancel</button>
|
||||
</p>
|
||||
<p>* Mandatory attributes</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<br clear="all">
|
||||
|
||||
<%@ include file="demoLabel.inc" %>
|
||||
</div>
|
||||
|
||||
<%@ include file="footer.inc" %>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<%--
|
||||
Copyright (c) 2007 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
--%>
|
||||
<%@ page contentType="text/html; charset=UTF-8"%>
|
||||
<%@ page import="com.google.api.gbase.client.ServiceErrors"%>
|
||||
<%@ page import="sample.gbase.recipe.RecipeUtil"%>
|
||||
<%@page import="sample.gbase.recipe.DisplayUtils"%>
|
||||
<%@page import="com.google.api.gbase.client.ServiceError"%>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Google Base API demo: Recipe Book</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<%@ include file="demoLabel.inc" %>
|
||||
<%
|
||||
String error = RecipeUtil.getRecipeError(request);
|
||||
String description = RecipeUtil.getRecipeErrorDescription(request);
|
||||
ServiceErrors serviceErrors = RecipeUtil.getRecipeErrorObject(request);
|
||||
%>
|
||||
|
||||
<div style="height:70%; padding-top: 20px;">
|
||||
An error has occured. Hit the browser's Back button for continuing to use
|
||||
the Recipe Demo Application. <br/>
|
||||
|
||||
<%if (error != null) { %>
|
||||
<div class="errordiv">
|
||||
<span class="errormessage"><%= DisplayUtils.escape(error) %></span>
|
||||
<br/>
|
||||
</div>
|
||||
<%} // has error %>
|
||||
|
||||
<%if (description != null) { %>
|
||||
<div class="errordiv">
|
||||
Detailed information about the error: <br/>
|
||||
<pre><%= DisplayUtils.escape(description) %></pre>
|
||||
</div>
|
||||
<%} // has description %>
|
||||
|
||||
<%if (serviceErrors != null) { %>
|
||||
<div class="errordiv">
|
||||
Detailed information about the Google Base service error: <br/>
|
||||
<ul>
|
||||
<%for (ServiceError serviceError : serviceErrors.getAllErrors()) { %>
|
||||
<li> <%= DisplayUtils.escape(serviceError.getType()) %> : <%= DisplayUtils.escape(serviceError.getReason()) %>
|
||||
<%} // for each service error %>
|
||||
</ul>
|
||||
|
||||
<%if (serviceErrors.getAllErrors().size() == 0) { %>
|
||||
<i>No service errors could be parsed</i>
|
||||
<%} // if no service errors registered %>
|
||||
</div>
|
||||
<%} // has errors object %>
|
||||
|
||||
</div>
|
||||
|
||||
<br clear="all">
|
||||
<%@ include file="demoLabel.inc" %>
|
||||
</div>
|
||||
|
||||
<%@ include file="footer.inc" %>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,138 @@
|
||||
<%--
|
||||
Copyright (c) 2006 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
--%>
|
||||
<%@ page contentType="text/html; charset=UTF-8"%>
|
||||
<%@ page import="sample.gbase.recipe.DisplayUtils"%>
|
||||
<%@ page import="sample.gbase.recipe.Recipe"%>
|
||||
<%@ page import="sample.gbase.recipe.RecipeSearch"%>
|
||||
<%@ page import="sample.gbase.recipe.RecipeUtil"%>
|
||||
<%@ page import="java.util.Iterator"%>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Google Base API demo: Recipe Book</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="content">
|
||||
<%@ include file="demoLabel.inc" %>
|
||||
|
||||
<%RecipeSearch recipeSearch = RecipeUtil.getRecipeSearch(request); %>
|
||||
<%if (recipeSearch.isOwnItems()) { %>
|
||||
<a href="recipeSearch" class="toplink">All recipes</a>
|
||||
<%} else { %>
|
||||
<a href="recipeList" class="toplink">My recipes</a>
|
||||
<%} %>
|
||||
|
||||
|
||||
<%if (recipeSearch.isOwnItems()) { %>
|
||||
<a href="recipeAdd" class="toplink">Add a recipe</a>
|
||||
<%} %>
|
||||
|
||||
<h1>
|
||||
<%if (recipeSearch.isOwnItems()) { %>
|
||||
<a href="recipeList" class="on">My Recipes</a>
|
||||
<%} else { %>
|
||||
<a href="recipeSearch" class="on">All Recipes</a>
|
||||
<%} %>
|
||||
> Search
|
||||
<%if (recipeSearch.getTotal() > 0) { %>
|
||||
results
|
||||
<%} %>
|
||||
</h1>
|
||||
|
||||
<%@ include file="leftNav.jsp" %>
|
||||
|
||||
<div id="body" class="main">
|
||||
<div id="searchresults">
|
||||
|
||||
<script language="JavaScript">
|
||||
function gotoPage(index) {
|
||||
document.searchForm.reset();
|
||||
document.searchForm.startIndex.value =
|
||||
index*<%=recipeSearch.getMaxResults()%>;
|
||||
document.searchForm.submit();
|
||||
}
|
||||
function narrowSearchByIngredient(ingredient) {
|
||||
document.searchForm.reset();
|
||||
document.searchForm.othermainIngredient.value = ingredient;
|
||||
document.searchForm.submit();
|
||||
}
|
||||
function confirmDelete(oid) {
|
||||
if (confirm("Do you really want to delete this recipe?")) {
|
||||
window.location = "recipeDelete?oid=" + oid;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<%if (recipeSearch.getTotal() >= 0) {%>
|
||||
<%if (recipeSearch.getTotal() == 0) {%>
|
||||
<h4>Your search for recipes
|
||||
<%=recipeSearch.getFilterDescription() %>
|
||||
did not match any recipes.</h4>
|
||||
<%} %>
|
||||
<%if (recipeSearch.hasResults()) {%>
|
||||
<h4><%=recipeSearch.getTotal()%> recipes
|
||||
<%=recipeSearch.getFilterDescription() %>
|
||||
- showing <%=recipeSearch.getCurrentPageInterval()%></h4>
|
||||
<%for (Iterator iter = recipeSearch.getRecipes().iterator(); iter.hasNext(); ) {%>
|
||||
<%Recipe recipe = (Recipe)iter.next();%>
|
||||
<p>
|
||||
<a href="recipeDisplay?oid=<%=recipe.getId()%>" class="m"><%=DisplayUtils.escape(recipe.getTitle()).toUpperCase() %></a>
|
||||
Main ingredient:
|
||||
<%for (Iterator<String> ingredientIter = recipe.getMainIngredient().iterator(); ingredientIter.hasNext(); ) { %>
|
||||
<%String ingredient = ingredientIter.next(); %>
|
||||
<a href="javascript:narrowSearchByIngredient('<%=ingredient%>')" class="r"><%=ingredient%></a><%=ingredientIter.hasNext() ? ", " : "" %>
|
||||
<%} %>
|
||||
<br>
|
||||
posted on <%=recipe.getPostedOnAsString(false)%>
|
||||
by <%=DisplayUtils.escape(recipe.getPostedBy())%><br>
|
||||
|
||||
<%if (recipeSearch.isOwnItems()) { %>
|
||||
<a href="recipeUpdate?oid=<%=recipe.getId() %>">Update</a> |
|
||||
<a href="javascript:confirmDelete('<%=recipe.getId() %>')">Delete</a>
|
||||
<%} %>
|
||||
</p>
|
||||
<%} // for recipe in recipeSearch.getRecipes()%>
|
||||
<%} // if recipeSearch.hasResults%>
|
||||
|
||||
<%if (recipeSearch.getTotal() > recipeSearch.getMaxResults()) { //results don't fit on one page?%>
|
||||
<p class="pagination">
|
||||
Results <%=recipeSearch.getCurrentPageInterval() %> of <%=recipeSearch.getTotal() %><br/>
|
||||
<%if (recipeSearch.hasPreviousPage()) {%>
|
||||
<a href="javascript:gotoPage(<%=recipeSearch.getCurrentPage() - 1 %>)"
|
||||
style="margin-right:1em;"
|
||||
class="next">« Previous <%=recipeSearch.getMaxResults() %></a>
|
||||
<%} %>
|
||||
<%if (recipeSearch.hasNextPage()) {%>
|
||||
<a href="javascript:gotoPage(<%=recipeSearch.getCurrentPage() + 1 %>)"
|
||||
class="next">Next <%=recipeSearch.getNextPageSize() %> »</a>
|
||||
<%} %>
|
||||
</p>
|
||||
<%} // pages? %>
|
||||
<%} //total >= 0 %>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<br clear="all">
|
||||
<%@ include file="demoLabel.inc" %>
|
||||
</div>
|
||||
|
||||
<%@ include file="footer.inc" %>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,125 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
|
||||
<web-app>
|
||||
<display-name>Google Base Recipe Search</display-name>
|
||||
<description>Search Recipes on Google Base</description>
|
||||
<context-param>
|
||||
<param-name>baseUrl</param-name>
|
||||
<param-value>http://www.google.com/base/</param-value>
|
||||
<description>URL of the google base server to connect to.</description>
|
||||
</context-param>
|
||||
<context-param>
|
||||
<param-name>applicationName</param-name>
|
||||
<param-value>Google-RecipeDemo-1.0</param-value>
|
||||
<description>Application name used when authenticating.</description>
|
||||
</context-param>
|
||||
<context-param>
|
||||
<param-name>key</param-name>
|
||||
<param-value></param-value>
|
||||
<description>Developer key used to authenticate against the Google Base data API servers.</description>
|
||||
</context-param>
|
||||
<filter>
|
||||
<filter-name>AuthenticationFilter</filter-name>
|
||||
<filter-class>sample.gbase.recipe.AuthenticationFilter</filter-class>
|
||||
<init-param>
|
||||
<param-name>authsubProtocol</param-name>
|
||||
<param-value>https</param-value>
|
||||
<description>Protocol to be used when connecting to AuthSub.</description>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>authsubHostname</param-name>
|
||||
<param-value>www.google.com</param-value>
|
||||
<description>Hostname of the authentication server.</description>
|
||||
</init-param>
|
||||
</filter>
|
||||
<listener>
|
||||
<listener-class>sample.gbase.recipe.RecipeListener</listener-class>
|
||||
</listener>
|
||||
<filter-mapping>
|
||||
<filter-name>AuthenticationFilter</filter-name>
|
||||
<servlet-name>OwnRecipeSearchServlet</servlet-name>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>AuthenticationFilter</filter-name>
|
||||
<servlet-name>RecipeAddServlet</servlet-name>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>AuthenticationFilter</filter-name>
|
||||
<servlet-name>RecipeUpdateServlet</servlet-name>
|
||||
</filter-mapping>
|
||||
<filter-mapping>
|
||||
<filter-name>AuthenticationFilter</filter-name>
|
||||
<servlet-name>RecipeDeleteServlet</servlet-name>
|
||||
</filter-mapping>
|
||||
<servlet>
|
||||
<servlet-name>AllRecipeSearchServlet</servlet-name>
|
||||
<servlet-class>sample.gbase.recipe.RecipeSearchServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>scope</param-name>
|
||||
<param-value>all</param-value>
|
||||
</init-param>
|
||||
</servlet>
|
||||
<servlet>
|
||||
<servlet-name>OwnRecipeSearchServlet</servlet-name>
|
||||
<servlet-class>sample.gbase.recipe.RecipeSearchServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>scope</param-name>
|
||||
<param-value>own</param-value>
|
||||
</init-param>
|
||||
</servlet>
|
||||
<servlet>
|
||||
<servlet-name>RecipeAddServlet</servlet-name>
|
||||
<servlet-class>sample.gbase.recipe.RecipeActionServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>action</param-name>
|
||||
<param-value>add</param-value>
|
||||
</init-param>
|
||||
</servlet>
|
||||
<servlet>
|
||||
<servlet-name>RecipeUpdateServlet</servlet-name>
|
||||
<servlet-class>sample.gbase.recipe.RecipeActionServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>action</param-name>
|
||||
<param-value>update</param-value>
|
||||
</init-param>
|
||||
</servlet>
|
||||
<servlet>
|
||||
<servlet-name>RecipeDeleteServlet</servlet-name>
|
||||
<servlet-class>sample.gbase.recipe.RecipeActionServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>action</param-name>
|
||||
<param-value>delete</param-value>
|
||||
</init-param>
|
||||
</servlet>
|
||||
<servlet>
|
||||
<servlet-name>RecipeDisplayServlet</servlet-name>
|
||||
<servlet-class>sample.gbase.recipe.RecipeDisplayServlet</servlet-class>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>AllRecipeSearchServlet</servlet-name>
|
||||
<url-pattern>/recipeSearch</url-pattern>
|
||||
</servlet-mapping>
|
||||
<servlet-mapping>
|
||||
<servlet-name>OwnRecipeSearchServlet</servlet-name>
|
||||
<url-pattern>/recipeList</url-pattern>
|
||||
</servlet-mapping>
|
||||
<servlet-mapping>
|
||||
<servlet-name>RecipeAddServlet</servlet-name>
|
||||
<url-pattern>/recipeAdd</url-pattern>
|
||||
</servlet-mapping>
|
||||
<servlet-mapping>
|
||||
<servlet-name>RecipeUpdateServlet</servlet-name>
|
||||
<url-pattern>/recipeUpdate</url-pattern>
|
||||
</servlet-mapping>
|
||||
<servlet-mapping>
|
||||
<servlet-name>RecipeDeleteServlet</servlet-name>
|
||||
<url-pattern>/recipeDelete</url-pattern>
|
||||
</servlet-mapping>
|
||||
<servlet-mapping>
|
||||
<servlet-name>RecipeDisplayServlet</servlet-name>
|
||||
<url-pattern>/recipeDisplay</url-pattern>
|
||||
</servlet-mapping>
|
||||
<welcome-file-list>
|
||||
<welcome-file>index.jsp</welcome-file>
|
||||
</welcome-file-list>
|
||||
</web-app>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1 @@
|
||||
<%response.sendRedirect(request.getContextPath() + "/recipeSearch");%>
|
||||
@@ -0,0 +1,89 @@
|
||||
body {
|
||||
font-family: geneva,tahoma,arial,sans-serif; font-size: 83%;
|
||||
background: #FFFFCC;
|
||||
text-align:center;
|
||||
vertical-align:top;
|
||||
}
|
||||
td {
|
||||
font-size: 78%;
|
||||
vertical-align:top;
|
||||
}
|
||||
th {
|
||||
font-size: 78%;
|
||||
text-align: left;
|
||||
vertical-align:top;
|
||||
}
|
||||
|
||||
a:link { color: #0000CC; }
|
||||
a:visited { color:#0000CC; }
|
||||
a.on:visited { color: #0000CC; }
|
||||
.toplink:visited { color: #0000CC; }
|
||||
.demolabel a:link { color: #0000E0!important; font-weight:bold; }
|
||||
|
||||
a.m { display:block; }
|
||||
a.r { color: #6666cc; text-decoration:none;}
|
||||
|
||||
#search th { padding: 1em 0 2px 0; }
|
||||
#newitem th { padding: .8ex 1em 2px 0px; }
|
||||
#newitem td { padding: 2px 1em 2px 0px; }
|
||||
|
||||
.demolabel { background: #ffffff; font-size: 124%; text-align:center; padding: .5em; -moz-border-radius:4px; border: 1px solid #EEEE66;}
|
||||
|
||||
|
||||
input.txt, textarea { border: 1px inset #999999; -moz-border-radius: 3px; padding:2px;}
|
||||
textarea { font-family: geneva, tahoma, arial, sans-serif; font-size: 105%; }
|
||||
label { cursor: pointer; }
|
||||
ul.inputlist {
|
||||
padding-left:0;
|
||||
list-style-type:none;
|
||||
}
|
||||
.inputlist li { padding-right: 1em; }
|
||||
|
||||
#content { text-align:left; margin: 0 15% 0 15%;}
|
||||
#leftnav {
|
||||
width: 30%;
|
||||
float:left;
|
||||
}
|
||||
#search {
|
||||
border:1px solid #EEEE66;
|
||||
-moz-border-radius:12px;
|
||||
background: #FFFFe0;
|
||||
padding:.5em;
|
||||
}
|
||||
#rpp { float:right; }
|
||||
.next { font-weight: bold; font-size: 124%; }
|
||||
.main {
|
||||
margin-left: 33%;
|
||||
border: 1px solid #EEEE66;
|
||||
-moz-border-radius:8px;
|
||||
background: #FFFFe0;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#footer { margin-top: 2em; }
|
||||
#footer p { margin:0; }
|
||||
|
||||
h2 { margin:0; }
|
||||
h3 { margin:0; }
|
||||
h4 { margin:0; font-size: 100%; font-weight: normal;}
|
||||
h5 { margin:0; font-weight: normal; display:inline; font-size: 100%;}
|
||||
|
||||
.toplink {
|
||||
float:right;
|
||||
font-weight:bold;
|
||||
margin-left:1.6em;
|
||||
padding-top:1.6em;
|
||||
font-size: 124%;
|
||||
}
|
||||
|
||||
#poweredby {
|
||||
font-size: 82%;
|
||||
margin-left:3%;
|
||||
}
|
||||
#poweredby img { height: 27px; width: 75px; }
|
||||
|
||||
.instructions { font-size: 78%; color: #000000;}
|
||||
.instructions i { font-style: normal; color:#008000; }
|
||||
|
||||
.errormessage { font-weight: bold; color: #CC1100; margin-bottom: 0.5em; }
|
||||
.errordiv { padding-top: 35px; }
|
||||
Reference in New Issue
Block a user