How to write an early lease termination letter 86

Posted by ryan
at 11:52 AM on Monday, January 30, 2006



I have been living in some apartments in Richmond for almost a year and half now, and the whole time I have been plagued with noisy neighbors due to both the inconsideration of said neighbors and the poor quality of the construction of the building. Rather than suffer through more sleepless nights and increasing blood pressure I finally decided to move – with one problem: my lease wasn’t up yet. Meaning I would need to break my lease early.

For those who know anything about apartment living you know that breaking a lease can be a difficult thing to pull off. Your lease will no doubt state harsh penalties for breaking the lease early (from a few month’s rent to the remaining rent owed as part of your lease). While you can try and argue with your landlord, the fact of the matter is that they’ve got you by the balls.

My (successful) approach was to write a very official and business-like letter to my property manager explaining in a stern but non-threatening way my reasons for wanting out. As I couldn’t find one of these document templates on the web when I was writing mine I thought it might be useful to some of you out there to see what worked for me. (names have been excluded and some adjustments may be needed – MS Word and OpenOffice versions attached after the letter below)




Dear [property manager name],

I am writing you to inform you that we wish to terminate our lease early due to the ongoing dissatisfaction with the quality of life provided to us by the [apartment name] apartments. While we have always been pleased with the cooperation and understanding of you and the apartment management to our situation, the fact remains that the environment in our apartment is substandard, specifically with regard to the noises generated by neighboring units. Our complaints, and those of other tenants, of this prohibitive conduct as specified in section 20 of the Apartment Lease Contract have not changed the situation nor have they provided the “peace and quiet enjoyment” the [apartment name] is committed to providing (Section 11, Rules and Regulations).

It is our desire to be released from our lease without fault on [desired early lease termination date]. You can expect our full cooperation in showing the apartment during our remaining occupation to facilitate its immediate rental and prevent the [apartment name] from any financial losses. I hope that our immediate rental payment history and good-standing account sufficiently indicates our continued accountability and sincerity.

Again, we appreciate all the attempts that you have done as property manager to alleviate our dissatisfaction but have decided that it is in both parties’ best interests for us to vacate the unit. Please let us know the feasibility of our request and if we can do anything to make the process proceed smoothly and to your satisfaction. Also, please let us know if you would like us to speak directly with any other management parties about our request.

Thank you, again, for the consideration.

Sincerely,




MS Word version
OpenOffice 2.x version
OpenOffice 1.x version

If your landlord doesn’t buy your attempt at the matter, I would think you have a few more options, all of escalating implications:

  1. Get a real estate lawyer to write a letter on your behalf (the letterhead alone may be worth more than the contents, so if you have an attorney friend it may be worth hitting them up for the favor)
  2. Contact the Better Business Bureau to try and resolve the situation. You may want to notify your property manager that this is a next step at some point – my guess is they won’t want this hassle and may be willing to cooperate.
  3. Slander the F* out of them on the internet. Do a search for apartment reviews and let others know your experiences. If you can’t get out of your situation, at least make sure others don’t suffer.

Pretty URLs (Rails style) with WebWork

Posted by ryan
at 10:39 AM on Friday, January 13, 2006



I recently had to develop a way to provide for non-parameterized URLs in a WebWork (2.1.x) application. Non-parameterized URLs are those that don’t have the endless list of ?key1=value1&key2=value2 parameters attached to the URL and instead have the nice tidy Rails style URL like ../key1/value1/key2/value2.

Besides being much prettier, this style of URL also helps with search engines as some don’t like to index pages with too many parameters specified (or with an “&id” parameter). When the parameters appear as part of the URL, everybody is happy.

WebWork v2.1.x doesn’t exactly have a nice plugin point to introduce this behavior, so the actual dispatcher servlet needs to be modified to parse URLs of that format. Just so happens there is a very undocumented version of the dispatcher servlet called com.opensymphony.webwork.dispatcher.CoolUriServletDispatcher that attempts to provide just such behavior (thanks, Jim). However, it puked up some String.substring() index out of bounds exceptions when I gave it a spin. I have a feeling its parsing of the URL was very dependent on the request mapping configuration in the web.xml (which is a hairy task). So, what else was I to do but create my own?

