All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.swm.gwt.linker.server.Html5ManifestServletBase Maven / Gradle / Ivy

There is a newer version: 3.1
Show newest version
package de.swm.gwt.linker.server;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import de.swm.gwt.linker.ManifestWriter;
import de.swm.gwt.linker.PermutationMapLinker;
import de.swm.gwt.linker.XMLPermutationProvider;
import de.swm.gwt.linker.XMLPermutationProviderException;
import de.swm.gwt.linker.server.propertyprovider.MgwtOsPropertyProvider;
import de.swm.gwt.linker.server.propertyprovider.PropertyProvider;
import de.swm.gwt.linker.server.propertyprovider.PropertyProviderException;

/**
 * Base class for servlet which delivers application cache manifest for a specific GWT permutation.
 * Modified by Alexander Vilbig (SWM Services GmbH) from original MGWT version to improve matching of user agent and locale properties.
 * 
 * @author Daniel Kurka (see http://code.google.com/p/mgwt/)
 *
 */
public class Html5ManifestServletBase extends HttpServlet {

	private static final long serialVersionUID = -2540671294104865306L;
	private XMLPermutationProvider permutationProvider;

	private Map propertyProviders = new HashMap();
	Map> permutationMap = null;

	public Html5ManifestServletBase() {
		permutationProvider = new XMLPermutationProvider();

	}

	protected void addPropertyProvider(PropertyProvider propertyProvider) {
		propertyProviders.put(propertyProvider.getPropertyName(), propertyProvider);
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

		String moduleName = getModuleName(req);

		String baseUrl = getBaseUrl(req);

		initPermutationMap(baseUrl, moduleName);

		Set computedBindings = calculateBindinPropertiesForClient(req);

		// remove locale and user agent property bindings if not present in permutation map
		Set filteredBindings = computedBindings;
		if (!containsPermutationProperty("locale")) {
			log("Removing locale from matched properties...");
			filteredBindings = filterProperty("locale", filteredBindings);
		}
		if (!containsPermutationProperty("user.agent")) {			
			log("Removing user agent from matched properties...");
			filteredBindings = filterProperty("user.agent", filteredBindings);
		}

		log("Matching following binding properties: " + filteredBindings);
		String strongName = determinePermutationStrongName(filteredBindings);
		
		if (strongName == null) {
			log("Setting locale to default in second round of matching...");
			BindingProperty localeProperty = null;
			for (BindingProperty property : filteredBindings) {
				if (property.getName().equals("locale")) {
					localeProperty = property;
				}
			}
			if (localeProperty != null) {
				filteredBindings.remove(localeProperty);
				filteredBindings.add(new BindingProperty("locale", "default"));
			}
			strongName = determinePermutationStrongName(filteredBindings);
		}

		if (strongName != null) {
			String manifest = readManifest(baseUrl + moduleName + "/" + strongName
					+ PermutationMapLinker.PERMUTATION_MANIFEST_FILE_ENDING);
			serveStringManifest(req, resp, manifest);
			return;
		}

		boolean isIPhoneWithoutCookie = isIphoneWithoutCookie(computedBindings);
		boolean isIPadWithoutCookie = isIpadWithoutCookie(computedBindings);

		if (isIPhoneWithoutCookie || isIPadWithoutCookie) {
			Set nonRetinaMatch = new HashSet();
			Set retinaMatch = new HashSet();

			if (isIPhoneWithoutCookie) {
				computedBindings.remove(MgwtOsPropertyProvider.iPhone_undefined);
				nonRetinaMatch.add(MgwtOsPropertyProvider.iPhone);
				retinaMatch.add(MgwtOsPropertyProvider.retina);
			}

			if (isIPadWithoutCookie) {
				computedBindings.remove(MgwtOsPropertyProvider.iPad_undefined);
				nonRetinaMatch.add(MgwtOsPropertyProvider.iPad);
				retinaMatch.add(MgwtOsPropertyProvider.iPad_retina);
			}

			nonRetinaMatch.addAll(computedBindings);
			retinaMatch.addAll(computedBindings);

			String moduleNameNonRetina = determinePermutationStrongName(nonRetinaMatch);
			String moduleNameRetina = determinePermutationStrongName(retinaMatch);

			if (moduleNameNonRetina != null && moduleNameRetina != null) {

				// load files for both permutations
				Set filesForPermutation = getFilesForPermutation(baseUrl, moduleName, moduleNameNonRetina);
				filesForPermutation.addAll(getFilesForPermutation(baseUrl, moduleName, moduleNameRetina));

				// dynamically write a new manifest..
				ManifestWriter manifestWriter = new ManifestWriter();
				String writeManifest = manifestWriter.writeManifest(new HashSet(), filesForPermutation);
				serveStringManifest(req, resp, writeManifest);
				return;
			}
		}

		// if we got here we just don`t know the device react with 500 -> no
		// manifest...

		throw new ServletException("unkown device for bindings " + computedBindings);

	}

