Commit 3fb6dc861087ed3d2ded6c6e14660fc6fd5b773f
1 parent
81beb05d
Add WebPrefs framework, detail: https://github.com/Mumfrey/webprefs
Showing
33 changed files
with
2734 additions
and
0 deletions
src/client/java/com/mumfrey/webprefs/WebPreferences.java
0 → 100644
| 1 | +package com.mumfrey.webprefs; | |
| 2 | + | |
| 3 | +import java.util.HashMap; | |
| 4 | +import java.util.HashSet; | |
| 5 | +import java.util.Map; | |
| 6 | +import java.util.Set; | |
| 7 | +import java.util.concurrent.ConcurrentHashMap; | |
| 8 | +import java.util.regex.Pattern; | |
| 9 | + | |
| 10 | +import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | |
| 11 | +import com.mumfrey.webprefs.exceptions.InvalidKeyException; | |
| 12 | +import com.mumfrey.webprefs.exceptions.InvalidValueException; | |
| 13 | +import com.mumfrey.webprefs.exceptions.ReadOnlyPreferencesException; | |
| 14 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | |
| 15 | +import com.mumfrey.webprefs.interfaces.IWebPreferences; | |
| 16 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesClient; | |
| 17 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesProvider; | |
| 18 | + | |
| 19 | +class WebPreferences implements IWebPreferences | |
| 20 | +{ | |
| 21 | + /** | |
| 22 | + * The update frequency to use when operating normally, this is the | |
| 23 | + * frequency that new requests will be submitted to the remote request queue | |
| 24 | + */ | |
| 25 | + private static final int UPDATE_FREQUENCY_TICKS = 20; // 1 second | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * Number of ticks to wait before a request is assumed to have timed out | |
| 29 | + */ | |
| 30 | + private static final int REQUEST_TIMEOUT_TICKS = 20 * 60; // 1 minute | |
| 31 | + | |
| 32 | + /** | |
| 33 | + * Number of ticks to wait on any communication error (request failed at the | |
| 34 | + * server, request failed to be submitted, request timed out, etc.) | |
| 35 | + */ | |
| 36 | + private static final int UPDATE_ERROR_SUSPEND_TICKS = 20 * 60; // 1 minute | |
| 37 | + | |
| 38 | + /** | |
| 39 | + * Pattern for validating keys | |
| 40 | + */ | |
| 41 | + private static final Pattern keyPattern = Pattern.compile("^[a-z0-9_\\-\\.]{1,32}$"); | |
| 42 | + | |
| 43 | + /** | |
| 44 | + * Preferences client, this is a delegate which is used to communicate with | |
| 45 | + * the preferences provider | |
| 46 | + * | |
| 47 | + * @author Adam Mummery-Smith | |
| 48 | + */ | |
| 49 | + class Client implements IWebPreferencesClient | |
| 50 | + { | |
| 51 | + @Override | |
| 52 | + public void onGetRequestSuccess(String uuid, Map<String, String> values) | |
| 53 | + { | |
| 54 | + if (!WebPreferences.this.uuid.equals(uuid)) | |
| 55 | + { | |
| 56 | + throw new RuntimeException("Received unsolicited response"); | |
| 57 | + } | |
| 58 | + | |
| 59 | + WebPreferences.this.onGetRequestSuccess(values); | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Override | |
| 63 | + public void onSetRequestSuccess(String uuid, Set<String> keys) | |
| 64 | + { | |
| 65 | + if (!WebPreferences.this.uuid.equals(uuid)) | |
| 66 | + { | |
| 67 | + throw new RuntimeException("Received unsolicited response"); | |
| 68 | + } | |
| 69 | + | |
| 70 | + WebPreferences.this.onSetRequestSuccess(keys); | |
| 71 | + } | |
| 72 | + | |
| 73 | + @Override | |
| 74 | + public void onGetRequestFailed(String uuid, Set<String> keys, RequestFailureReason reason) | |
| 75 | + { | |
| 76 | + if (!WebPreferences.this.uuid.equals(uuid)) | |
| 77 | + { | |
| 78 | + throw new RuntimeException("Received unsolicited response"); | |
| 79 | + } | |
| 80 | + | |
| 81 | + WebPreferences.this.onGetRequestFailed(keys, reason); | |
| 82 | + } | |
| 83 | + | |
| 84 | + @Override | |
| 85 | + public void onSetRequestFailed(String uuid, Set<String> keys, RequestFailureReason reason) | |
| 86 | + { | |
| 87 | + if (!WebPreferences.this.uuid.equals(uuid)) | |
| 88 | + { | |
| 89 | + throw new RuntimeException("Received unsolicited response"); | |
| 90 | + } | |
| 91 | + | |
| 92 | + WebPreferences.this.onSetRequestFailed(keys, reason); | |
| 93 | + } | |
| 94 | + } | |
| 95 | + | |
| 96 | + /** | |
| 97 | + * Preferences provider | |
| 98 | + */ | |
| 99 | + private final IWebPreferencesProvider provider; | |
| 100 | + | |
| 101 | + /** | |
| 102 | + * Preferences delegate | |
| 103 | + */ | |
| 104 | + private final IWebPreferencesClient client; | |
| 105 | + | |
| 106 | + /** | |
| 107 | + * Our UUID | |
| 108 | + */ | |
| 109 | + protected final String uuid; | |
| 110 | + | |
| 111 | + /** | |
| 112 | + * True if we are a private settings set | |
| 113 | + */ | |
| 114 | + protected final boolean isPrivate; | |
| 115 | + | |
| 116 | + protected final boolean isReadOnly; | |
| 117 | + | |
| 118 | + /** | |
| 119 | + * Current key/value pairs | |
| 120 | + */ | |
| 121 | + protected final Map<String, String> prefs = new ConcurrentHashMap<String, String>(); | |
| 122 | + | |
| 123 | + /** | |
| 124 | + * Keys which have been requested by a consumer but not requested from the | |
| 125 | + * server yet | |
| 126 | + */ | |
| 127 | + protected final Set<String> requestedPrefs = new HashSet<String>(); | |
| 128 | + | |
| 129 | + /** | |
| 130 | + * Keys which have been requested from the server but not received yet | |
| 131 | + */ | |
| 132 | + protected final Set<String> pendingPrefs = new HashSet<String>(); | |
| 133 | + | |
| 134 | + /** | |
| 135 | + * Keys which have been set by a consumer but not sent to the server yet | |
| 136 | + */ | |
| 137 | + protected final Set<String> dirtyPrefs = new HashSet<String>(); | |
| 138 | + | |
| 139 | + /** | |
| 140 | + * Concurrency lock | |
| 141 | + */ | |
| 142 | + protected final Object lock = new Object(); | |
| 143 | + | |
| 144 | + /** | |
| 145 | + * True when any kind of | |
| 146 | + */ | |
| 147 | + protected volatile boolean dirty = false; | |
| 148 | + | |
| 149 | + private volatile int updateCheckTimer = 1; | |
| 150 | + | |
| 151 | + protected int requestTimeoutTimer = 0; | |
| 152 | + | |
| 153 | + WebPreferences(IWebPreferencesProvider provider, String uuid, boolean isPrivate, boolean isReadOnly) | |
| 154 | + { | |
| 155 | + this.provider = provider; | |
| 156 | + this.uuid = uuid; | |
| 157 | + this.isPrivate = isPrivate; | |
| 158 | + this.isReadOnly = isReadOnly; | |
| 159 | + this.client = new Client(); | |
| 160 | + } | |
| 161 | + | |
| 162 | + /* (non-Javadoc) | |
| 163 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#getUUID() | |
| 164 | + */ | |
| 165 | + @Override | |
| 166 | + public String getUUID() | |
| 167 | + { | |
| 168 | + return this.uuid; | |
| 169 | + } | |
| 170 | + | |
| 171 | + /* (non-Javadoc) | |
| 172 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#isPrivate() | |
| 173 | + */ | |
| 174 | + @Override | |
| 175 | + public boolean isPrivate() | |
| 176 | + { | |
| 177 | + return this.isPrivate; | |
| 178 | + } | |
| 179 | + | |
| 180 | + /* (non-Javadoc) | |
| 181 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#isReadOnly() | |
| 182 | + */ | |
| 183 | + @Override | |
| 184 | + public boolean isReadOnly() | |
| 185 | + { | |
| 186 | + return this.isReadOnly; | |
| 187 | + } | |
| 188 | + | |
| 189 | + void onTick() | |
| 190 | + { | |
| 191 | + if (this.updateCheckTimer > 0) | |
| 192 | + { | |
| 193 | + this.updateCheckTimer--; | |
| 194 | + if (this.updateCheckTimer < 1) | |
| 195 | + { | |
| 196 | + this.update(); | |
| 197 | + } | |
| 198 | + } | |
| 199 | + | |
| 200 | + if (this.requestTimeoutTimer > 0) | |
| 201 | + { | |
| 202 | + this.requestTimeoutTimer--; | |
| 203 | + if (this.requestTimeoutTimer < 1) | |
| 204 | + { | |
| 205 | + this.handleTimeout(); | |
| 206 | + } | |
| 207 | + } | |
| 208 | + } | |
| 209 | + | |
| 210 | + /** | |
| 211 | + * Handle server requests on a periodic basis | |
| 212 | + */ | |
| 213 | + private void update() | |
| 214 | + { | |
| 215 | + this.updateCheckTimer = WebPreferences.UPDATE_FREQUENCY_TICKS; | |
| 216 | + | |
| 217 | + if (!this.dirty || !this.provider.isActive()) | |
| 218 | + { | |
| 219 | + return; | |
| 220 | + } | |
| 221 | + | |
| 222 | + synchronized (this.lock) | |
| 223 | + { | |
| 224 | + this.dirty = false; | |
| 225 | + | |
| 226 | + if (this.requestedPrefs.size() > 0) | |
| 227 | + { | |
| 228 | + LiteLoaderLogger.debug("Preferences for " + this.uuid + " is submitting a request for " | |
| 229 | + + this.requestedPrefs.size() + " requested preferences"); | |
| 230 | + if (this.provider.requestGet(this.client, this.uuid, new HashSet<String>(this.requestedPrefs), this.isPrivate)) | |
| 231 | + { | |
| 232 | + this.requestTimeoutTimer = WebPreferences.REQUEST_TIMEOUT_TICKS; | |
| 233 | + this.pendingPrefs.addAll(this.requestedPrefs); | |
| 234 | + this.requestedPrefs.clear(); | |
| 235 | + } | |
| 236 | + else | |
| 237 | + { | |
| 238 | + this.dirty = true; | |
| 239 | + } | |
| 240 | + } | |
| 241 | + | |
| 242 | + } | |
| 243 | + | |
| 244 | + this.commit(false); | |
| 245 | + } | |
| 246 | + | |
| 247 | + /** | |
| 248 | + * Called when a pending request is deemed to have timed out | |
| 249 | + */ | |
| 250 | + private void handleTimeout() | |
| 251 | + { | |
| 252 | + this.updateCheckTimer = WebPreferences.UPDATE_ERROR_SUSPEND_TICKS; | |
| 253 | + | |
| 254 | + synchronized (this.lock) | |
| 255 | + { | |
| 256 | + this.requestedPrefs.addAll(this.pendingPrefs); | |
| 257 | + this.pendingPrefs.clear(); | |
| 258 | + this.dirty = true; | |
| 259 | + } | |
| 260 | + } | |
| 261 | + | |
| 262 | + /* (non-Javadoc) | |
| 263 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | |
| 264 | + * #request(java.lang.String) | |
| 265 | + */ | |
| 266 | + @Override | |
| 267 | + public void request(String key) | |
| 268 | + { | |
| 269 | + WebPreferences.validateKey(key); | |
| 270 | + | |
| 271 | + synchronized (this.lock) | |
| 272 | + { | |
| 273 | + this.dirty |= this.addRequestedKey(key); | |
| 274 | + } | |
| 275 | + } | |
| 276 | + | |
| 277 | + /* (non-Javadoc) | |
| 278 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | |
| 279 | + * #request(java.lang.String[]) | |
| 280 | + */ | |
| 281 | + @Override | |
| 282 | + public void request(String... keys) | |
| 283 | + { | |
| 284 | + if (keys.length < 1) return; | |
| 285 | + if (keys.length == 1) this.request(keys[0]); | |
| 286 | + | |
| 287 | + synchronized (this.lock) | |
| 288 | + { | |
| 289 | + boolean dirty = false; | |
| 290 | + | |
| 291 | + for (String key : keys) | |
| 292 | + { | |
| 293 | + WebPreferences.validateKey(key); | |
| 294 | + dirty |= this.addRequestedKey(key); | |
| 295 | + } | |
| 296 | + | |
| 297 | + this.dirty |= dirty; | |
| 298 | + } | |
| 299 | + } | |
| 300 | + | |
| 301 | + /* (non-Javadoc) | |
| 302 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | |
| 303 | + * #request(java.util.Set) | |
| 304 | + */ | |
| 305 | + @Override | |
| 306 | + public void request(Set<String> keys) | |
| 307 | + { | |
| 308 | + if (keys == null || keys.size() < 1) return; | |
| 309 | + | |
| 310 | + synchronized (this.lock) | |
| 311 | + { | |
| 312 | + boolean dirty = false; | |
| 313 | + | |
| 314 | + for (String key : keys) | |
| 315 | + { | |
| 316 | + WebPreferences.validateKey(key); | |
| 317 | + dirty |= this.addRequestedKey(key); | |
| 318 | + } | |
| 319 | + | |
| 320 | + this.dirty |= dirty; | |
| 321 | + } | |
| 322 | + } | |
| 323 | + | |
| 324 | + /* (non-Javadoc) | |
| 325 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#poll() | |
| 326 | + */ | |
| 327 | + @Override | |
| 328 | + public void poll() | |
| 329 | + { | |
| 330 | + synchronized (this.lock) | |
| 331 | + { | |
| 332 | + this.requestedPrefs.addAll(this.prefs.keySet()); | |
| 333 | + this.requestedPrefs.removeAll(this.pendingPrefs); | |
| 334 | + this.dirty = true; | |
| 335 | + } | |
| 336 | + } | |
| 337 | + | |
| 338 | + /* (non-Javadoc) | |
| 339 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences#commit(boolean) | |
| 340 | + */ | |
| 341 | + @Override | |
| 342 | + public void commit(boolean force) | |
| 343 | + { | |
| 344 | + synchronized (this.lock) | |
| 345 | + { | |
| 346 | + // Permanent error condition | |
| 347 | + if (this.updateCheckTimer < 0) | |
| 348 | + { | |
| 349 | + return; | |
| 350 | + } | |
| 351 | + | |
| 352 | + if (force) | |
| 353 | + { | |
| 354 | + this.dirtyPrefs.addAll(this.prefs.keySet()); | |
| 355 | + } | |
| 356 | + | |
| 357 | + if (this.dirtyPrefs.size() > 0) | |
| 358 | + { | |
| 359 | + Map<String, String> outgoingPrefs = new HashMap<String, String>(); | |
| 360 | + for (String key : this.dirtyPrefs) | |
| 361 | + { | |
| 362 | + outgoingPrefs.put(key, this.prefs.get(key)); | |
| 363 | + } | |
| 364 | + | |
| 365 | + LiteLoaderLogger.debug("Preferences for " + this.uuid + " is submitting a SET for " + outgoingPrefs.size() + " dirty preferences"); | |
| 366 | + if (this.provider.requestSet(this.client, this.uuid, outgoingPrefs, this.isPrivate)) | |
| 367 | + { | |
| 368 | + this.dirtyPrefs.clear(); | |
| 369 | + } | |
| 370 | + else | |
| 371 | + { | |
| 372 | + this.dirty = true; | |
| 373 | + } | |
| 374 | + } | |
| 375 | + } | |
| 376 | + } | |
| 377 | + | |
| 378 | + /* (non-Javadoc) | |
| 379 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | |
| 380 | + * #has(java.lang.String) | |
| 381 | + */ | |
| 382 | + @Override | |
| 383 | + public boolean has(String key) | |
| 384 | + { | |
| 385 | + WebPreferences.validateKey(key); | |
| 386 | + | |
| 387 | + return this.get(key) != null; | |
| 388 | + } | |
| 389 | + | |
| 390 | + /* (non-Javadoc) | |
| 391 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | |
| 392 | + * #get(java.lang.String) | |
| 393 | + */ | |
| 394 | + @Override | |
| 395 | + public String get(String key) | |
| 396 | + { | |
| 397 | + WebPreferences.validateKey(key); | |
| 398 | + | |
| 399 | + // .get() can be outside of the synchronisation lock because we are using ConcurrentHashSet | |
| 400 | + String value = this.prefs.get(key); | |
| 401 | + | |
| 402 | + if (value == null) | |
| 403 | + { | |
| 404 | + synchronized (this.lock) | |
| 405 | + { | |
| 406 | + this.dirty |= this.addRequestedKey(key); | |
| 407 | + } | |
| 408 | + } | |
| 409 | + | |
| 410 | + return value; | |
| 411 | + } | |
| 412 | + | |
| 413 | + /* (non-Javadoc) | |
| 414 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | |
| 415 | + * #get(java.lang.String, java.lang.String) | |
| 416 | + */ | |
| 417 | + @Override | |
| 418 | + public String get(String key, String defaultValue) | |
| 419 | + { | |
| 420 | + WebPreferences.validateKey(key); | |
| 421 | + | |
| 422 | + String value = this.get(key); | |
| 423 | + return value != null ? value : defaultValue; | |
| 424 | + } | |
| 425 | + | |
| 426 | + /* (non-Javadoc) | |
| 427 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | |
| 428 | + * #set(java.lang.String, java.lang.String) | |
| 429 | + */ | |
| 430 | + @Override | |
| 431 | + public void set(String key, String value) | |
| 432 | + { | |
| 433 | + if (this.isReadOnly()) | |
| 434 | + { | |
| 435 | + throw new ReadOnlyPreferencesException("Preference collection for " + this.uuid + " is read-only"); | |
| 436 | + } | |
| 437 | + | |
| 438 | + WebPreferences.validateKV(key, value); | |
| 439 | + | |
| 440 | + synchronized (this.lock) | |
| 441 | + { | |
| 442 | + String oldValue = this.prefs.get(key); | |
| 443 | + if (value.equals(oldValue)) return; | |
| 444 | + | |
| 445 | + this.prefs.put(key, value); | |
| 446 | + this.dirtyPrefs.add(key); | |
| 447 | + this.requestedPrefs.remove(key); | |
| 448 | + this.dirty = true; | |
| 449 | + } | |
| 450 | + } | |
| 451 | + | |
| 452 | + /* (non-Javadoc) | |
| 453 | + * @see com.mumfrey.webprefs.interfaces.IWebPreferences | |
| 454 | + * #remove(java.lang.String) | |
| 455 | + */ | |
| 456 | + @Override | |
| 457 | + public void remove(String key) | |
| 458 | + { | |
| 459 | + this.set(key, ""); | |
| 460 | + } | |
| 461 | + | |
| 462 | + /** | |
| 463 | + * Add a key to the current request set, the key will be requested from the | |
| 464 | + * server on the next {@link #update()} | |
| 465 | + * | |
| 466 | + * @param key | |
| 467 | + * @return | |
| 468 | + */ | |
| 469 | + private boolean addRequestedKey(String key) | |
| 470 | + { | |
| 471 | + if (key != null && !this.pendingPrefs.contains(key)) | |
| 472 | + { | |
| 473 | + this.requestedPrefs.add(key); | |
| 474 | + return true; | |
| 475 | + } | |
| 476 | + | |
| 477 | + return false; | |
| 478 | + } | |
| 479 | + | |
| 480 | + /** | |
| 481 | + * Callback from the preferences provider | |
| 482 | + */ | |
| 483 | + void onGetRequestSuccess(Map<String, String> values) | |
| 484 | + { | |
| 485 | + this.requestTimeoutTimer = 0; | |
| 486 | + | |
| 487 | + synchronized (this.lock) | |
| 488 | + { | |
| 489 | + this.prefs.putAll(values); | |
| 490 | + | |
| 491 | + Set<String> keys = values.keySet(); | |
| 492 | + this.dirtyPrefs.removeAll(keys); | |
| 493 | + this.pendingPrefs.removeAll(keys); | |
| 494 | + this.requestedPrefs.removeAll(keys); | |
| 495 | + } | |
| 496 | + } | |
| 497 | + | |
| 498 | + /** | |
| 499 | + * Callback from the preferences provider | |
| 500 | + */ | |
| 501 | + void onSetRequestSuccess(Set<String> keys) | |
| 502 | + { | |
| 503 | + this.requestTimeoutTimer = 0; | |
| 504 | + | |
| 505 | + synchronized (this.lock) | |
| 506 | + { | |
| 507 | + this.dirtyPrefs.removeAll(keys); | |
| 508 | + this.requestedPrefs.removeAll(keys); | |
| 509 | + this.dirty = (this.dirtyPrefs.size() > 0 || this.requestedPrefs.size() > 0); | |
| 510 | + } | |
| 511 | + } | |
| 512 | + | |
| 513 | + /** | |
| 514 | + * Callback from the preferences provider | |
| 515 | + * @param reason | |
| 516 | + */ | |
| 517 | + void onGetRequestFailed(Set<String> keys, RequestFailureReason reason) | |
| 518 | + { | |
| 519 | + this.requestTimeoutTimer = 0; | |
| 520 | + this.handleFailedRequest(reason); | |
| 521 | + | |
| 522 | + synchronized (this.lock) | |
| 523 | + { | |
| 524 | + this.dirtyPrefs.addAll(keys); | |
| 525 | + this.pendingPrefs.removeAll(keys); | |
| 526 | + this.dirty = true; | |
| 527 | + } | |
| 528 | + } | |
| 529 | + | |
| 530 | + /** | |
| 531 | + * Callback from the preferences provider | |
| 532 | + * @param reason | |
| 533 | + */ | |
| 534 | + void onSetRequestFailed(Set<String> keys, RequestFailureReason reason) | |
| 535 | + { | |
| 536 | + this.requestTimeoutTimer = 0; | |
| 537 | + this.handleFailedRequest(reason); | |
| 538 | + | |
| 539 | + synchronized (this.lock) | |
| 540 | + { | |
| 541 | + this.requestedPrefs.addAll(keys); | |
| 542 | + this.pendingPrefs.removeAll(keys); | |
| 543 | + this.dirty = true; | |
| 544 | + } | |
| 545 | + } | |
| 546 | + | |
| 547 | + /** | |
| 548 | + * @param reason | |
| 549 | + */ | |
| 550 | + private void handleFailedRequest(RequestFailureReason reason) | |
| 551 | + { | |
| 552 | + if (reason.isPermanent()) | |
| 553 | + { | |
| 554 | + LiteLoaderLogger.debug("Halting update of preferences for " + this.uuid + " permanently because " + reason); | |
| 555 | + this.updateCheckTimer = -1; | |
| 556 | + } | |
| 557 | + | |
| 558 | + int suspendUpdateFor = WebPreferences.UPDATE_ERROR_SUSPEND_TICKS * Math.max(1, reason.getSeverity()); | |
| 559 | + LiteLoaderLogger.debug("Suspending update of preferences for " + this.uuid + " for " + suspendUpdateFor + " because " + reason); | |
| 560 | + this.updateCheckTimer = suspendUpdateFor; | |
| 561 | + } | |
| 562 | + | |
| 563 | + /** | |
| 564 | + * @param key | |
| 565 | + */ | |
| 566 | + protected static final void validateKey(String key) | |
| 567 | + { | |
| 568 | + if (key == null || !WebPreferences.keyPattern.matcher(key).matches()) | |
| 569 | + { | |
| 570 | + throw new InvalidKeyException("The specified key [" + key + "] is not valid"); | |
| 571 | + } | |
| 572 | + } | |
| 573 | + | |
| 574 | + /** | |
| 575 | + * @param key | |
| 576 | + * @param value | |
| 577 | + */ | |
| 578 | + protected static final void validateKV(String key, String value) | |
| 579 | + { | |
| 580 | + WebPreferences.validateKey(key); | |
| 581 | + | |
| 582 | + if (value == null || value.length() > 255) | |
| 583 | + { | |
| 584 | + throw new InvalidValueException("The specified value [" + value + "] for key [" + key + "] is not valid"); | |
| 585 | + } | |
| 586 | + } | |
| 587 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/WebPreferencesManager.java
0 → 100644
| 1 | +package com.mumfrey.webprefs; | |
| 2 | + | |
| 3 | +import java.io.File; | |
| 4 | +import java.net.Proxy; | |
| 5 | +import java.net.URI; | |
| 6 | +import java.net.URISyntaxException; | |
| 7 | +import java.util.HashMap; | |
| 8 | +import java.util.LinkedHashMap; | |
| 9 | +import java.util.LinkedList; | |
| 10 | +import java.util.List; | |
| 11 | +import java.util.Map; | |
| 12 | +import java.util.UUID; | |
| 13 | +import java.util.regex.Matcher; | |
| 14 | +import java.util.regex.Pattern; | |
| 15 | + | |
| 16 | +import com.mojang.authlib.GameProfile; | |
| 17 | +import com.mojang.realmsclient.dto.RealmsServer; | |
| 18 | +import com.mumfrey.liteloader.JoinGameListener; | |
| 19 | +import com.mumfrey.liteloader.Tickable; | |
| 20 | +import com.mumfrey.liteloader.core.LiteLoader; | |
| 21 | +import com.mumfrey.webprefs.exceptions.InvalidServiceException; | |
| 22 | +import com.mumfrey.webprefs.exceptions.InvalidUUIDException; | |
| 23 | +import com.mumfrey.webprefs.framework.WebPreferencesProvider; | |
| 24 | +import com.mumfrey.webprefs.interfaces.IWebPreferences; | |
| 25 | + | |
| 26 | +import net.minecraft.client.Minecraft; | |
| 27 | +import net.minecraft.client.multiplayer.ServerData; | |
| 28 | +import net.minecraft.entity.player.EntityPlayer; | |
| 29 | +import net.minecraft.network.INetHandler; | |
| 30 | +import net.minecraft.network.play.server.SPacketJoinGame; | |
| 31 | +import net.minecraft.util.Session; | |
| 32 | + | |
| 33 | +/** | |
| 34 | + * WebPreferences Service Manager, acts as a central registry for all web | |
| 35 | + * preference collections. | |
| 36 | + * | |
| 37 | + * <p>To access preferences on a service, first request the service using | |
| 38 | + * {@link #get(String)} or {@link #getDefault()}, you can then request | |
| 39 | + * preferences objects from the service by calling the various overloads of | |
| 40 | + * {@link #getPreferences}.</p> | |
| 41 | + * | |
| 42 | + * @author Adam Mummery-Smith | |
| 43 | + */ | |
| 44 | +public final class WebPreferencesManager | |
| 45 | +{ | |
| 46 | + /** | |
| 47 | + * WebPreferences Manager Update Daemon is injected into LiteLoader to | |
| 48 | + * facilitate passing events to the WebPreferences Manager without having to | |
| 49 | + * expose public callback methods. | |
| 50 | + * | |
| 51 | + * @author Adam Mummery-Smith | |
| 52 | + */ | |
| 53 | + static class WebPreferencesUpdateDeamon implements Tickable, JoinGameListener | |
| 54 | + { | |
| 55 | + @Override | |
| 56 | + public String getName() | |
| 57 | + { | |
| 58 | + return "Web Preferences Update Daemon"; | |
| 59 | + } | |
| 60 | + | |
| 61 | + @Override | |
| 62 | + public String getVersion() | |
| 63 | + { | |
| 64 | + return "N/A"; | |
| 65 | + } | |
| 66 | + | |
| 67 | + @Override | |
| 68 | + public void init(File configPath) | |
| 69 | + { | |
| 70 | + } | |
| 71 | + | |
| 72 | + @Override | |
| 73 | + public void upgradeSettings(String version, File configPath, File oldConfigPath) | |
| 74 | + { | |
| 75 | + } | |
| 76 | + | |
| 77 | + @Override | |
| 78 | + public void onTick(Minecraft minecraft, float partialTicks, boolean inGame, boolean clock) | |
| 79 | + { | |
| 80 | + if (clock) | |
| 81 | + { | |
| 82 | + for (WebPreferencesManager manager : WebPreferencesManager.managers.values()) | |
| 83 | + { | |
| 84 | + manager.onTick(); | |
| 85 | + } | |
| 86 | + } | |
| 87 | + } | |
| 88 | + | |
| 89 | + @Override | |
| 90 | + public void onJoinGame(INetHandler netHandler, SPacketJoinGame joinGamePacket, ServerData serverData, RealmsServer realmsServer) | |
| 91 | + { | |
| 92 | + for (WebPreferencesManager manager : WebPreferencesManager.managers.values()) | |
| 93 | + { | |
| 94 | + manager.onJoinGame(); | |
| 95 | + } | |
| 96 | + } | |
| 97 | + } | |
| 98 | + | |
| 99 | + /** | |
| 100 | + * Default KV api hostname to connect to | |
| 101 | + */ | |
| 102 | + private static final String DEFAULT_HOSTNAME = "kv.liteloader.com"; | |
| 103 | + | |
| 104 | + /** | |
| 105 | + * Regex for validating UUIDs | |
| 106 | + */ | |
| 107 | + private static final Pattern uuidPattern = Pattern.compile("^[a-f0-9]{32}$"); | |
| 108 | + | |
| 109 | + /** | |
| 110 | + * Mapping of hostnames to managers | |
| 111 | + */ | |
| 112 | + static final Map<String, WebPreferencesManager> managers = new LinkedHashMap<String, WebPreferencesManager>(); | |
| 113 | + | |
| 114 | + /** | |
| 115 | + * Update daemon | |
| 116 | + */ | |
| 117 | + private static WebPreferencesUpdateDeamon updateDeamon; | |
| 118 | + | |
| 119 | + /** | |
| 120 | + * Session for this instance | |
| 121 | + */ | |
| 122 | + private final Session session; | |
| 123 | + | |
| 124 | + /** | |
| 125 | + * Preference provider, manages queueing requests and passing responses back | |
| 126 | + * to clients | |
| 127 | + */ | |
| 128 | + private final WebPreferencesProvider provider; | |
| 129 | + | |
| 130 | + /** | |
| 131 | + * All preference sets, for iteration purposes | |
| 132 | + */ | |
| 133 | + private final List<WebPreferences> allPreferences = new LinkedList<WebPreferences>(); | |
| 134 | + | |
| 135 | + /** | |
| 136 | + * All public preference sets, mapped by UUID | |
| 137 | + */ | |
| 138 | + private final Map<String, IWebPreferences> preferencesPublic = new HashMap<String, IWebPreferences>(); | |
| 139 | + | |
| 140 | + /** | |
| 141 | + * All private preference sets, mapped by UUID | |
| 142 | + */ | |
| 143 | + private final Map<String, IWebPreferences> preferencesPrivate = new HashMap<String, IWebPreferences>(); | |
| 144 | + | |
| 145 | + private WebPreferencesManager(Proxy proxy, Session session, String hostName) | |
| 146 | + { | |
| 147 | + this.session = session; | |
| 148 | + this.provider = new WebPreferencesProvider(proxy, session, hostName, 50); | |
| 149 | + } | |
| 150 | + | |
| 151 | + void onTick() | |
| 152 | + { | |
| 153 | + this.provider.onTick(); | |
| 154 | + | |
| 155 | + for (WebPreferences prefs : this.allPreferences) | |
| 156 | + { | |
| 157 | + try | |
| 158 | + { | |
| 159 | + prefs.onTick(); | |
| 160 | + } | |
| 161 | + catch (Exception ex) {} | |
| 162 | + } | |
| 163 | + } | |
| 164 | + | |
| 165 | + void onJoinGame() | |
| 166 | + { | |
| 167 | + for (WebPreferences prefs : this.allPreferences) | |
| 168 | + { | |
| 169 | + try | |
| 170 | + { | |
| 171 | + prefs.poll(); | |
| 172 | + } | |
| 173 | + catch (Exception ex) {} | |
| 174 | + } | |
| 175 | + } | |
| 176 | + | |
| 177 | + | |
| 178 | + /** | |
| 179 | + * Get a public or private preferences collection for the local player | |
| 180 | + * | |
| 181 | + * @param privatePrefs true to fetch the player's private preferences, false | |
| 182 | + * to fetch public preferences | |
| 183 | + * @return player's preference collection, creates if necessary | |
| 184 | + */ | |
| 185 | + public IWebPreferences getLocalPreferences(boolean privatePrefs) | |
| 186 | + { | |
| 187 | + return this.getPreferences(this.session.getPlayerID(), privatePrefs); | |
| 188 | + } | |
| 189 | + | |
| 190 | + /** | |
| 191 | + * Get a public preferences collection for the specified player. | |
| 192 | + * | |
| 193 | + * @param player Player to fetch preferences for | |
| 194 | + * @param privatePrefs True to fetch the player's private preferences, false | |
| 195 | + * to fetch the public preferences | |
| 196 | + * @return Preference collection or <tt>null</tt> if the player's profile | |
| 197 | + * cannot be retrieved | |
| 198 | + */ | |
| 199 | + public IWebPreferences getPreferences(EntityPlayer player) | |
| 200 | + { | |
| 201 | + return this.getPreferences(player, false); | |
| 202 | + } | |
| 203 | + | |
| 204 | + /** | |
| 205 | + * Get a public or private preferences collection for the specified player, | |
| 206 | + * note that accessing a private collection for another player is likely | |
| 207 | + * to be prohibited by the service. | |
| 208 | + * | |
| 209 | + * @param player Player to fetch preferences for | |
| 210 | + * @param privatePrefs True to fetch the player's private preferences, false | |
| 211 | + * to fetch the public preferences | |
| 212 | + * @return Preference collection or <tt>null</tt> if the player's profile | |
| 213 | + * cannot be retrieved | |
| 214 | + */ | |
| 215 | + public IWebPreferences getPreferences(EntityPlayer player, boolean privatePrefs) | |
| 216 | + { | |
| 217 | + GameProfile gameProfile = player.getGameProfile(); | |
| 218 | + return gameProfile != null ? this.getPreferences(gameProfile, privatePrefs) : null; | |
| 219 | + } | |
| 220 | + | |
| 221 | + /** | |
| 222 | + * Get a public preferences collection for the specified game profile. | |
| 223 | + * | |
| 224 | + * @param gameProfile game profile to fetch preferences for | |
| 225 | + * @return Preference collection or <tt>null</tt> if the supplied profile is | |
| 226 | + * null | |
| 227 | + */ | |
| 228 | + public IWebPreferences getPreferences(GameProfile gameProfile) | |
| 229 | + { | |
| 230 | + return gameProfile != null ? this.getPreferences(gameProfile, false) : null; | |
| 231 | + } | |
| 232 | + | |
| 233 | + /** | |
| 234 | + * Get a public or private preferences collection for the specified game | |
| 235 | + * profile, note that accessing a private collection for another player is | |
| 236 | + * likely to be prohibited by the service. | |
| 237 | + * | |
| 238 | + * @param gameProfile game profile to fetch preferences for | |
| 239 | + * @param privatePrefs True to fetch the player's private preferences, false | |
| 240 | + * to fetch the public preferences | |
| 241 | + * @return Preference collection or <tt>null</tt> if the supplied profile is | |
| 242 | + * null | |
| 243 | + */ | |
| 244 | + public IWebPreferences getPreferences(GameProfile gameProfile, boolean privatePrefs) | |
| 245 | + { | |
| 246 | + return gameProfile != null ? this.getPreferences(gameProfile.getId(), privatePrefs) : null; | |
| 247 | + } | |
| 248 | + | |
| 249 | + /** | |
| 250 | + * Get a public preferences collection for the specified player UUID. | |
| 251 | + * | |
| 252 | + * @param uuid UUID to fetch preferences for | |
| 253 | + * @return Preference collection or <tt>null</tt> if the supplied UUID is | |
| 254 | + * null | |
| 255 | + */ | |
| 256 | + public IWebPreferences getPreferences(UUID uuid) | |
| 257 | + { | |
| 258 | + return uuid != null ? this.getPreferences(uuid, false) : null; | |
| 259 | + } | |
| 260 | + | |
| 261 | + /** | |
| 262 | + * Get a public or private preferences collection for the specified player | |
| 263 | + * UUID, note that accessing a private collection for another player is | |
| 264 | + * likely to be prohibited by the service. | |
| 265 | + * | |
| 266 | + * @param uuid UUID to fetch preferences for | |
| 267 | + * @param privatePrefs True to fetch the player's private preferences, false | |
| 268 | + * to fetch the public preferences | |
| 269 | + * @return Preference collection or <tt>null</tt> if the supplied UUID is | |
| 270 | + * null | |
| 271 | + */ | |
| 272 | + public IWebPreferences getPreferences(UUID uuid, boolean privatePrefs) | |
| 273 | + { | |
| 274 | + return uuid != null ? this.getPreferences(uuid.toString(), privatePrefs) : null; | |
| 275 | + } | |
| 276 | + | |
| 277 | + public IWebPreferences getPreferences(String uuid, boolean privatePrefs) | |
| 278 | + { | |
| 279 | + uuid = this.sanitiseUUID(uuid); | |
| 280 | + | |
| 281 | + Map<String, IWebPreferences> preferences = privatePrefs ? this.preferencesPrivate : this.preferencesPublic; | |
| 282 | + | |
| 283 | + IWebPreferences prefs = preferences.get(uuid); | |
| 284 | + | |
| 285 | + if (prefs == null) | |
| 286 | + { | |
| 287 | + WebPreferences newPrefs = new WebPreferences(this.provider, uuid, privatePrefs, !uuid.equals(this.session.getPlayerID())); | |
| 288 | + this.allPreferences.add(newPrefs); | |
| 289 | + preferences.put(uuid, newPrefs); | |
| 290 | + prefs = newPrefs; | |
| 291 | + } | |
| 292 | + | |
| 293 | + return prefs; | |
| 294 | + } | |
| 295 | + | |
| 296 | + private String sanitiseUUID(String uuid) | |
| 297 | + { | |
| 298 | + if (uuid == null) | |
| 299 | + { | |
| 300 | + throw new InvalidUUIDException("The UUID was null"); | |
| 301 | + } | |
| 302 | + | |
| 303 | + uuid = uuid.toLowerCase().replace("-", "").trim(); | |
| 304 | + Matcher uuidPatternMatcher = WebPreferencesManager.uuidPattern.matcher(uuid); | |
| 305 | + if (!uuidPatternMatcher.matches()) | |
| 306 | + { | |
| 307 | + throw new InvalidUUIDException("The specified string [" + uuid + "] is not a valid UUID"); | |
| 308 | + } | |
| 309 | + | |
| 310 | + return uuid; | |
| 311 | + } | |
| 312 | + | |
| 313 | + /** | |
| 314 | + * Get the default preferences manager (kv.liteloader.com) | |
| 315 | + * | |
| 316 | + * @return default preferences manager | |
| 317 | + */ | |
| 318 | + public static WebPreferencesManager getDefault() | |
| 319 | + { | |
| 320 | + return WebPreferencesManager.get(WebPreferencesManager.DEFAULT_HOSTNAME); | |
| 321 | + } | |
| 322 | + | |
| 323 | + /** | |
| 324 | + * Get a preferences manager for the specified service hostname | |
| 325 | + * | |
| 326 | + * @param hostName service hostname (bare hostname only, no protocol) | |
| 327 | + * @return preferences manager | |
| 328 | + * @throws InvalidServiceException if the specified host name is invalid | |
| 329 | + */ | |
| 330 | + @SuppressWarnings("unused") | |
| 331 | + public static WebPreferencesManager get(String hostName) throws InvalidServiceException | |
| 332 | + { | |
| 333 | + try | |
| 334 | + { | |
| 335 | + new URI(String.format("http://%s/", hostName)); | |
| 336 | + } | |
| 337 | + catch (URISyntaxException ex) | |
| 338 | + { | |
| 339 | + throw new InvalidServiceException("The specified service host was not valid: " + hostName, ex); | |
| 340 | + } | |
| 341 | + | |
| 342 | + if (WebPreferencesManager.updateDeamon == null) | |
| 343 | + { | |
| 344 | + WebPreferencesManager.updateDeamon = new WebPreferencesUpdateDeamon(); | |
| 345 | + LiteLoader.getInterfaceManager().registerListener(WebPreferencesManager.updateDeamon); | |
| 346 | + } | |
| 347 | + | |
| 348 | + WebPreferencesManager manager = WebPreferencesManager.managers.get(hostName); | |
| 349 | + | |
| 350 | + if (manager == null) | |
| 351 | + { | |
| 352 | + Minecraft minecraft = Minecraft.getMinecraft(); | |
| 353 | + | |
| 354 | + Proxy proxy = minecraft.getProxy(); | |
| 355 | + Session session = minecraft.getSession(); | |
| 356 | + | |
| 357 | + manager = new WebPreferencesManager(proxy, session, hostName); | |
| 358 | + WebPreferencesManager.managers.put(hostName, manager); | |
| 359 | + } | |
| 360 | + | |
| 361 | + return manager; | |
| 362 | + } | |
| 363 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidKeyException.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.exceptions; | |
| 2 | + | |
| 3 | +public class InvalidKeyException extends RuntimeException | |
| 4 | +{ | |
| 5 | + private static final long serialVersionUID = 1L; | |
| 6 | + | |
| 7 | + public InvalidKeyException() | |
| 8 | + { | |
| 9 | + } | |
| 10 | + | |
| 11 | + public InvalidKeyException(String message) | |
| 12 | + { | |
| 13 | + super(message); | |
| 14 | + } | |
| 15 | + | |
| 16 | + public InvalidKeyException(Throwable cause) | |
| 17 | + { | |
| 18 | + super(cause); | |
| 19 | + } | |
| 20 | + | |
| 21 | + public InvalidKeyException(String message, Throwable cause) | |
| 22 | + { | |
| 23 | + super(message, cause); | |
| 24 | + } | |
| 25 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidPreferenceOperationException.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.exceptions; | |
| 2 | + | |
| 3 | +public class InvalidPreferenceOperationException extends RuntimeException | |
| 4 | +{ | |
| 5 | + private static final long serialVersionUID = 1L; | |
| 6 | + | |
| 7 | + public InvalidPreferenceOperationException() | |
| 8 | + { | |
| 9 | + } | |
| 10 | + | |
| 11 | + public InvalidPreferenceOperationException(String message) | |
| 12 | + { | |
| 13 | + super(message); | |
| 14 | + } | |
| 15 | + | |
| 16 | + public InvalidPreferenceOperationException(Throwable cause) | |
| 17 | + { | |
| 18 | + super(cause); | |
| 19 | + } | |
| 20 | + | |
| 21 | + public InvalidPreferenceOperationException(String message, Throwable cause) | |
| 22 | + { | |
| 23 | + super(message, cause); | |
| 24 | + } | |
| 25 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidRequestException.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.exceptions; | |
| 2 | + | |
| 3 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | |
| 4 | + | |
| 5 | +public class InvalidRequestException extends RuntimeException | |
| 6 | +{ | |
| 7 | + private static final long serialVersionUID = 1L; | |
| 8 | + | |
| 9 | + private final RequestFailureReason reason; | |
| 10 | + | |
| 11 | + public InvalidRequestException(RequestFailureReason reason) | |
| 12 | + { | |
| 13 | + this.reason = reason; | |
| 14 | + } | |
| 15 | + | |
| 16 | + public InvalidRequestException(RequestFailureReason reason, String message) | |
| 17 | + { | |
| 18 | + super(message); | |
| 19 | + this.reason = reason; | |
| 20 | + } | |
| 21 | + | |
| 22 | + public InvalidRequestException(RequestFailureReason reason, Throwable cause) | |
| 23 | + { | |
| 24 | + super(cause); | |
| 25 | + this.reason = reason; | |
| 26 | + } | |
| 27 | + | |
| 28 | + public InvalidRequestException(RequestFailureReason reason, String message, Throwable cause) | |
| 29 | + { | |
| 30 | + super(message, cause); | |
| 31 | + this.reason = reason; | |
| 32 | + } | |
| 33 | + | |
| 34 | + public RequestFailureReason getReason() | |
| 35 | + { | |
| 36 | + return this.reason; | |
| 37 | + } | |
| 38 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidRequestKeyException.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.exceptions; | |
| 2 | + | |
| 3 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | |
| 4 | + | |
| 5 | +public class InvalidRequestKeyException extends InvalidRequestException | |
| 6 | +{ | |
| 7 | + private static final long serialVersionUID = 1L; | |
| 8 | + | |
| 9 | + public InvalidRequestKeyException() | |
| 10 | + { | |
| 11 | + super(RequestFailureReason.BAD_PARAMS); | |
| 12 | + } | |
| 13 | + | |
| 14 | + public InvalidRequestKeyException(String message) | |
| 15 | + { | |
| 16 | + super(RequestFailureReason.BAD_PARAMS, message); | |
| 17 | + } | |
| 18 | + | |
| 19 | + public InvalidRequestKeyException(Throwable cause) | |
| 20 | + { | |
| 21 | + super(RequestFailureReason.BAD_PARAMS, cause); | |
| 22 | + } | |
| 23 | + | |
| 24 | + public InvalidRequestKeyException(String message, Throwable cause) | |
| 25 | + { | |
| 26 | + super(RequestFailureReason.BAD_PARAMS, message, cause); | |
| 27 | + } | |
| 28 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidRequestValueException.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.exceptions; | |
| 2 | + | |
| 3 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | |
| 4 | + | |
| 5 | +public class InvalidRequestValueException extends InvalidRequestException | |
| 6 | +{ | |
| 7 | + private static final long serialVersionUID = 1L; | |
| 8 | + | |
| 9 | + public InvalidRequestValueException() | |
| 10 | + { | |
| 11 | + super(RequestFailureReason.BAD_PARAMS); | |
| 12 | + } | |
| 13 | + | |
| 14 | + public InvalidRequestValueException(String message) | |
| 15 | + { | |
| 16 | + super(RequestFailureReason.BAD_PARAMS, message); | |
| 17 | + } | |
| 18 | + | |
| 19 | + public InvalidRequestValueException(Throwable cause) | |
| 20 | + { | |
| 21 | + super(RequestFailureReason.BAD_PARAMS, cause); | |
| 22 | + } | |
| 23 | + | |
| 24 | + public InvalidRequestValueException(String message, Throwable cause) | |
| 25 | + { | |
| 26 | + super(RequestFailureReason.BAD_PARAMS, message, cause); | |
| 27 | + } | |
| 28 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidResponseException.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.exceptions; | |
| 2 | + | |
| 3 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | |
| 4 | + | |
| 5 | +public class InvalidResponseException extends RuntimeException | |
| 6 | +{ | |
| 7 | + private static final long serialVersionUID = 1L; | |
| 8 | + | |
| 9 | + private final RequestFailureReason response; | |
| 10 | + | |
| 11 | + public InvalidResponseException(RequestFailureReason response) | |
| 12 | + { | |
| 13 | + this.response = response; | |
| 14 | + } | |
| 15 | + | |
| 16 | + public InvalidResponseException(RequestFailureReason response, String message) | |
| 17 | + { | |
| 18 | + super(message); | |
| 19 | + this.response = response; | |
| 20 | + } | |
| 21 | + | |
| 22 | + public InvalidResponseException(RequestFailureReason response, Throwable cause) | |
| 23 | + { | |
| 24 | + super(cause); | |
| 25 | + this.response = response; | |
| 26 | + } | |
| 27 | + | |
| 28 | + public InvalidResponseException(RequestFailureReason response, String message, Throwable cause) | |
| 29 | + { | |
| 30 | + super(message, cause); | |
| 31 | + this.response = response; | |
| 32 | + } | |
| 33 | + | |
| 34 | + public RequestFailureReason getReason() | |
| 35 | + { | |
| 36 | + return this.response; | |
| 37 | + } | |
| 38 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidServiceException.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.exceptions; | |
| 2 | + | |
| 3 | +public class InvalidServiceException extends RuntimeException | |
| 4 | +{ | |
| 5 | + private static final long serialVersionUID = 1L; | |
| 6 | + | |
| 7 | + public InvalidServiceException() | |
| 8 | + { | |
| 9 | + } | |
| 10 | + | |
| 11 | + public InvalidServiceException(String message) | |
| 12 | + { | |
| 13 | + super(message); | |
| 14 | + } | |
| 15 | + | |
| 16 | + public InvalidServiceException(Throwable cause) | |
| 17 | + { | |
| 18 | + super(cause); | |
| 19 | + } | |
| 20 | + | |
| 21 | + public InvalidServiceException(String message, Throwable cause) | |
| 22 | + { | |
| 23 | + super(message, cause); | |
| 24 | + } | |
| 25 | + | |
| 26 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidUUIDException.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.exceptions; | |
| 2 | + | |
| 3 | +public class InvalidUUIDException extends RuntimeException | |
| 4 | +{ | |
| 5 | + private static final long serialVersionUID = 1L; | |
| 6 | + | |
| 7 | + public InvalidUUIDException() | |
| 8 | + { | |
| 9 | + } | |
| 10 | + | |
| 11 | + public InvalidUUIDException(String message) | |
| 12 | + { | |
| 13 | + super(message); | |
| 14 | + } | |
| 15 | + | |
| 16 | + public InvalidUUIDException(Throwable cause) | |
| 17 | + { | |
| 18 | + super(cause); | |
| 19 | + } | |
| 20 | + | |
| 21 | + public InvalidUUIDException(String message, Throwable cause) | |
| 22 | + { | |
| 23 | + super(message, cause); | |
| 24 | + } | |
| 25 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/exceptions/InvalidValueException.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.exceptions; | |
| 2 | + | |
| 3 | +public class InvalidValueException extends RuntimeException | |
| 4 | +{ | |
| 5 | + private static final long serialVersionUID = 1L; | |
| 6 | + | |
| 7 | + public InvalidValueException() | |
| 8 | + { | |
| 9 | + } | |
| 10 | + | |
| 11 | + public InvalidValueException(String message) | |
| 12 | + { | |
| 13 | + super(message); | |
| 14 | + } | |
| 15 | + | |
| 16 | + public InvalidValueException(Throwable cause) | |
| 17 | + { | |
| 18 | + super(cause); | |
| 19 | + } | |
| 20 | + | |
| 21 | + public InvalidValueException(String message, Throwable cause) | |
| 22 | + { | |
| 23 | + super(message, cause); | |
| 24 | + } | |
| 25 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/exceptions/ReadOnlyPreferencesException.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.exceptions; | |
| 2 | + | |
| 3 | +public class ReadOnlyPreferencesException extends InvalidPreferenceOperationException | |
| 4 | +{ | |
| 5 | + private static final long serialVersionUID = 1L; | |
| 6 | + | |
| 7 | + public ReadOnlyPreferencesException() | |
| 8 | + { | |
| 9 | + } | |
| 10 | + | |
| 11 | + public ReadOnlyPreferencesException(String message) | |
| 12 | + { | |
| 13 | + super(message); | |
| 14 | + } | |
| 15 | + | |
| 16 | + public ReadOnlyPreferencesException(Throwable cause) | |
| 17 | + { | |
| 18 | + super(cause); | |
| 19 | + } | |
| 20 | + | |
| 21 | + public ReadOnlyPreferencesException(String message, Throwable cause) | |
| 22 | + { | |
| 23 | + super(message, cause); | |
| 24 | + } | |
| 25 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/framework/RequestFailureReason.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.framework; | |
| 2 | + | |
| 3 | +public enum RequestFailureReason | |
| 4 | +{ | |
| 5 | + UNKNOWN(1), | |
| 6 | + BAD_PARAMS(1), | |
| 7 | + NO_SESSION(100), | |
| 8 | + SERVER_ERROR(3), | |
| 9 | + UNAUTHORISED(5), | |
| 10 | + THROTTLED(2), | |
| 11 | + UUID_MISMATCH(10), | |
| 12 | + BAD_DATA(1); | |
| 13 | + | |
| 14 | + private final int severity; | |
| 15 | + | |
| 16 | + private RequestFailureReason(int severity) | |
| 17 | + { | |
| 18 | + this.severity = severity; | |
| 19 | + } | |
| 20 | + | |
| 21 | + public int getSeverity() | |
| 22 | + { | |
| 23 | + return this.severity; | |
| 24 | + } | |
| 25 | + | |
| 26 | + public boolean isPermanent() | |
| 27 | + { | |
| 28 | + return this.severity > 99; | |
| 29 | + } | |
| 30 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesProvider.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.framework; | |
| 2 | + | |
| 3 | +import java.net.Proxy; | |
| 4 | +import java.util.Map; | |
| 5 | +import java.util.Set; | |
| 6 | +import java.util.concurrent.BlockingQueue; | |
| 7 | +import java.util.concurrent.LinkedBlockingQueue; | |
| 8 | + | |
| 9 | +import net.minecraft.util.Session; | |
| 10 | + | |
| 11 | +import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | |
| 12 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesClient; | |
| 13 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesProvider; | |
| 14 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesService; | |
| 15 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceMonitor; | |
| 16 | + | |
| 17 | +public class WebPreferencesProvider extends Thread implements IWebPreferencesProvider, IWebPreferencesServiceMonitor | |
| 18 | +{ | |
| 19 | + private final IWebPreferencesService service; | |
| 20 | + | |
| 21 | + private final String hostName; | |
| 22 | + | |
| 23 | + private final Session session; | |
| 24 | + | |
| 25 | + private final int failureThreshold; | |
| 26 | + | |
| 27 | + private int failureCount = 0; | |
| 28 | + | |
| 29 | + private volatile boolean active = true; | |
| 30 | + | |
| 31 | + private final BlockingQueue<WebPreferencesServiceTask> tasks = new LinkedBlockingQueue<WebPreferencesServiceTask>(2048); | |
| 32 | + | |
| 33 | + public WebPreferencesProvider(Proxy proxy, Session session, String hostName, int maxFailedRequestsCount) | |
| 34 | + { | |
| 35 | + this.service = new WebPreferencesService(proxy, session); | |
| 36 | + this.service.addMonitor(this); | |
| 37 | + | |
| 38 | + this.hostName = hostName; | |
| 39 | + this.session = session; | |
| 40 | + this.failureThreshold = maxFailedRequestsCount; | |
| 41 | + | |
| 42 | + this.setName("WebPreferencesProvider daemon thread [" + hostName + "]"); | |
| 43 | + this.setDaemon(true); | |
| 44 | + this.start(); | |
| 45 | + } | |
| 46 | + | |
| 47 | + @Override | |
| 48 | + public boolean isActive() | |
| 49 | + { | |
| 50 | + return this.active; | |
| 51 | + } | |
| 52 | + | |
| 53 | + public void onTick() | |
| 54 | + { | |
| 55 | + } | |
| 56 | + | |
| 57 | + @Override | |
| 58 | + public void run() | |
| 59 | + { | |
| 60 | + try | |
| 61 | + { | |
| 62 | + while (this.active) | |
| 63 | + { | |
| 64 | + WebPreferencesServiceTask task = this.tasks.take(); | |
| 65 | + try | |
| 66 | + { | |
| 67 | + LiteLoaderLogger.debug("WebPreferencesProvider [%s] is processing %s for %s", this.hostName, | |
| 68 | + task.getClass().getSimpleName(), task.getRequest().getUUID()); | |
| 69 | + this.service.submit(task.getRequest()); | |
| 70 | + } | |
| 71 | + catch (Throwable th) | |
| 72 | + { | |
| 73 | + if (th instanceof InterruptedException) throw (InterruptedException)th; | |
| 74 | + th.printStackTrace(); | |
| 75 | + | |
| 76 | + this.onRequestFailed(th, 1); | |
| 77 | + } | |
| 78 | + } | |
| 79 | + } | |
| 80 | + catch (InterruptedException ex) | |
| 81 | + { | |
| 82 | + ex.printStackTrace(); | |
| 83 | + } | |
| 84 | + } | |
| 85 | + | |
| 86 | + @Override | |
| 87 | + public void onKeyRequestFailed() | |
| 88 | + { | |
| 89 | + this.registerError(this.failureThreshold / 2); | |
| 90 | + } | |
| 91 | + | |
| 92 | + @Override | |
| 93 | + public void onRequestFailed(Throwable th, int severity) | |
| 94 | + { | |
| 95 | + this.registerError(severity); | |
| 96 | + } | |
| 97 | + | |
| 98 | + private void registerError(int severity) | |
| 99 | + { | |
| 100 | + this.failureCount += severity; | |
| 101 | + if (this.failureCount >= this.failureThreshold) | |
| 102 | + { | |
| 103 | + LiteLoaderLogger.warning("WebPreferencesProvider for " + this.hostName + " is terminating. Too many failed requests."); | |
| 104 | + this.active = false; | |
| 105 | + this.tasks.clear(); | |
| 106 | + this.interrupt(); | |
| 107 | + } | |
| 108 | + } | |
| 109 | + | |
| 110 | + @Override | |
| 111 | + public boolean requestGet(IWebPreferencesClient client, String uuid, Set<String> keys, boolean getPrivate) | |
| 112 | + { | |
| 113 | + if (!this.isActive()) | |
| 114 | + { | |
| 115 | + return false; | |
| 116 | + } | |
| 117 | + | |
| 118 | + WebPreferencesServiceTask task = new WebPreferencesServiceTaskGet(this, client); | |
| 119 | + task.setRequest(new WebPreferencesRequestGet(task, uuid, keys, getPrivate)); | |
| 120 | + return this.tasks.offer(task); | |
| 121 | + } | |
| 122 | + | |
| 123 | + @Override | |
| 124 | + public boolean requestSet(IWebPreferencesClient client, String uuid, Map<String, String> values, boolean setPrivate) | |
| 125 | + { | |
| 126 | + if (!this.isActive()) | |
| 127 | + { | |
| 128 | + return false; | |
| 129 | + } | |
| 130 | + | |
| 131 | + WebPreferencesServiceTask task = new WebPreferencesServiceTaskSet(this, client); | |
| 132 | + task.setRequest(new WebPreferencesRequestSet(task, uuid, values, setPrivate)); | |
| 133 | + return this.tasks.offer(task); | |
| 134 | + } | |
| 135 | + | |
| 136 | + @Override | |
| 137 | + public String getHostName() | |
| 138 | + { | |
| 139 | + return this.hostName; | |
| 140 | + } | |
| 141 | + | |
| 142 | + @Override | |
| 143 | + public Session getSession() | |
| 144 | + { | |
| 145 | + return this.session; | |
| 146 | + } | |
| 147 | + | |
| 148 | + @Override | |
| 149 | + public IWebPreferencesService getService() | |
| 150 | + { | |
| 151 | + return this.service; | |
| 152 | + } | |
| 153 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesRequestAbstract.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.framework; | |
| 2 | + | |
| 3 | +import java.net.URI; | |
| 4 | +import java.util.HashMap; | |
| 5 | +import java.util.Map; | |
| 6 | +import java.util.regex.Pattern; | |
| 7 | + | |
| 8 | +import net.minecraft.util.Session; | |
| 9 | + | |
| 10 | +import com.google.gson.Gson; | |
| 11 | +import com.mumfrey.webprefs.exceptions.InvalidRequestException; | |
| 12 | +import com.mumfrey.webprefs.exceptions.InvalidRequestKeyException; | |
| 13 | +import com.mumfrey.webprefs.exceptions.InvalidRequestValueException; | |
| 14 | +import com.mumfrey.webprefs.exceptions.InvalidResponseException; | |
| 15 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | |
| 16 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | |
| 17 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceDelegate; | |
| 18 | + | |
| 19 | +abstract class WebPreferencesRequestAbstract implements IWebPreferencesRequest | |
| 20 | +{ | |
| 21 | + private static final long serialVersionUID = 1L; | |
| 22 | + | |
| 23 | + private static final Pattern keyPattern = Pattern.compile("^[a-z0-9_\\-\\.]{1,32}$"); | |
| 24 | + | |
| 25 | + private static final Gson gson = new Gson(); | |
| 26 | + | |
| 27 | + private final transient URI uri; | |
| 28 | + | |
| 29 | + private final transient IWebPreferencesServiceDelegate delegate; | |
| 30 | + | |
| 31 | + private final transient String uuid; | |
| 32 | + | |
| 33 | + public WebPreferencesRequestAbstract(IWebPreferencesServiceDelegate delegate, String uuid) | |
| 34 | + { | |
| 35 | + if (delegate == null) | |
| 36 | + { | |
| 37 | + throw new IllegalArgumentException("Attempted to create a request with no delegate"); | |
| 38 | + } | |
| 39 | + | |
| 40 | + this.uri = URI.create(String.format("http://%s%s", delegate.getHostName(), this.getPath())); | |
| 41 | + | |
| 42 | + this.delegate = delegate; | |
| 43 | + this.uuid = uuid; | |
| 44 | + } | |
| 45 | + | |
| 46 | + protected abstract String getPath(); | |
| 47 | + | |
| 48 | + @Override | |
| 49 | + public IWebPreferencesServiceDelegate getDelegate() | |
| 50 | + { | |
| 51 | + return this.delegate; | |
| 52 | + } | |
| 53 | + | |
| 54 | + @Override | |
| 55 | + public URI getRequestURI() | |
| 56 | + { | |
| 57 | + return this.uri; | |
| 58 | + } | |
| 59 | + | |
| 60 | + @Override | |
| 61 | + public String getUUID() | |
| 62 | + { | |
| 63 | + return this.uuid; | |
| 64 | + } | |
| 65 | + | |
| 66 | + @Override | |
| 67 | + public Map<String, String> getPostVars() | |
| 68 | + { | |
| 69 | + Map<String, String> params = new HashMap<String, String>(); | |
| 70 | + this.addParams(params); | |
| 71 | + return params; | |
| 72 | + } | |
| 73 | + | |
| 74 | + protected void addParams(Map<String, String> params) | |
| 75 | + { | |
| 76 | + if (this.isValidationRequired()) | |
| 77 | + { | |
| 78 | + Session session = this.getDelegate().getSession(); | |
| 79 | + if (session == null) | |
| 80 | + { | |
| 81 | + throw new InvalidRequestException(RequestFailureReason.NO_SESSION, "Request has no session"); | |
| 82 | + } | |
| 83 | + | |
| 84 | + params.put("u", session.getUsername()); | |
| 85 | + } | |
| 86 | + | |
| 87 | + params.put("i", this.uuid); | |
| 88 | + params.put("j", this.toJson()); | |
| 89 | + } | |
| 90 | + | |
| 91 | + @Override | |
| 92 | + public final void onReceivedResponse(IWebPreferencesResponse response) | |
| 93 | + { | |
| 94 | + if (response == null) | |
| 95 | + { | |
| 96 | + throw new InvalidResponseException(null, "Error reading server response"); | |
| 97 | + } | |
| 98 | + | |
| 99 | + if (response.getResponse().startsWith("500")) | |
| 100 | + { | |
| 101 | + throw new InvalidResponseException(RequestFailureReason.SERVER_ERROR, | |
| 102 | + "The server returned an invalid resonse: " + response.getResponse(), response.getThrowable()); | |
| 103 | + } | |
| 104 | + | |
| 105 | + if (!response.getResponse().startsWith("200")) | |
| 106 | + { | |
| 107 | + RequestFailureReason reason = RequestFailureReason.UNKNOWN; | |
| 108 | + | |
| 109 | + if (response.getResponse().startsWith("429")) reason = RequestFailureReason.THROTTLED; | |
| 110 | + if (response.getResponse().startsWith("401")) reason = RequestFailureReason.UNAUTHORISED; | |
| 111 | + | |
| 112 | + String message = response.getMessage(); | |
| 113 | + throw new InvalidResponseException(reason, | |
| 114 | + "The server responsed with " + response.getResponse() + (message != null ? " \"" + message + "\"" : "")); | |
| 115 | + } | |
| 116 | + | |
| 117 | + if (!this.getUUID().equals(response.getUUID())) | |
| 118 | + { | |
| 119 | + throw new InvalidResponseException(RequestFailureReason.UUID_MISMATCH, "The response UUID did not match the request"); | |
| 120 | + } | |
| 121 | + | |
| 122 | + this.validateResponse(response); | |
| 123 | + } | |
| 124 | + | |
| 125 | + protected abstract void validateResponse(IWebPreferencesResponse response); | |
| 126 | + | |
| 127 | + protected final void validateKey(String key) | |
| 128 | + { | |
| 129 | + if (key == null || !WebPreferencesRequestAbstract.keyPattern.matcher(key).matches()) | |
| 130 | + { | |
| 131 | + throw new InvalidRequestKeyException("The specified key [" + key + "] is not valid"); | |
| 132 | + } | |
| 133 | + } | |
| 134 | + | |
| 135 | + protected final void validateValue(String key, String value) | |
| 136 | + { | |
| 137 | + if (value == null || value.length() > 255) | |
| 138 | + { | |
| 139 | + throw new InvalidRequestValueException("The specified value [" + value + "] for key [" + key + "] is not valid"); | |
| 140 | + } | |
| 141 | + } | |
| 142 | + | |
| 143 | + public String toJson() | |
| 144 | + { | |
| 145 | + return WebPreferencesRequestAbstract.gson.toJson(this); | |
| 146 | + } | |
| 147 | + | |
| 148 | + @Override | |
| 149 | + public String toString() | |
| 150 | + { | |
| 151 | + try | |
| 152 | + { | |
| 153 | + return WebPreferencesRequestAbstract.gson.toJson(this); | |
| 154 | + } | |
| 155 | + catch (Throwable th) | |
| 156 | + { | |
| 157 | + return "{\"Invalid JSON\"}"; | |
| 158 | + } | |
| 159 | + } | |
| 160 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesRequestGet.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.framework; | |
| 2 | + | |
| 3 | +import java.util.HashSet; | |
| 4 | +import java.util.Set; | |
| 5 | + | |
| 6 | +import com.google.gson.annotations.Expose; | |
| 7 | +import com.google.gson.annotations.SerializedName; | |
| 8 | +import com.mumfrey.webprefs.exceptions.InvalidRequestException; | |
| 9 | +import com.mumfrey.webprefs.exceptions.InvalidResponseException; | |
| 10 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | |
| 11 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceDelegate; | |
| 12 | + | |
| 13 | +class WebPreferencesRequestGet extends WebPreferencesRequestAbstract | |
| 14 | +{ | |
| 15 | + private static final long serialVersionUID = 1L; | |
| 16 | + | |
| 17 | + @Expose @SerializedName("get") | |
| 18 | + private final Set<String> keys = new HashSet<String>(); | |
| 19 | + | |
| 20 | + @Expose @SerializedName("private") | |
| 21 | + private boolean isPrivate; | |
| 22 | + | |
| 23 | + public WebPreferencesRequestGet(IWebPreferencesServiceDelegate delegate, String uuid, Set<String> keys) | |
| 24 | + { | |
| 25 | + this(delegate, uuid, keys, false); | |
| 26 | + } | |
| 27 | + | |
| 28 | + public WebPreferencesRequestGet(IWebPreferencesServiceDelegate delegate, String uuid, Set<String> keys, boolean isPrivate) | |
| 29 | + { | |
| 30 | + super(delegate, uuid); | |
| 31 | + | |
| 32 | + if (isPrivate && delegate.getSession() == null) | |
| 33 | + { | |
| 34 | + throw new InvalidRequestException(RequestFailureReason.NO_SESSION, "Cannot request private values without supplying a session"); | |
| 35 | + } | |
| 36 | + | |
| 37 | + this.validate(keys); | |
| 38 | + | |
| 39 | + this.keys.addAll(keys); | |
| 40 | + this.isPrivate = isPrivate; | |
| 41 | + } | |
| 42 | + | |
| 43 | + @Override | |
| 44 | + protected String getPath() | |
| 45 | + { | |
| 46 | + return "/get"; | |
| 47 | + } | |
| 48 | + | |
| 49 | + @Override | |
| 50 | + public boolean isValidationRequired() | |
| 51 | + { | |
| 52 | + return this.isPrivate; | |
| 53 | + } | |
| 54 | + | |
| 55 | + @Override | |
| 56 | + public Set<String> getKeys() | |
| 57 | + { | |
| 58 | + return this.keys; | |
| 59 | + } | |
| 60 | + | |
| 61 | + @Override | |
| 62 | + protected void validateResponse(IWebPreferencesResponse response) | |
| 63 | + { | |
| 64 | + if (response.hasValues()) | |
| 65 | + { | |
| 66 | + Set<String> responseKeys = response.getValues().keySet(); | |
| 67 | + for (String key : this.keys) | |
| 68 | + { | |
| 69 | + if (!responseKeys.contains(key)) | |
| 70 | + { | |
| 71 | + throw new InvalidResponseException(RequestFailureReason.BAD_DATA, | |
| 72 | + "The server responded with an incomplete key set, missing key [" + key + "]"); | |
| 73 | + } | |
| 74 | + } | |
| 75 | + } | |
| 76 | + } | |
| 77 | + | |
| 78 | + private void validate(Set<String> keys) | |
| 79 | + { | |
| 80 | + if (keys == null || keys.isEmpty()) | |
| 81 | + { | |
| 82 | + throw new InvalidRequestException(RequestFailureReason.BAD_PARAMS, "Cannot request an empty set"); | |
| 83 | + } | |
| 84 | + | |
| 85 | + for (String key : keys) | |
| 86 | + { | |
| 87 | + this.validateKey(key); | |
| 88 | + } | |
| 89 | + } | |
| 90 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesRequestKey.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.framework; | |
| 2 | + | |
| 3 | +import java.util.HashSet; | |
| 4 | +import java.util.Set; | |
| 5 | + | |
| 6 | +import net.minecraft.util.Session; | |
| 7 | + | |
| 8 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | |
| 9 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | |
| 10 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceDelegate; | |
| 11 | + | |
| 12 | +public class WebPreferencesRequestKey extends WebPreferencesRequestAbstract | |
| 13 | +{ | |
| 14 | + private static final long serialVersionUID = 1L; | |
| 15 | + | |
| 16 | + protected final transient WebPreferencesService server; | |
| 17 | + | |
| 18 | + public WebPreferencesRequestKey(final WebPreferencesService server, final Session session, final String hostName) | |
| 19 | + { | |
| 20 | + super(new IWebPreferencesServiceDelegate() | |
| 21 | + { | |
| 22 | + @Override | |
| 23 | + public void onRequestFailed(IWebPreferencesRequest request, Throwable th, RequestFailureReason reason) | |
| 24 | + { | |
| 25 | + server.handleKeyRequestFailed(th); | |
| 26 | + } | |
| 27 | + | |
| 28 | + @Override | |
| 29 | + public void onReceivedResponse(IWebPreferencesRequest request, IWebPreferencesResponse response) | |
| 30 | + { | |
| 31 | + server.handleKeyRequestCompleted(response); | |
| 32 | + } | |
| 33 | + | |
| 34 | + @Override | |
| 35 | + public Session getSession() | |
| 36 | + { | |
| 37 | + return session; | |
| 38 | + } | |
| 39 | + | |
| 40 | + @Override | |
| 41 | + public String getHostName() | |
| 42 | + { | |
| 43 | + return hostName; | |
| 44 | + } | |
| 45 | + }, session.getPlayerID()); | |
| 46 | + | |
| 47 | + this.server = server; | |
| 48 | + } | |
| 49 | + | |
| 50 | + @Override | |
| 51 | + public boolean isValidationRequired() | |
| 52 | + { | |
| 53 | + return true; | |
| 54 | + } | |
| 55 | + | |
| 56 | + @Override | |
| 57 | + protected String getPath() | |
| 58 | + { | |
| 59 | + return "/key"; | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Override | |
| 63 | + protected void validateResponse(IWebPreferencesResponse response) | |
| 64 | + { | |
| 65 | + } | |
| 66 | + | |
| 67 | + @Override | |
| 68 | + public Set<String> getKeys() | |
| 69 | + { | |
| 70 | + return new HashSet<String>(); | |
| 71 | + } | |
| 72 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesRequestSet.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.framework; | |
| 2 | + | |
| 3 | +import java.util.HashMap; | |
| 4 | +import java.util.Map; | |
| 5 | +import java.util.Map.Entry; | |
| 6 | +import java.util.Set; | |
| 7 | + | |
| 8 | +import com.google.gson.annotations.Expose; | |
| 9 | +import com.google.gson.annotations.SerializedName; | |
| 10 | +import com.mumfrey.webprefs.exceptions.InvalidRequestException; | |
| 11 | +import com.mumfrey.webprefs.exceptions.InvalidResponseException; | |
| 12 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | |
| 13 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceDelegate; | |
| 14 | + | |
| 15 | +class WebPreferencesRequestSet extends WebPreferencesRequestAbstract | |
| 16 | +{ | |
| 17 | + private static final long serialVersionUID = 1L; | |
| 18 | + | |
| 19 | + @Expose @SerializedName("set") | |
| 20 | + private final Map<String, String> map = new HashMap<String, String>(); | |
| 21 | + | |
| 22 | + @Expose @SerializedName("private") | |
| 23 | + private boolean isPrivate; | |
| 24 | + | |
| 25 | + public WebPreferencesRequestSet(IWebPreferencesServiceDelegate delegate, String uuid, Map<String, String> values) | |
| 26 | + { | |
| 27 | + this(delegate, uuid, values, false); | |
| 28 | + } | |
| 29 | + | |
| 30 | + public WebPreferencesRequestSet(IWebPreferencesServiceDelegate delegate, String uuid, Map<String, String> values, boolean isPrivate) | |
| 31 | + { | |
| 32 | + super(delegate, uuid); | |
| 33 | + | |
| 34 | + if (isPrivate && delegate.getSession() == null) | |
| 35 | + { | |
| 36 | + throw new InvalidRequestException(RequestFailureReason.NO_SESSION, "Cannot request private values without supplying a session"); | |
| 37 | + } | |
| 38 | + | |
| 39 | + this.validate(values); | |
| 40 | + | |
| 41 | + this.map.putAll(values); | |
| 42 | + this.isPrivate = isPrivate; | |
| 43 | + } | |
| 44 | + | |
| 45 | + @Override | |
| 46 | + protected String getPath() | |
| 47 | + { | |
| 48 | + return "/set"; | |
| 49 | + } | |
| 50 | + | |
| 51 | + @Override | |
| 52 | + public boolean isValidationRequired() | |
| 53 | + { | |
| 54 | + return true; | |
| 55 | + } | |
| 56 | + | |
| 57 | + @Override | |
| 58 | + public Set<String> getKeys() | |
| 59 | + { | |
| 60 | + return this.map.keySet(); | |
| 61 | + } | |
| 62 | + | |
| 63 | + public Map<String, String> getMap() | |
| 64 | + { | |
| 65 | + return this.map; | |
| 66 | + } | |
| 67 | + | |
| 68 | + @Override | |
| 69 | + protected void validateResponse(IWebPreferencesResponse response) | |
| 70 | + { | |
| 71 | + if (response.hasSetters()) | |
| 72 | + { | |
| 73 | + Set<String> responseKeys = response.getSetters(); | |
| 74 | + for (String key : this.map.keySet()) | |
| 75 | + { | |
| 76 | + if (!responseKeys.contains(key)) | |
| 77 | + { | |
| 78 | + throw new InvalidResponseException(RequestFailureReason.BAD_DATA, | |
| 79 | + "The server responded with an incomplete key set, missing key [" + key + "]"); | |
| 80 | + } | |
| 81 | + } | |
| 82 | + } | |
| 83 | + } | |
| 84 | + | |
| 85 | + private void validate(Map<String, String> set) | |
| 86 | + { | |
| 87 | + for (Entry<String, String> entry : set.entrySet()) | |
| 88 | + { | |
| 89 | + this.validateKey(entry.getKey()); | |
| 90 | + this.validateValue(entry.getKey(), entry.getValue()); | |
| 91 | + } | |
| 92 | + } | |
| 93 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesResponse.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.framework; | |
| 2 | + | |
| 3 | +import java.util.HashSet; | |
| 4 | +import java.util.List; | |
| 5 | +import java.util.Map; | |
| 6 | +import java.util.Set; | |
| 7 | + | |
| 8 | +import com.google.gson.Gson; | |
| 9 | +import com.google.gson.GsonBuilder; | |
| 10 | +import com.google.gson.JsonSyntaxException; | |
| 11 | +import com.google.gson.annotations.Expose; | |
| 12 | +import com.google.gson.annotations.SerializedName; | |
| 13 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | |
| 14 | + | |
| 15 | +class WebPreferencesResponse implements IWebPreferencesResponse | |
| 16 | +{ | |
| 17 | + private static final long serialVersionUID = 1L; | |
| 18 | + | |
| 19 | + private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); | |
| 20 | + | |
| 21 | + @Expose @SerializedName("response") | |
| 22 | + private String response; | |
| 23 | + | |
| 24 | + @Expose @SerializedName("message") | |
| 25 | + private String message; | |
| 26 | + | |
| 27 | + @Expose @SerializedName("uuid") | |
| 28 | + private String uuid; | |
| 29 | + | |
| 30 | + @Expose @SerializedName("serverid") | |
| 31 | + private String serverId; | |
| 32 | + | |
| 33 | + @Expose @SerializedName("rate") | |
| 34 | + private int rateLimit; | |
| 35 | + | |
| 36 | + @Expose @SerializedName("get") | |
| 37 | + private Map<String, String> get; | |
| 38 | + | |
| 39 | + @Expose @SerializedName("set") | |
| 40 | + private List<String> set; | |
| 41 | + | |
| 42 | + private transient Throwable th; | |
| 43 | + | |
| 44 | + public WebPreferencesResponse() {} | |
| 45 | + | |
| 46 | + private WebPreferencesResponse(String response, Throwable th) | |
| 47 | + { | |
| 48 | + this.response = response; | |
| 49 | + this.th = th; | |
| 50 | + } | |
| 51 | + | |
| 52 | + @Override | |
| 53 | + public String getResponse() | |
| 54 | + { | |
| 55 | + return this.response; | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Override | |
| 59 | + public String getMessage() | |
| 60 | + { | |
| 61 | + return this.message; | |
| 62 | + } | |
| 63 | + | |
| 64 | + @Override | |
| 65 | + public Throwable getThrowable() | |
| 66 | + { | |
| 67 | + return this.th; | |
| 68 | + } | |
| 69 | + | |
| 70 | + @Override | |
| 71 | + public String getUUID() | |
| 72 | + { | |
| 73 | + return this.uuid; | |
| 74 | + } | |
| 75 | + | |
| 76 | + @Override | |
| 77 | + public String getServerId() | |
| 78 | + { | |
| 79 | + return this.serverId; | |
| 80 | + } | |
| 81 | + | |
| 82 | + @Override | |
| 83 | + public boolean hasValues() | |
| 84 | + { | |
| 85 | + return this.get != null; | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Override | |
| 89 | + public Map<String, String> getValues() | |
| 90 | + { | |
| 91 | + return this.get; | |
| 92 | + } | |
| 93 | + | |
| 94 | + @Override | |
| 95 | + public boolean hasSetters() | |
| 96 | + { | |
| 97 | + return this.set != null; | |
| 98 | + } | |
| 99 | + | |
| 100 | + @Override | |
| 101 | + public Set<String> getSetters() | |
| 102 | + { | |
| 103 | + return new HashSet<String>(this.set); | |
| 104 | + } | |
| 105 | + | |
| 106 | + public static IWebPreferencesResponse fromJson(String json) | |
| 107 | + { | |
| 108 | + try | |
| 109 | + { | |
| 110 | + return WebPreferencesResponse.gson.fromJson(json, WebPreferencesResponse.class); | |
| 111 | + } | |
| 112 | + catch (JsonSyntaxException ex) | |
| 113 | + { | |
| 114 | + return new WebPreferencesResponse("500 Invalid JSON", ex); | |
| 115 | + } | |
| 116 | + catch (Throwable th) | |
| 117 | + { | |
| 118 | + return new WebPreferencesResponse("500 Invalid JSON", th); | |
| 119 | + } | |
| 120 | + } | |
| 121 | + | |
| 122 | + @Override | |
| 123 | + public String toString() | |
| 124 | + { | |
| 125 | + try | |
| 126 | + { | |
| 127 | + return WebPreferencesResponse.gson.toJson(this); | |
| 128 | + } | |
| 129 | + catch (Throwable th) | |
| 130 | + { | |
| 131 | + return "{\"Invalid JSON\"}"; | |
| 132 | + } | |
| 133 | + } | |
| 134 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesService.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.framework; | |
| 2 | + | |
| 3 | +import java.io.BufferedReader; | |
| 4 | +import java.io.IOException; | |
| 5 | +import java.io.InputStream; | |
| 6 | +import java.io.InputStreamReader; | |
| 7 | +import java.io.OutputStream; | |
| 8 | +import java.io.UnsupportedEncodingException; | |
| 9 | +import java.net.HttpURLConnection; | |
| 10 | +import java.net.Proxy; | |
| 11 | +import java.net.URI; | |
| 12 | +import java.net.URL; | |
| 13 | +import java.net.URLEncoder; | |
| 14 | +import java.util.ArrayList; | |
| 15 | +import java.util.HashMap; | |
| 16 | +import java.util.List; | |
| 17 | +import java.util.Map; | |
| 18 | +import java.util.Map.Entry; | |
| 19 | + | |
| 20 | +import net.minecraft.util.Session; | |
| 21 | + | |
| 22 | +import org.apache.commons.io.IOUtils; | |
| 23 | + | |
| 24 | +import com.google.common.base.Charsets; | |
| 25 | +import com.google.gson.Gson; | |
| 26 | +import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | |
| 27 | +import com.mumfrey.webprefs.exceptions.InvalidRequestException; | |
| 28 | +import com.mumfrey.webprefs.exceptions.InvalidResponseException; | |
| 29 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | |
| 30 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | |
| 31 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesService; | |
| 32 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceMonitor; | |
| 33 | + | |
| 34 | +class WebPreferencesService implements IWebPreferencesService | |
| 35 | +{ | |
| 36 | + private static final int TIMEOUT_MSEC = 5000; | |
| 37 | + | |
| 38 | + private final Proxy proxy; | |
| 39 | + | |
| 40 | + private final Session session; | |
| 41 | + | |
| 42 | + private final Map<String, String> serverKeys = new HashMap<String, String>(); | |
| 43 | + | |
| 44 | + private final List<IWebPreferencesServiceMonitor> monitors = new ArrayList<IWebPreferencesServiceMonitor>(); | |
| 45 | + | |
| 46 | + private long lastMojangAuth = 0L; | |
| 47 | + | |
| 48 | + WebPreferencesService(Proxy proxy, Session session) | |
| 49 | + { | |
| 50 | + this.proxy = proxy; | |
| 51 | + this.session = session; | |
| 52 | + } | |
| 53 | + | |
| 54 | + @Override | |
| 55 | + public void addMonitor(IWebPreferencesServiceMonitor monitor) | |
| 56 | + { | |
| 57 | + if (!this.monitors.contains(monitor)) | |
| 58 | + { | |
| 59 | + this.monitors.add(monitor); | |
| 60 | + } | |
| 61 | + } | |
| 62 | + | |
| 63 | + void handleKeyRequestFailed(Throwable th) | |
| 64 | + { | |
| 65 | + LiteLoaderLogger.debug(th, "Key request failed with message %s", th.getMessage()); | |
| 66 | + | |
| 67 | + for (IWebPreferencesServiceMonitor monitor : this.monitors) | |
| 68 | + { | |
| 69 | + monitor.onKeyRequestFailed(); | |
| 70 | + } | |
| 71 | + } | |
| 72 | + | |
| 73 | + void handleKeyRequestCompleted(IWebPreferencesResponse response) | |
| 74 | + { | |
| 75 | + } | |
| 76 | + | |
| 77 | + @Override | |
| 78 | + public void submit(IWebPreferencesRequest request) | |
| 79 | + { | |
| 80 | + try | |
| 81 | + { | |
| 82 | + this.beginProcessingRequest(request); | |
| 83 | + } | |
| 84 | + catch (InvalidRequestException ex) | |
| 85 | + { | |
| 86 | + request.getDelegate().onRequestFailed(request, ex, ex.getReason()); | |
| 87 | + } | |
| 88 | + } | |
| 89 | + | |
| 90 | + private IWebPreferencesResponse beginProcessingRequest(IWebPreferencesRequest request) throws InvalidRequestException | |
| 91 | + { | |
| 92 | + LiteLoaderLogger.debug("WebPreferencesService is processing %s for %s", request.getClass().getSimpleName(), request.getUUID()); | |
| 93 | + | |
| 94 | + if (request.isValidationRequired()) | |
| 95 | + { | |
| 96 | + String requestClass = request.getClass().getSimpleName(); | |
| 97 | + | |
| 98 | + Session session = request.getDelegate().getSession(); | |
| 99 | + if (session == null) | |
| 100 | + { | |
| 101 | + throw new InvalidRequestException(RequestFailureReason.NO_SESSION, | |
| 102 | + "Validation is required for " + requestClass + " but no session was provided."); | |
| 103 | + } | |
| 104 | + | |
| 105 | + String serverId = this.getServerIdForRequest(request); | |
| 106 | + | |
| 107 | + if (!this.registerServerConnection(session, serverId)) | |
| 108 | + { | |
| 109 | + throw new InvalidRequestException(RequestFailureReason.NO_SESSION, | |
| 110 | + "Validation is required for " + requestClass + " but no session was provided or session validation failed"); | |
| 111 | + } | |
| 112 | + } | |
| 113 | + | |
| 114 | + return this.processRequest(request); | |
| 115 | + } | |
| 116 | + | |
| 117 | + private IWebPreferencesResponse processRequest(IWebPreferencesRequest request) | |
| 118 | + { | |
| 119 | + try | |
| 120 | + { | |
| 121 | + String data = this.httpPost(request.getRequestURI(), request.getPostVars()); | |
| 122 | + IWebPreferencesResponse response = WebPreferencesResponse.fromJson(data); | |
| 123 | + | |
| 124 | + LiteLoaderLogger.debug("Response: %s", response); | |
| 125 | + request.onReceivedResponse(response); | |
| 126 | + | |
| 127 | + request.getDelegate().onReceivedResponse(request, response); | |
| 128 | + return response; | |
| 129 | + } | |
| 130 | + catch (InvalidResponseException ex) | |
| 131 | + { | |
| 132 | + request.getDelegate().onRequestFailed(request, ex, ex.getReason()); | |
| 133 | + | |
| 134 | + for (IWebPreferencesServiceMonitor monitor : this.monitors) | |
| 135 | + { | |
| 136 | + monitor.onRequestFailed(ex, ex.getReason().getSeverity()); | |
| 137 | + } | |
| 138 | + } | |
| 139 | + catch (IOException ex) | |
| 140 | + { | |
| 141 | + request.getDelegate().onRequestFailed(request, ex, RequestFailureReason.SERVER_ERROR); | |
| 142 | + | |
| 143 | + for (IWebPreferencesServiceMonitor monitor : this.monitors) | |
| 144 | + { | |
| 145 | + monitor.onRequestFailed(ex, RequestFailureReason.SERVER_ERROR.getSeverity()); | |
| 146 | + } | |
| 147 | + } | |
| 148 | + catch (Exception ex) | |
| 149 | + { | |
| 150 | + for (IWebPreferencesServiceMonitor monitor : this.monitors) | |
| 151 | + { | |
| 152 | + monitor.onRequestFailed(ex, RequestFailureReason.UNKNOWN.getSeverity()); | |
| 153 | + } | |
| 154 | + } | |
| 155 | + | |
| 156 | + return null; | |
| 157 | + } | |
| 158 | + | |
| 159 | + private String getServerIdForRequest(IWebPreferencesRequest request) | |
| 160 | + { | |
| 161 | + if (request.getDelegate().getSession() == null) | |
| 162 | + { | |
| 163 | + return null; | |
| 164 | + } | |
| 165 | + | |
| 166 | + String hostName = request.getDelegate().getHostName(); | |
| 167 | + String serverId = this.serverKeys.get(hostName); | |
| 168 | + | |
| 169 | + if (serverId == null) | |
| 170 | + { | |
| 171 | + LiteLoaderLogger.info("Looking up server ID for " + hostName); | |
| 172 | + WebPreferencesRequestKey keyRequest = new WebPreferencesRequestKey(this, this.session, hostName); | |
| 173 | + IWebPreferencesResponse response = this.processRequest(keyRequest); | |
| 174 | + if (response == null || response.getServerId() == null) | |
| 175 | + { | |
| 176 | + throw new InvalidRequestException(RequestFailureReason.SERVER_ERROR, "Could not retrieve server ID for " + hostName); | |
| 177 | + } | |
| 178 | + | |
| 179 | + serverId = response.getServerId(); | |
| 180 | + this.serverKeys.put(hostName, serverId); | |
| 181 | + | |
| 182 | + LiteLoaderLogger.info("Got server ID for " + hostName + " [" + serverId + "]"); | |
| 183 | + } | |
| 184 | + | |
| 185 | + return serverId; | |
| 186 | + } | |
| 187 | + | |
| 188 | + public String httpPost(URI uri, Map<String, String> params) throws IOException | |
| 189 | + { | |
| 190 | + String query = this.buildQuery(params); | |
| 191 | + byte[] queryBytes = query.getBytes(Charsets.UTF_8); | |
| 192 | + | |
| 193 | + LiteLoaderLogger.debug("Connecting to " + uri); | |
| 194 | + HttpURLConnection http = (HttpURLConnection)uri.toURL().openConnection(this.proxy); | |
| 195 | + http.setConnectTimeout(WebPreferencesService.TIMEOUT_MSEC); | |
| 196 | + http.setReadTimeout(WebPreferencesService.TIMEOUT_MSEC); | |
| 197 | + http.setUseCaches(false); | |
| 198 | + http.setDoOutput(true); | |
| 199 | + | |
| 200 | + http.addRequestProperty("Content-type", "application/x-www-form-urlencoded"); | |
| 201 | + http.setRequestProperty("Content-Length", "" + queryBytes.length); | |
| 202 | + | |
| 203 | + OutputStream outputStream = null; | |
| 204 | + | |
| 205 | + try | |
| 206 | + { | |
| 207 | + outputStream = http.getOutputStream(); | |
| 208 | + IOUtils.write(queryBytes, outputStream); | |
| 209 | + } | |
| 210 | + finally | |
| 211 | + { | |
| 212 | + IOUtils.closeQuietly(outputStream); | |
| 213 | + } | |
| 214 | + | |
| 215 | + try | |
| 216 | + { | |
| 217 | + String debugMessages = http.getHeaderField("X-Debug-Message"); | |
| 218 | + if (debugMessages != null) | |
| 219 | + { | |
| 220 | + String[] messages = new Gson().fromJson(debugMessages, String[].class); | |
| 221 | + for (String message : messages) | |
| 222 | + { | |
| 223 | + LiteLoaderLogger.debug("[SERVER] %s", message); | |
| 224 | + } | |
| 225 | + } | |
| 226 | + } | |
| 227 | + catch (Exception ex) {} | |
| 228 | + | |
| 229 | + InputStream inputStream = null; | |
| 230 | + | |
| 231 | + try | |
| 232 | + { | |
| 233 | + try | |
| 234 | + { | |
| 235 | + inputStream = http.getInputStream(); | |
| 236 | + String response = IOUtils.toString(inputStream, Charsets.UTF_8); | |
| 237 | + return response; | |
| 238 | + } | |
| 239 | + catch (IOException ex) | |
| 240 | + { | |
| 241 | + IOUtils.closeQuietly(inputStream); | |
| 242 | + inputStream = http.getErrorStream(); | |
| 243 | + if (inputStream == null) | |
| 244 | + { | |
| 245 | + return this.formatErrorAsJson(http.getResponseCode() + " " + http.getResponseMessage(), ex.getMessage()); | |
| 246 | + } | |
| 247 | + | |
| 248 | + String response = IOUtils.toString(inputStream, Charsets.UTF_8); | |
| 249 | + | |
| 250 | + String contentType = http.getHeaderField("Content-type"); | |
| 251 | + if (!"application/json".equals(contentType)) | |
| 252 | + { | |
| 253 | + System.err.println(response); | |
| 254 | + return this.formatErrorAsJson(http.getResponseCode() + " " + http.getResponseMessage(), "Invalid content type " + contentType); | |
| 255 | + } | |
| 256 | + | |
| 257 | + return response; | |
| 258 | + } | |
| 259 | + } | |
| 260 | + finally | |
| 261 | + { | |
| 262 | + IOUtils.closeQuietly(inputStream); | |
| 263 | + } | |
| 264 | + } | |
| 265 | + | |
| 266 | + private String formatErrorAsJson(String response, String message) | |
| 267 | + { | |
| 268 | + return String.format("{\"response\":\"%s\",\"message\":\"%s\"}", response, message); | |
| 269 | + } | |
| 270 | + | |
| 271 | + private String buildQuery(Map<String, String> params) | |
| 272 | + { | |
| 273 | + StringBuilder sb = new StringBuilder(); | |
| 274 | + | |
| 275 | + try | |
| 276 | + { | |
| 277 | + String separator = ""; | |
| 278 | + for (Entry<String, String> postValue : params.entrySet()) | |
| 279 | + { | |
| 280 | + sb.append(separator).append(postValue.getKey()).append("=").append(URLEncoder.encode(postValue.getValue(), "UTF-8")); | |
| 281 | + separator = "&"; | |
| 282 | + } | |
| 283 | + } | |
| 284 | + catch (UnsupportedEncodingException ex) | |
| 285 | + { | |
| 286 | + ex.printStackTrace(); | |
| 287 | + } | |
| 288 | + | |
| 289 | + return sb.toString(); | |
| 290 | + } | |
| 291 | + | |
| 292 | + private boolean registerServerConnection(Session session, String serverId) | |
| 293 | + { | |
| 294 | + if (session == null || serverId == null) | |
| 295 | + { | |
| 296 | + return false; | |
| 297 | + } | |
| 298 | + | |
| 299 | + if (System.currentTimeMillis() - this.lastMojangAuth < 300000L) | |
| 300 | + { | |
| 301 | + LiteLoaderLogger.debug("Mojang connection is still fresh, using existing ticket"); | |
| 302 | + return true; | |
| 303 | + } | |
| 304 | + | |
| 305 | + try | |
| 306 | + { | |
| 307 | + LiteLoaderLogger.debug("Creating Mojang session ticket..."); | |
| 308 | + URL checkServerUrl = new URL("http://session.minecraft.net/game/joinserver.jsp?user=" + URLEncoder.encode(session.getUsername(), "UTF-8") + "&sessionId=" + URLEncoder.encode(session.getSessionID(), "UTF-8") + "&serverId=" + URLEncoder.encode(serverId, "UTF-8")); | |
| 309 | + BufferedReader responseReader = new BufferedReader(new InputStreamReader(checkServerUrl.openStream())); | |
| 310 | + String response = responseReader.readLine(); | |
| 311 | + responseReader.close(); | |
| 312 | + boolean joinSuccess = "OK".equals(response); | |
| 313 | + if (joinSuccess) | |
| 314 | + { | |
| 315 | + this.lastMojangAuth = System.currentTimeMillis(); | |
| 316 | + return true; | |
| 317 | + } | |
| 318 | + } | |
| 319 | + catch (IOException ex) | |
| 320 | + { | |
| 321 | + ex.printStackTrace(); | |
| 322 | + LiteLoaderLogger.debug("Failed to log on to invoke joinserver, connection to mojang failed"); | |
| 323 | + throw new InvalidRequestException(RequestFailureReason.SERVER_ERROR, "Failed registering server connection with Mojang"); | |
| 324 | + } | |
| 325 | + | |
| 326 | + return false; | |
| 327 | + } | |
| 328 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesServiceTask.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.framework; | |
| 2 | + | |
| 3 | +import net.minecraft.util.Session; | |
| 4 | + | |
| 5 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesClient; | |
| 6 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesProvider; | |
| 7 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | |
| 8 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesServiceDelegate; | |
| 9 | + | |
| 10 | +abstract class WebPreferencesServiceTask implements IWebPreferencesServiceDelegate | |
| 11 | +{ | |
| 12 | + private final IWebPreferencesProvider provider; | |
| 13 | + | |
| 14 | + private final IWebPreferencesClient client; | |
| 15 | + | |
| 16 | + private IWebPreferencesRequest request; | |
| 17 | + | |
| 18 | + WebPreferencesServiceTask(IWebPreferencesProvider provider, IWebPreferencesClient client) | |
| 19 | + { | |
| 20 | + this.provider = provider; | |
| 21 | + this.client = client; | |
| 22 | + } | |
| 23 | + | |
| 24 | + public IWebPreferencesClient getClient() | |
| 25 | + { | |
| 26 | + return this.client; | |
| 27 | + } | |
| 28 | + | |
| 29 | + public IWebPreferencesRequest getRequest() | |
| 30 | + { | |
| 31 | + return this.request; | |
| 32 | + } | |
| 33 | + | |
| 34 | + public void setRequest(IWebPreferencesRequest request) | |
| 35 | + { | |
| 36 | + this.request = request; | |
| 37 | + } | |
| 38 | + | |
| 39 | + @Override | |
| 40 | + public String getHostName() | |
| 41 | + { | |
| 42 | + return this.provider.getHostName(); | |
| 43 | + } | |
| 44 | + | |
| 45 | + @Override | |
| 46 | + public Session getSession() | |
| 47 | + { | |
| 48 | + return this.provider.getSession(); | |
| 49 | + } | |
| 50 | + | |
| 51 | + @Override | |
| 52 | + public String toString() | |
| 53 | + { | |
| 54 | + return String.format("%s[%s]", this.getClass().getSimpleName(), this.request); | |
| 55 | + } | |
| 56 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesServiceTaskGet.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.framework; | |
| 2 | + | |
| 3 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesClient; | |
| 4 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesProvider; | |
| 5 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | |
| 6 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | |
| 7 | + | |
| 8 | +class WebPreferencesServiceTaskGet extends WebPreferencesServiceTask | |
| 9 | +{ | |
| 10 | + WebPreferencesServiceTaskGet(IWebPreferencesProvider provider, IWebPreferencesClient client) | |
| 11 | + { | |
| 12 | + super(provider, client); | |
| 13 | + } | |
| 14 | + | |
| 15 | + @Override | |
| 16 | + public void onReceivedResponse(IWebPreferencesRequest request, IWebPreferencesResponse response) | |
| 17 | + { | |
| 18 | + IWebPreferencesClient client = this.getClient(); | |
| 19 | + if (client != null && response.hasValues()) | |
| 20 | + { | |
| 21 | + client.onGetRequestSuccess(response.getUUID(), response.getValues()); | |
| 22 | + } | |
| 23 | + } | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + public void onRequestFailed(IWebPreferencesRequest request, Throwable th, RequestFailureReason reason) | |
| 27 | + { | |
| 28 | + IWebPreferencesClient client = this.getClient(); | |
| 29 | + if (client != null) | |
| 30 | + { | |
| 31 | + client.onGetRequestFailed(request.getUUID(), request.getKeys(), reason); | |
| 32 | + } | |
| 33 | + } | |
| 34 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/framework/WebPreferencesServiceTaskSet.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.framework; | |
| 2 | + | |
| 3 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesClient; | |
| 4 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesProvider; | |
| 5 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesRequest; | |
| 6 | +import com.mumfrey.webprefs.interfaces.IWebPreferencesResponse; | |
| 7 | + | |
| 8 | +class WebPreferencesServiceTaskSet extends WebPreferencesServiceTask | |
| 9 | +{ | |
| 10 | + WebPreferencesServiceTaskSet(IWebPreferencesProvider provider, IWebPreferencesClient client) | |
| 11 | + { | |
| 12 | + super(provider, client); | |
| 13 | + } | |
| 14 | + | |
| 15 | + @Override | |
| 16 | + public void onReceivedResponse(IWebPreferencesRequest request, IWebPreferencesResponse response) | |
| 17 | + { | |
| 18 | + IWebPreferencesClient client = this.getClient(); | |
| 19 | + if (client != null && response.hasSetters()) | |
| 20 | + { | |
| 21 | + client.onSetRequestSuccess(response.getUUID(), response.getSetters()); | |
| 22 | + } | |
| 23 | + } | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + public void onRequestFailed(IWebPreferencesRequest request, Throwable th, RequestFailureReason reason) | |
| 27 | + { | |
| 28 | + IWebPreferencesClient client = this.getClient(); | |
| 29 | + if (client != null) | |
| 30 | + { | |
| 31 | + client.onSetRequestFailed(request.getUUID(), request.getKeys(), reason); | |
| 32 | + } | |
| 33 | + } | |
| 34 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/interfaces/IReliableWebPreferences.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.interfaces; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * Reliable WebPreferences, no this isn't implemented yet | |
| 5 | + * | |
| 6 | + * @author Adam Mummery-Smith | |
| 7 | + */ | |
| 8 | +public interface IReliableWebPreferences extends IWebPreferences | |
| 9 | +{ | |
| 10 | + public void isSynchronised(String key); | |
| 11 | + | |
| 12 | + public void setWithNotify(String key, String value, IWebPreferencesListener listener); | |
| 13 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferences.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.interfaces; | |
| 2 | + | |
| 3 | +import java.util.Set; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * Web-based preferences, objects implementing this interface represent a remote | |
| 7 | + * asychronous Key/Value store which fetches and commits values on a best-effort | |
| 8 | + * basis. | |
| 9 | + * | |
| 10 | + * <p>Values in this store are not guaranteed to be correct, nor are values | |
| 11 | + * written to the store guaranteed to be successfully written to the store. | |
| 12 | + * | |
| 13 | + * <p>In general, consumers should call {@link #get} and {@link #set} with the | |
| 14 | + * assumption that reads and writes will happen asynchronously, eg. they should | |
| 15 | + * continually poll {@link #get} rather than caching any responses returned | |
| 16 | + * from the preferences object.</p> | |
| 17 | + * | |
| 18 | + * <p>Behaviour is such that any call to {@link #has} or {@link #get} will | |
| 19 | + * trigger asynchronous retrieval of properties from the backend server. | |
| 20 | + * Likewise calls to {@link #set} will trigger asychronous commit of dirty | |
| 21 | + * values (both reads and sets are batched and sent to the server as a single | |
| 22 | + * request where possible).</p> | |
| 23 | + * | |
| 24 | + * <p>Keys in the collection are restricted to lowercase letters and the period | |
| 25 | + * symbol only, failure to adhere to this format will cause any accessor methods | |
| 26 | + * to throw InvalidKeyException. Values in the collection are strings and are | |
| 27 | + * limited to 255 characters and must not be null, failure to adhere to these | |
| 28 | + * restrictions will cause accessors to throw {@link InvalidValueException}.</p> | |
| 29 | + * | |
| 30 | + * <p>Consumers can trigger synchronisation events on the collection using | |
| 31 | + * convenience methods, these methods do not ensure propagation of settings but | |
| 32 | + * act can be used to alter the normal behaviour of the set when necessary, for | |
| 33 | + * example to poll for updated values periodically. The methods request(), | |
| 34 | + * poll() and commit() can be used to trigger synchronisation events, consult | |
| 35 | + * the javadoc for each method for more details.</p> | |
| 36 | + * | |
| 37 | + * @author Adam Mummery-Smith | |
| 38 | + */ | |
| 39 | +public interface IWebPreferences | |
| 40 | +{ | |
| 41 | + /** | |
| 42 | + * Get the UUID assoicated with this preference collection | |
| 43 | + */ | |
| 44 | + public abstract String getUUID(); | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * Get whether this collection is private or public | |
| 48 | + */ | |
| 49 | + public abstract boolean isPrivate(); | |
| 50 | + | |
| 51 | + /** | |
| 52 | + * Get whether this collection is read-only. Only the player's local | |
| 53 | + * preferences are writable. Any attempt to write to a read-only collection | |
| 54 | + * will throw a ReadOnlyPreferencesException. | |
| 55 | + */ | |
| 56 | + public abstract boolean isReadOnly(); | |
| 57 | + | |
| 58 | + /** | |
| 59 | + * Indicate to the set that the value specified by key is required an | |
| 60 | + * should be fetched from the server in the next batch. In general, | |
| 61 | + * consumers should use get() or has() to access the values in this | |
| 62 | + * collection, and expect that the collection will manage fetching the | |
| 63 | + * values from the server as part of the normal update cycle. However this | |
| 64 | + * method has two uses:</p> | |
| 65 | + * | |
| 66 | + * <ul> | |
| 67 | + * <li>You may choose to call {@link #request} when a handle to a | |
| 68 | + * preferences set is obtained, to indicate to the preferences set that | |
| 69 | + * you will require the value in the future and it should attempt to fetch | |
| 70 | + * the value so that it is ready for queries against the set later.</li> | |
| 71 | + * <li>You may wish to indicate to the set that it should request an | |
| 72 | + * updated value from the server for a key which it already has (normally | |
| 73 | + * values are cached indefinitely).</li> | |
| 74 | + * </ul> | |
| 75 | + * | |
| 76 | + * <p>In both cases, calls to request() should generally be limited to | |
| 77 | + * periodic request for values, to avoid constantly polling the server for | |
| 78 | + * values, and potentially running foul of request throttling at the server | |
| 79 | + * side</p> | |
| 80 | + * | |
| 81 | + * @param key key to request from the server | |
| 82 | + */ | |
| 83 | + public abstract void request(String key); | |
| 84 | + | |
| 85 | + /** | |
| 86 | + * Works exactly as {@link IWebPreferences#request(String)} but allows | |
| 87 | + * multiple values to be requested in a single invocation | |
| 88 | + * | |
| 89 | + * @param keys keys to request | |
| 90 | + */ | |
| 91 | + public abstract void request(String... keys); | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * Works exactly as {@link IWebPreferences#request(String)} but allows | |
| 95 | + * multiple values to be requested in a single invocation | |
| 96 | + * | |
| 97 | + * @param keys keys to request | |
| 98 | + */ | |
| 99 | + public abstract void request(Set<String> keys); | |
| 100 | + | |
| 101 | + /** | |
| 102 | + * Calling <tt>poll()</tt> is essentially the same as calling | |
| 103 | + * {@link #request} for every key currently in the collection, this method | |
| 104 | + * can essentially be used to refresh the entire collection and should be | |
| 105 | + * called only in exceptional circumstances. It is called automatically by | |
| 106 | + * the preferences manager when connecting to a server. | |
| 107 | + * | |
| 108 | + * <p>Invoking <tt>poll()</tt> causes all of the values in the collection to | |
| 109 | + * be requested as a single batch</p> | |
| 110 | + */ | |
| 111 | + public abstract void poll(); | |
| 112 | + | |
| 113 | + /** | |
| 114 | + * Similar to {@link poll} except for property writes instead of property | |
| 115 | + * reads. Under normal circumstances it should not be necessary to call this | |
| 116 | + * method, since commits are handled asychronously by the update loop. | |
| 117 | + * However it can be used to forcibly commit even "clean" values to the | |
| 118 | + * server. This is useful if you wish to ensure that values on the server be | |
| 119 | + * set to match the current values in the collection, regardless of | |
| 120 | + * comodification elsewhere. | |
| 121 | + * | |
| 122 | + * @param force If set to false, only dirty properties are batched. If true, | |
| 123 | + * all properties are batched. | |
| 124 | + */ | |
| 125 | + public abstract void commit(boolean force); | |
| 126 | + | |
| 127 | + /** | |
| 128 | + * Get whether this collection has a value for the specified key, and | |
| 129 | + * triggers asnchronous retrieval if not | |
| 130 | + * | |
| 131 | + * @param key Key to check for | |
| 132 | + * @return | |
| 133 | + */ | |
| 134 | + public abstract boolean has(String key); | |
| 135 | + | |
| 136 | + /** | |
| 137 | + * Get the value for the specified key from this collection. Invoking this | |
| 138 | + * method causes asynchronous retrieval of the specified key if it is not | |
| 139 | + * found in the collection and returns null. If the key is found, then the | |
| 140 | + * value is returned and the collection remains unchanged. | |
| 141 | + * | |
| 142 | + * @param key | |
| 143 | + * @return | |
| 144 | + */ | |
| 145 | + public abstract String get(String key); | |
| 146 | + | |
| 147 | + /** | |
| 148 | + * Works exactly like get(String), including triggering asynchronous | |
| 149 | + * retrieval of the property if the property is not found in the collection, | |
| 150 | + * except that if the property is not found the specified defaultValue is | |
| 151 | + * returned instead of returning null. | |
| 152 | + * | |
| 153 | + * @param key | |
| 154 | + * @param defaultValue | |
| 155 | + * @return | |
| 156 | + */ | |
| 157 | + public abstract String get(String key, String defaultValue); | |
| 158 | + | |
| 159 | + /** | |
| 160 | + * Sets a value in the collection and marks it for asynchronous commit to | |
| 161 | + * the server. | |
| 162 | + * | |
| 163 | + * @param key | |
| 164 | + * @param value | |
| 165 | + */ | |
| 166 | + public abstract void set(String key, String value); | |
| 167 | + | |
| 168 | + /** | |
| 169 | + * Remove a key from this collection. Marks the key to be deleted from the | |
| 170 | + * server as well. | |
| 171 | + * | |
| 172 | + * @param key | |
| 173 | + */ | |
| 174 | + public abstract void remove(String key); | |
| 175 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesClient.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.interfaces; | |
| 2 | + | |
| 3 | +import java.util.Map; | |
| 4 | +import java.util.Set; | |
| 5 | + | |
| 6 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | |
| 7 | + | |
| 8 | +public interface IWebPreferencesClient | |
| 9 | +{ | |
| 10 | + public abstract void onGetRequestSuccess(String uuid, Map<String, String> values); | |
| 11 | + | |
| 12 | + public abstract void onSetRequestSuccess(String uuid, Set<String> keys); | |
| 13 | + | |
| 14 | + public abstract void onGetRequestFailed(String uuid, Set<String> keys, RequestFailureReason reason); | |
| 15 | + | |
| 16 | + public abstract void onSetRequestFailed(String uuid, Set<String> keys, RequestFailureReason reason); | |
| 17 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesListener.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.interfaces; | |
| 2 | + | |
| 3 | +import java.util.Set; | |
| 4 | + | |
| 5 | +public interface IWebPreferencesListener | |
| 6 | +{ | |
| 7 | + public abstract void onCommitSuccess(IReliableWebPreferences preferences, Set<String> keys); | |
| 8 | + | |
| 9 | + public abstract void onCommitFailed(IReliableWebPreferences preferences, Set<String> keys); | |
| 10 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesProvider.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.interfaces; | |
| 2 | + | |
| 3 | +import java.util.Map; | |
| 4 | +import java.util.Set; | |
| 5 | + | |
| 6 | +import net.minecraft.util.Session; | |
| 7 | + | |
| 8 | +public interface IWebPreferencesProvider | |
| 9 | +{ | |
| 10 | + public abstract boolean isActive(); | |
| 11 | + | |
| 12 | + public abstract String getHostName(); | |
| 13 | + | |
| 14 | + public abstract Session getSession(); | |
| 15 | + | |
| 16 | + public abstract IWebPreferencesService getService(); | |
| 17 | + | |
| 18 | + public boolean requestGet(IWebPreferencesClient client, String uuid, Set<String> keys, boolean getPrivate); | |
| 19 | + | |
| 20 | + public boolean requestSet(IWebPreferencesClient client, String uuid, Map<String, String> values, boolean setPrivate); | |
| 21 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesRequest.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.interfaces; | |
| 2 | + | |
| 3 | +import java.io.Serializable; | |
| 4 | +import java.net.URI; | |
| 5 | +import java.util.Map; | |
| 6 | +import java.util.Set; | |
| 7 | + | |
| 8 | +public interface IWebPreferencesRequest extends Serializable | |
| 9 | +{ | |
| 10 | + public abstract IWebPreferencesServiceDelegate getDelegate(); | |
| 11 | + | |
| 12 | + public abstract boolean isValidationRequired(); | |
| 13 | + | |
| 14 | + public abstract URI getRequestURI(); | |
| 15 | + | |
| 16 | + public abstract String getUUID(); | |
| 17 | + | |
| 18 | + public abstract Set<String> getKeys(); | |
| 19 | + | |
| 20 | + public abstract Map<String, String> getPostVars(); | |
| 21 | + | |
| 22 | + public abstract void onReceivedResponse(IWebPreferencesResponse response); | |
| 23 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesResponse.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.interfaces; | |
| 2 | + | |
| 3 | +import java.io.Serializable; | |
| 4 | +import java.util.Map; | |
| 5 | +import java.util.Set; | |
| 6 | + | |
| 7 | +public interface IWebPreferencesResponse extends Serializable | |
| 8 | +{ | |
| 9 | + public abstract String getResponse(); | |
| 10 | + | |
| 11 | + public abstract String getMessage(); | |
| 12 | + | |
| 13 | + public abstract Throwable getThrowable(); | |
| 14 | + | |
| 15 | + public abstract String getUUID(); | |
| 16 | + | |
| 17 | + public abstract String getServerId(); | |
| 18 | + | |
| 19 | + public abstract boolean hasSetters(); | |
| 20 | + | |
| 21 | + public abstract Set<String> getSetters(); | |
| 22 | + | |
| 23 | + public abstract boolean hasValues(); | |
| 24 | + | |
| 25 | + public abstract Map<String, String> getValues(); | |
| 26 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesService.java
0 → 100644
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesServiceDelegate.java
0 → 100644
| 1 | +package com.mumfrey.webprefs.interfaces; | |
| 2 | + | |
| 3 | +import net.minecraft.util.Session; | |
| 4 | + | |
| 5 | +import com.mumfrey.webprefs.framework.RequestFailureReason; | |
| 6 | + | |
| 7 | +public interface IWebPreferencesServiceDelegate | |
| 8 | +{ | |
| 9 | + public abstract String getHostName(); | |
| 10 | + | |
| 11 | + public abstract Session getSession(); | |
| 12 | + | |
| 13 | + public abstract void onReceivedResponse(IWebPreferencesRequest request, IWebPreferencesResponse response); | |
| 14 | + | |
| 15 | + public abstract void onRequestFailed(IWebPreferencesRequest request, Throwable th, RequestFailureReason reason); | |
| 16 | +} | ... | ... |
src/client/java/com/mumfrey/webprefs/interfaces/IWebPreferencesServiceMonitor.java
0 → 100644