My version is pretty similar to the CoolUriServletDispatcher that’s included with WebWork except that it has some enhancements:

  1. doesn’t throw a String.subtring index out of bounds exception on my deployment ;)
  2. can handle conventional actionName.action (or any other extension) url formats
  3. assumes a format of actionName/value to set the id property on the action and NOT to set the id property on the actionName property of the action
  4. can simultaneously handle both pretty url parameters (/key1/value1…) and conventional parameters (/key1/value1?key2=value2)
Here are some quick usage guidelines:
  • Conventional requests will be handled as always:
    http://HOST/CONTEXT/ACTION_NAME.action?id=1&key1=value1
    will be handled by the ACTION_NAME action with the given parameters
  • Pretty urls in this format (when using both conventional and pretty url formats):
    http://HOST/CONTEXT/link/ACTION_NAME/id/1/key1/value1
    will be handled by the ACTION_NAME action with parameters (id=1, key1=value1)
  • Pretty urls in this format (when using both conventional and pretty url formats):
    http://HOST/CONTEXT/link/ACTION_NAME/id/1?key1=value1
    will be handled by the ACTION_NAME action with parameters (id=1, key1=value1)
  • Pretty urls in this format (when using both conventional and pretty url formats):
    http://HOST/CONTEXT/link/ACTION_NAME/1?key1=value1
    will be handled by the ACTION_NAME action with parameters (id=1, key1=value1)
  • When only the pretty url format is used and all requests are mapped to this dispatcher servlet, the /link portion can be removed and you will see the same functionality:
    http://HOST/CONTEXT/ACTION_NAME/id/1/key1/value1
    will be handled by the ACTION_NAME action with parameters (id=1, key1=value1)
    Same goes for: http://HOST/CONTEXT/ACTION_NAME/1/key1/value1

I’ve attempted to make the javadocs quite complete, so I’ll point you there for further documentation: UriMappingServletDispatcher javadocs.

And here is the actual servlet file (actual source is attached at the end of this entry as well):

UriMappingServletDispatcher.java (for Java 5)
UriMappingServletDispatcher.java (for Java 1.4 and below)

It should be noted that the recently released WebWork v2.2 does have an ActionMapper plugin point and a RestfulActionMapper implementation that does this very thing, so go use that if you can.



Actual source:

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import sun.security.action.GetPropertyAction;

import com.opensymphony.webwork.dispatcher.ServletDispatcher;
public class UriMappingServletDispatcher extends ServletDispatcher {

    private static final long serialVersionUID = 1L;

    private static final String DEFAULT_ENCODING = (String) AccessController
            .doPrivileged(new GetPropertyAction("file.encoding"));

    public static final String URI_IGNORE_CONFIG_KEY = "ignoreURIPortion";

