protected <T> T uploadOrReplace(Map<String, String> params, byte[] photoData, Class<T> tClass, OAuthRequest request) throws JinxException {
        String boundary = JinxUtils.generateBoundary();
        request.addHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
        for (Map.Entry<String, String> entry : params.entrySet()) {
            String key = entry.getKey();
            if (!key.equals("photo") && !key.equals("filename") && !key.equals("filemimetype")) {
                request.addQuerystringParameter(key, String.valueOf(entry.getValue()));
            }
        }

        this.oAuthService.signRequest(this.accessToken, request);

        // add all parameters to payload
        params.putAll(request.getOauthParameters());
        request.addPayload(buildMultipartBody(params, photoData, boundary));

        org.scribe.model.Response flickrResponse = request.send();

        if (flickrResponse == null || flickrResponse.getBody() == null) {
            throw new JinxException("Null return from call to Flickr.");
        }
        if (verboseLogging) {
            JinxLogger.getLogger().log("RESPONSE is " + flickrResponse.getBody());
        }

        // upload returns XML, so convert to json
        String json = JinxUtils.xml2json(flickrResponse.getBody());

        T fromJson = gson.fromJson(json, tClass);

        if (this.flickrErrorThrowsException && ((Response) fromJson).getCode() != 0) {
            Response r = (Response) fromJson;
            throw new JinxException("Flickr returned non-zero status.", null, r);
        }
        return fromJson;
    }