	protected String getBaseUrl(HttpServletRequest req) {
		String base = req.getServletPath();
		// cut off module
		return base.substring(0, base.lastIndexOf("/") + 1);
	}

	public boolean isIphoneWithoutCookie(Set bps) {
		for (BindingProperty bp : bps) {
			if ("mgwt.os".equals(bp.getName())) {
				if ("iphone_undefined".equals(bp.getValue())) {
					// oh shit this is an iphone
					// so now we need to serve two manifests
					// retina...
					// non retina

					return true;

				}
			}
		}
		return false;
	}

	public boolean isIpadWithoutCookie(Set bps) {
		for (BindingProperty bp : bps) {
			if ("mgwt.os".equals(bp.getName())) {
				if ("ipad_undefined".equals(bp.getValue())) {
					// oh shit this is an ipad
					// so now we need to serve two manifests
					// retina...
					// non retina

					return true;

				}
			}
		}
		return false;
	}

	public Set getFilesForPermutation(String baseUrl, String moduleName, String permutation)
			throws ServletException {
		String fileName = baseUrl + moduleName + "/" + permutation + PermutationMapLinker.PERMUTATION_FILE_ENDING;
		XMLPermutationProvider xmlPermutationProvider = new XMLPermutationProvider();

		try {
			File file = new File(getServletContext().getRealPath(fileName));
			InputStream inputStream = new FileInputStream(file);
			return xmlPermutationProvider.getPermutationFiles(inputStream);
		} catch (XMLPermutationProviderException e) {
			log("can not read permutation file");
			throw new ServletException("can not read permutation file", e);
		} catch (FileNotFoundException e) {
			log("can not read permutation file");
			throw new ServletException("can not read permutation file", e);
		}
	}

	public String readManifest(String filePath) throws ServletException {
		File manifestFile = new File(getServletContext().getRealPath(filePath));

		StringWriter manifestWriter = new StringWriter();
		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader(manifestFile));
			String line = null;

			while ((line = br.readLine()) != null) {

				manifestWriter.append(line + "\n");
			}