    private String ignoreURIPortion = "";

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
     */
    public void init(ServletConfig servletConfig) throws ServletException {
        super.init(servletConfig);

        // Get if we need to ignore any portion of the URL
        String ignore = servletConfig.getInitParameter(URI_IGNORE_CONFIG_KEY);
        if (ignore != null) {
            ignoreURIPortion = ignore;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException {

        // Determine action name
        String actionName = getActionName(getAdjustedString(request
                .getServletPath()));
        if (actionName != null && !"".equals(actionName)) {
            serviceVanillaRequest(request, response, actionName);
        } else {
            actionName = getUriMappedActionName(request);
            serviceUriMappedRequest(request, response, actionName);
        }
    }

    /**
     * This request has been identified as not being a vanilla request, so
     * handle it as though it's a URI mapped request.
     * 
     * @param request
     * @param response
     * @param actionName
     */
    protected void serviceUriMappedRequest(HttpServletRequest request,
            HttpServletResponse response, String actionName) {

        // Place to store our parsed parameters
        Map parameters = new HashMap();

        // Get the part of the URL from the actionName onwards
        String requestURI = getAdjustedString(request.getRequestURI());
        String paramPortion = requestURI.substring(requestURI
                .indexOf(actionName), requestURI.length());
        StringTokenizer st = new StringTokenizer(paramPortion, "/");

        // Collect the parameters
        List paramsList = new ArrayList(st.countTokens());
        while (st.hasMoreTokens()) {
            try {
                paramsList.add(URLDecoder.decode(st.nextToken(),
                        DEFAULT_ENCODING));
            } catch (UnsupportedEncodingException e) {
                paramsList.add(st.nextToken());
            }
        }

        // Which tokens the params start on. Default is the the second,
        // since the first token is the action
        int tokenStart = 1;

        // If there are an even number of params then we can assume that
        // the first token is the name of the action and the second
        // is the id property to set on that action.
        if (paramsList.size() % 2 == 0) {
            parameters.put("id", paramsList.get(1));
            tokenStart = 2;
        }

        // Parse the rest of the parameters as key/value/key/value...
        for (int i = tokenStart; i < paramsList.size(); i = i + 2) {
            parameters.put(paramsList.get(i), paramsList.get(i + 1));
        }

        // Add in any 'normal' request parameters that are present (which
        // will override the url mapped ones)
        parameters.putAll(request.getParameterMap());

        // Pass of our parameters and action on to the default processing
        // This part is wholly copied from
        // com.opensymphony.webwork.dispatcher.CoolUriServletDispatcher
        try {
            request = wrapRequest(request);
            serviceAction(request, response, ””, actionName,
                    getRequestMap(request), parameters, getSessionMap(request),
                    getApplicationMap());
        } catch (IOException e) {
            String message = “Could not wrap servlet request with MultipartRequestWrapper!”;
            sendError(request, response,
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    new ServletException(message, e));
        }

    }

    /*
     * This request has been identified as being a vanilla request:
     * actionName.action?key=value&key=value…., handle it appropriately
     * 
     * @param request
     * @param response
     * @param actionName
     */
    protected void serviceVanillaRequest(HttpServletRequest request,
            HttpServletResponse response, String actionName) {
        try {
            serviceAction(wrapRequest(request), response,
                    getNameSpace(request), actionName, getRequestMap(request),
                    getParameterMap(request), getSessionMap(request),
                    getApplicationMap());
        } catch (IOException e) {
            String message = “Could not wrap servlet request with MultipartRequestWrapper!”;
            sendError(request, response,
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    new ServletException(message, e));
        }
    }

    /*
     * The action could not be determined when the vanilla action.xxx format was
     * used – revert to other more manual means to determine the action name.
     * 
     * @param request
     * @return
     /
    protected String getUriMappedActionName(HttpServletRequest request) {

        // Safest way seems to be to take the request URI, and strip off the
        // context path from the beginning, then strip off everything after the
        // first slash
        String uri = request.getRequestURI();
        String contextPath = request.getContextPath();
        String servletPath = request.getServletPath();

        // Strip off the context and servlet paths to get at our action string
        String baseUri = uri.substring(contextPath.length(), uri.length());
        baseUri = baseUri.substring(servletPath.length(), baseUri.length());

        // Strip off trailing args past the action name (and not
        // including leftover leading ”/”)
        int trailingSlash = baseUri.indexOf(”/”, 1);
        baseUri = baseUri.substring(1, (trailingSlash > 0 ? trailingSlash
                : baseUri.length()));
        return baseUri;
    }

    /*
     * Get the portion of the request URI that should be analyzed for action
     * names, parameters etc… This is basically the URI minus the text
     * specified in the “ignoreURIPortion” servlet init param. (only takes out
     * the first occurance of the string – override for other functionality)
     * 
     * @param request
     * @return
     */
    protected String getAdjustedString(String adjustable) {
        return adjustable.replace(ignoreURIPortion, ””);
    }
}