/* * Copyright 2011 gitblit.com. * * 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 com.gitblit; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.Map; import java.util.Properties; import com.gitblit.utils.FileUtils; /** * Dynamically loads and reloads a properties file by keeping track of the last * modification date. * * @author James Moger * */ public class FileSettings extends IStoredSettings { protected File propertiesFile; private final Properties properties = new Properties(); private volatile long lastModified; private volatile boolean forceReload; public FileSettings() { super(FileSettings.class); } public FileSettings(String file) { this(); load(file); } public void load(String file) { this.propertiesFile = new File(file); } /** * Merges the provided settings into this instance. This will also * set the target file for this instance IFF it is unset AND the merge * source is also a FileSettings. This is a little sneaky. */ @Override public void merge(IStoredSettings settings) { super.merge(settings); // sneaky: set the target file from the merge source if (propertiesFile == null && settings instanceof FileSettings) { this.propertiesFile = ((FileSettings) settings).propertiesFile; } } /** * Returns a properties object which contains the most recent contents of * the properties file. */ @Override protected synchronized Properties read() { if (propertiesFile != null && propertiesFile.exists() && (forceReload || (propertiesFile.lastModified() > lastModified))) { FileInputStream is = null; try { Properties props = new Properties(); is = new FileInputStream(propertiesFile); props.load(is); // load properties after we have successfully read file properties.clear(); properties.putAll(props); lastModified = propertiesFile.lastModified(); forceReload = false; } catch (FileNotFoundException f) { // IGNORE - won't happen because file.exists() check above } catch (Throwable t) { logger.error("Failed to read " + propertiesFile.getName(), t); } finally { if (is != null) { try { is.close(); } catch (Throwable t) { // IGNORE } } } } return properties; } @Override public boolean saveSettings() { String content = FileUtils.readContent(propertiesFile, "\n"); for (String key : removals) { String regex = "(?m)^(" + regExEscape(key) + "\\s*+=\\s*+)" + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$"; content = content.replaceAll(regex, ""); } removals.clear(); FileUtils.writeContent(propertiesFile, content); // manually set the forceReload flag because not all JVMs support real // millisecond resolution of lastModified. (issue-55) forceReload = true; return true; } /** * Updates the specified settings in the settings file. */ @Override public synchronized boolean saveSettings(Map settings) { String content = FileUtils.readContent(propertiesFile, "\n"); for (Map.Entry setting:settings.entrySet()) { String regex = "(?m)^(" + regExEscape(setting.getKey()) + "\\s*+=\\s*+)" + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$"; String oldContent = content; content = content.replaceAll(regex, setting.getKey() + " = " + setting.getValue()); if (content.equals(oldContent)) { // did not replace value because it does not exist in the file // append new setting to content (issue-85) content += "\n" + setting.getKey() + " = " + setting.getValue(); } } FileUtils.writeContent(propertiesFile, content); // manually set the forceReload flag because not all JVMs support real // millisecond resolution of lastModified. (issue-55) forceReload = true; return true; } private String regExEscape(String input) { return input.replace(".", "\\.").replace("$", "\\$").replace("{", "\\{"); } /** * @return the last modification date of the properties file */ protected long lastModified() { return lastModified; } /** * @return the state of the force reload flag */ protected boolean forceReload() { return forceReload; } @Override public String toString() { return propertiesFile.getAbsolutePath(); } } '#n35'>35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
/*
 * Copyright 2012 gitblit.com.
 *
 * 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 com.gitblit.wicket;

import java.text.MessageFormat;

import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.form.StatelessForm;
import org.apache.wicket.protocol.http.RequestUtils;
import org.apache.wicket.protocol.http.WicketURLDecoder;
import org.apache.wicket.protocol.http.request.WebRequestCodingStrategy;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitblit.wicket.pages.BasePage;

/**
 * This class is used to create a stateless form that can POST or GET to a
 * bookmarkable page regardless of the pagemap and even after session expiration
 * or a server restart.
 *
 * The trick is to embed "wicket:bookmarkablePage" as a hidden field of the form.
 * Wicket already has logic to extract this parameter when it is trying
 * to determine which page should receive the request.
 *
 * The parameters of the containing page can optionally be included as hidden
 * fields in this form.  Note that if a page parameter's name collides with any
 * child's wicket:id in this form then the page parameter is excluded.
 *
 * @author James Moger
 *
 */
public class SessionlessForm<T> extends StatelessForm<T> {

	private static final long serialVersionUID = 1L;

	private static final String HIDDEN_DIV_START = "<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\">";

	protected final Class<? extends BasePage> pageClass;

	protected final PageParameters pageParameters;

	private final Logger log = LoggerFactory.getLogger(SessionlessForm.class);

	/**
	 * Sessionless forms must have a bookmarkable page class.  A bookmarkable
	 * page is defined as a page that has only a default and/or a PageParameter
	 * constructor.
	 *
	 * @param id
	 * @param bookmarkablePageClass
	 */
	public SessionlessForm(String id, Class<? extends BasePage> bookmarkablePageClass) {
		this(id, bookmarkablePageClass, null);
	}

	/**
	 * Sessionless forms must have a bookmarkable page class.  A bookmarkable
	 * page is defined as a page that has only a default and/or a PageParameter
	 * constructor.
	 *
	 * @param id
	 * @param bookmarkablePageClass
	 * @param pageParameters
	 */
	public SessionlessForm(String id, Class<? extends BasePage> bookmarkablePageClass,
			PageParameters pageParameters) {
		super(id);
		this.pageClass = bookmarkablePageClass;
		this.pageParameters = pageParameters;
	}


	/**
	 * Append an additional hidden input tag that forces Wicket to correctly
	 * determine the destination page class even after a session expiration or
	 * a server restart.
	 *
	 * @param markupStream
	 *            The markup stream
	 * @param openTag
	 *            The open tag for the body
	 */
	@Override
	protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
	{
		// render the hidden bookmarkable page field
		AppendingStringBuffer buffer = new AppendingStringBuffer(HIDDEN_DIV_START);
		buffer.append("<input type=\"hidden\" name=\"")
			.append(WebRequestCodingStrategy.BOOKMARKABLE_PAGE_PARAMETER_NAME)
			.append("\" value=\":")
			.append(pageClass.getName())
			.append("\" />");

		// insert the page parameters, if any, as hidden fields as long as they
		// do not collide with any child wicket:id of the form.
		if (pageParameters != null) {
			for (String key : pageParameters.keySet()) {
				Component c = get(key);
				if (c != null) {
					// this form has a field id which matches the
					// parameter name, skip embedding a hidden value
					log.warn(MessageFormat.format("Skipping page parameter \"{0}\" from sessionless form hidden fields because it collides with a form child wicket:id", key));
					continue;
				}
				String value = pageParameters.getString(key);
				buffer.append("<input type=\"hidden\" name=\"")
				.append(recode(key))
				.append("\" value=\"")
				.append(recode(value))
				.append("\" />");
			}
		}

		buffer.append("</div>");
		getResponse().write(buffer);
		super.onComponentTagBody(markupStream, openTag);
	}

	/**
	 * Take URL-encoded query string value, unencode it and return HTML-escaped version
	 *
	 * @param s
	 *            value to reencode
	 * @return reencoded value
	 */
	private String recode(String s) {
		String un = WicketURLDecoder.QUERY_INSTANCE.decode(s);
		return Strings.escapeMarkup(un).toString();
	}

	protected String getAbsoluteUrl() {
		return getAbsoluteUrl(pageClass, pageParameters);
	}

	protected String getAbsoluteUrl(Class<? extends BasePage> pageClass, PageParameters pageParameters) {
		String relativeUrl = urlFor(pageClass, pageParameters).toString();
		String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
		return absoluteUrl;
	}
}