			return manifestWriter.toString();
		} catch (FileNotFoundException e) {
			log("could not find manifest file", e);
			throw new ServletException("can not find manifest file", e);
		} catch (IOException e) {
			log("error while reading manifest file", e);
			throw new ServletException("error while reading manifest file", e);
		} finally {
			if (br != null) {
				try {
					br.close();
				} catch (IOException e) {

				}
			}
		}
	}

	/**
	 * @param req
	 * @return
	 * @throws PropertyProviderException
	 */
	public Set calculateBindinPropertiesForClient(HttpServletRequest req) throws ServletException {

		try {
			Set computedBindings = new HashSet();

			Set> set = propertyProviders.entrySet();
			for (Entry entry : set) {
				String varValue = entry.getValue().getPropertyValue(req);
				BindingProperty bindingProperty = new BindingProperty(entry.getKey(), varValue);
				computedBindings.add(bindingProperty);
			}
			return computedBindings;
		} catch (PropertyProviderException e) {
			log("can not calculate properties for client", e);
			throw new ServletException("can not calculate properties for client", e);
		}

	}

	public void serveStringManifest(HttpServletRequest req, HttpServletResponse resp, String manifest)
			throws ServletException {
		resp.setHeader("Cache-Control", "no-cache");
		resp.setHeader("Pragma", "no-cache");
		resp.setDateHeader("Expires", new Date().getTime());

		resp.setContentType("text/cache-manifest");

		try {
			InputStream is = new ByteArrayInputStream(manifest.getBytes("UTF-8"));
			ServletOutputStream os = resp.getOutputStream();
			byte[] buffer = new byte[1024];
			int bytesRead;

			while ((bytesRead = is.read(buffer)) != -1) {
				os.write(buffer, 0, bytesRead);
			}
			return;
		} catch (UnsupportedEncodingException e) {
			log("can not write manifest to output stream", e);
			throw new ServletException("can not write manifest to output stream", e);
		} catch (IOException e) {
			log("can not write manifest to output stream", e);
			throw new ServletException("can not write manifest to output stream", e);
		}

	}
	
	private String determinePermutationStrongName(Set computedBindings) throws ServletException {
		if (permutationMap == null) {
			throw new IllegalStateException("permutationMap can not be null");
		}
		
		if (computedBindings == null) {
			throw new IllegalArgumentException("computedBindings can not be null");
		}
		
		for (Entry> entry : permutationMap.entrySet()) {
			List value = entry.getValue();
			if (value.containsAll(computedBindings)) {
				log("Matching to permutation with strong name " + entry.getKey());
				return entry.getKey();
			}
		}
		return null;
	}
		
	private boolean containsPermutationProperty(String name) {
		for (Entry> entry : permutationMap.entrySet()) {
			List value = entry.getValue();
			for (BindingProperty property : value) {
				if (property.getName().equals(name)) {
					return true;
				}
			}
		}
		return false;		
	}

	private void initPermutationMap(String baseUrl, String moduleName) throws ServletException {

		if (permutationMap != null) {
			return;
		}

		if (moduleName == null) {
			throw new IllegalArgumentException("moduleName can not be null");
		}

		String realPath = getServletContext().getRealPath(
				baseUrl + moduleName + "/" + PermutationMapLinker.MANIFEST_MAP_FILE_NAME);

		try {
			FileInputStream fileInputStream = new FileInputStream(realPath);
			permutationMap = permutationProvider.getBindingProperties(fileInputStream);
		} catch (FileNotFoundException e) {
			log("can not find file: '" + realPath + "'", e);
			throw new ServletException("can not find permutation file", e);
		} catch (XMLPermutationProviderException e) {
			log("can not read xml file", e);
			throw new ServletException("can not read permutation information", e);
		}

	}

	// utility method to remove a specific binding property from a set of bindings
	private Set filterProperty(String name, Set computedBindings) {
		Set result = new HashSet();
		for (BindingProperty property : computedBindings) {
			if (!(property.getName().equals(name))) {
				result.add(property);
			}
		}
		return result;
	}

	public String getModuleName(HttpServletRequest req) throws ServletException {
		if (req == null) {
			throw new IllegalArgumentException("request can not be null");
		}

		// request url should be something like .../modulename.manifest" within
		// the same folder of your host page...
		Pattern pattern = Pattern.compile("/([a-zA-Z0-9]+)\\.manifest$");
		Matcher matcher = pattern.matcher(req.getServletPath());
		if (!matcher.find()) {
			log("can not calculate module base from url: '" + req.getServletPath() + "'");
			throw new ServletException("can not calculate module base from url: '" + req.getServletPath() + "'");
		}

		String module = matcher.group(1);
		return module;

	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy