1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.common.dns.dnsme;
17
18 import static com.google.common.base.Optional.fromNullable;
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.collect.Lists.newArrayList;
21 import static org.kuali.common.dns.model.DnsRecordType.CNAME;
22 import static org.kuali.common.dns.util.DNS.checkIpv4Fqdn;
23 import static org.kuali.common.util.base.Precondition.checkMin;
24 import static org.kuali.common.util.base.Precondition.checkNotBlank;
25 import static org.kuali.common.util.base.Precondition.checkNotNull;
26 import static org.kuali.common.util.nullify.NullUtils.trimToNone;
27
28 import java.io.UnsupportedEncodingException;
29 import java.lang.reflect.Type;
30 import java.net.URLEncoder;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34
35 import org.apache.commons.httpclient.HttpMethod;
36 import org.apache.commons.httpclient.NameValuePair;
37 import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
38 import org.apache.commons.httpclient.methods.PostMethod;
39 import org.apache.commons.httpclient.methods.PutMethod;
40 import org.kuali.common.dns.api.DnsService;
41 import org.kuali.common.dns.dnsme.model.DNSMadeEasyCredentials;
42 import org.kuali.common.dns.dnsme.model.DNSMadeEasyServiceContext;
43 import org.kuali.common.dns.dnsme.model.DnsMadeEasyDnsRecord;
44 import org.kuali.common.dns.dnsme.model.DnsMadeEasyDnsRecordComparator;
45 import org.kuali.common.dns.dnsme.model.DnsMadeEasyDomain;
46 import org.kuali.common.dns.dnsme.model.DnsMadeEasyDomainNames;
47 import org.kuali.common.dns.dnsme.model.DnsMadeEasySearchCriteria;
48 import org.kuali.common.dns.http.HttpRequestResult;
49 import org.kuali.common.dns.http.HttpUtil;
50 import org.kuali.common.dns.model.DnsRecordSearchCriteria;
51 import org.kuali.common.dns.model.DnsRecordType;
52 import org.kuali.common.dns.model.SimpleDnsRecord;
53 import org.kuali.common.util.Assert;
54
55 import com.google.common.base.Optional;
56 import com.google.common.collect.Ordering;
57 import com.google.gson.Gson;
58 import com.google.gson.reflect.TypeToken;
59
60 public final class DNSMadeEasyDnsService implements DnsService {
61
62 public static final int HTTP_OK = 200;
63 public static final int HTTP_CREATED = 201;
64
65
66 private final DNSMadeEasyServiceContext context;
67 private final String restApiUrl;
68 private final String domainName;
69 private final DNSMadeEasyCredentials credentials;
70 private final DnsMadeEasyDomain domain;
71
72
73 private final Gson gson = new Gson();
74 private final HttpUtil http = new HttpUtil();
75 private final DNSMEUtil dnsme = new DNSMEUtil();
76
77 public DNSMadeEasyDnsService(DNSMadeEasyServiceContext context) {
78 this.context = checkNotNull(context, "context");
79 this.restApiUrl = context.getRestApiUrl();
80 this.credentials = context.getCredentials();
81 this.domainName = context.getDomainName();
82
83
84
85 this.domain = getDomain(domainName);
86 }
87
88 protected List<DnsMadeEasyDomain> getDomains() {
89 String url = this.restApiUrl + "/domains";
90 String json = getJson(url, HTTP_OK);
91 DnsMadeEasyDomainNames domainNames = gson.fromJson(json, DnsMadeEasyDomainNames.class);
92 return getDomains(domainNames);
93 }
94
95 protected DnsMadeEasyDomain getDomain(String name) {
96 List<DnsMadeEasyDomain> domains = getDomains();
97 for (DnsMadeEasyDomain domain : domains) {
98 if (domain.getName().equalsIgnoreCase(name)) {
99 return domain;
100 }
101 }
102 return null;
103 }
104
105 protected DnsMadeEasyDomain addDomain(DnsMadeEasyDomain domain) {
106 String url = this.restApiUrl + "/domains/" + domain.getName();
107 PutMethod method = new PutMethod(url);
108 return addOrUpdateObject(url, HTTP_CREATED, domain, method);
109 }
110
111 protected void deleteDomain(DnsMadeEasyDomain domain) {
112 String url = this.restApiUrl + "/domains/" + domain.getName();
113 deleteObject(url);
114 }
115
116 protected String getQueryString(DnsMadeEasySearchCriteria search) {
117 List<NameValuePair> pairs = getPairs(search);
118 StringBuilder sb = new StringBuilder();
119 for (int i = 0; i < pairs.size(); i++) {
120 NameValuePair pair = pairs.get(i);
121 if (i == 0) {
122 sb.append("?");
123 } else {
124 sb.append("&");
125 }
126 sb.append(pair.getName() + "=" + encode(pair.getValue()));
127 }
128 return sb.toString();
129 }
130
131 protected List<NameValuePair> getPairs(DnsMadeEasySearchCriteria search) {
132 List<NameValuePair> pairs = new ArrayList<NameValuePair>();
133 addIfNotNull(pairs, getPair("name", search.getName()));
134 addIfNotNull(pairs, getPair("nameContains", search.getNameContains()));
135 addIfNotNull(pairs, getPair("value", search.getValue()));
136 addIfNotNull(pairs, getPair("valueContains", search.getValueContains()));
137 addIfNotNull(pairs, getPair("gtdLocation", search.getGtdLocation()));
138 addIfNotNull(pairs, getPair("type", search.getType()));
139 return pairs;
140 }
141
142 protected void addIfNotNull(List<NameValuePair> pairs, NameValuePair pair) {
143 if (pair == null) {
144 return;
145 } else {
146 pairs.add(pair);
147 }
148 }
149
150 protected NameValuePair getPair(String name, Object value) {
151 if (value == null) {
152 return null;
153 } else {
154 return new NameValuePair(name, value.toString());
155 }
156 }
157
158 protected String encode(String value) {
159 try {
160 return URLEncoder.encode(value, "UTF-8");
161 } catch (UnsupportedEncodingException e) {
162 throw new IllegalStateException(e);
163 }
164 }
165
166 protected DnsMadeEasyDnsRecord getRecord(DnsMadeEasyDomain domain, DnsMadeEasySearchCriteria search) {
167 List<DnsMadeEasyDnsRecord> records = getRecords(domain, search);
168 if (records.size() != 1) {
169 throw new IllegalStateException("Search criteria must match exactly 1 record but it matched " + records.size() + " records");
170 } else {
171 return records.get(0);
172 }
173 }
174
175 protected String getAllRecordsApiUrl() {
176 return getRecordsApiUrl(Optional.<DnsMadeEasySearchCriteria> absent());
177
178 }
179
180 protected String getRecordsApiUrl(Optional<DnsMadeEasySearchCriteria> criteria) {
181 checkNotNull(criteria, "criteria");
182 String url = this.restApiUrl + "/domains/" + this.domainName + "/records";
183 if (criteria.isPresent()) {
184 url += getQueryString(criteria.get());
185 }
186 return url;
187 }
188
189 protected List<DnsMadeEasyDnsRecord> getRecords(DnsMadeEasyDomain domain, DnsMadeEasySearchCriteria search) {
190 String url = getRecordsApiUrl(fromNullable(search));
191 String json = getJson(url, HTTP_OK);
192 List<DnsMadeEasyDnsRecord> records = getRecords(json);
193 for (DnsMadeEasyDnsRecord record : records) {
194 record.setDomain(domain);
195 }
196 return records;
197 }
198
199 protected DnsMadeEasySearchCriteria getSearch(String name, DnsRecordType type) {
200 DnsMadeEasySearchCriteria search = new DnsMadeEasySearchCriteria();
201 search.setName(name);
202 search.setType(type);
203 return search;
204 }
205
206 protected DnsMadeEasySearchCriteria getSearch(String name) {
207 DnsMadeEasySearchCriteria search = new DnsMadeEasySearchCriteria();
208 search.setName(name);
209 return search;
210 }
211
212 protected DnsMadeEasyDnsRecord getRecord(DnsMadeEasyDomain domain, String name) {
213 return getRecord(domain, getSearch(name));
214 }
215
216 protected DnsMadeEasyDnsRecord getRecord(DnsMadeEasyDomain domain, int recordId) {
217 String url = this.restApiUrl + "/domains/" + domain.getName() + "/records/" + recordId;
218 String resultJson = getJson(url, HTTP_OK);
219 DnsMadeEasyDnsRecord resultRecord = gson.fromJson(resultJson, DnsMadeEasyDnsRecord.class);
220 return resultRecord;
221 }
222
223 protected void validateForUpdate(DnsMadeEasyDnsRecord record) {
224 if (record.getId() == null && record.getName() == null) {
225 throw new IllegalStateException("Either id or name must have a value when updating");
226 }
227 }
228
229 protected void updateRecord(DnsMadeEasyDomain domain, DnsMadeEasyDnsRecord record) {
230 validateForUpdate(record);
231 if (record.getId() == null) {
232 DnsMadeEasyDnsRecord existingRecord = getRecord(domain, record.getName());
233 record.setId(existingRecord.getId());
234 }
235 validateRecord(record);
236 String url = this.restApiUrl + "/domains/" + domain.getName() + "/records/" + record.getId();
237 PutMethod method = new PutMethod(url);
238 addOrUpdateObject(url, HTTP_OK, record, method);
239 }
240
241 protected DnsMadeEasyDnsRecord addRecord(DnsMadeEasyDomain domain, DnsMadeEasyDnsRecord record) {
242 String url = this.restApiUrl + "/domains/" + domain.getName() + "/records";
243 if (record.getId() != null) {
244 throw new IllegalStateException("id must be null when adding");
245 }
246 validateRecord(record);
247 PostMethod method = new PostMethod(url);
248 return addOrUpdateObject(url, HTTP_CREATED, record, method);
249 }
250
251 protected void deleteRecord(DnsMadeEasyDomain domain, int recordId) {
252 String url = this.restApiUrl + "/domains/" + domain.getName() + "/records/" + recordId;
253 deleteObject(url);
254 }
255
256 protected void deleteRecord(DnsMadeEasyDomain domain, String name, DnsRecordType type) {
257 DnsMadeEasySearchCriteria search = getSearch(name, type);
258 DnsMadeEasyDnsRecord record = getRecord(domain, search);
259 Assert.isTrue(record.getId() != null, "id is required");
260 deleteRecord(domain, record.getId());
261 }
262
263 protected void deleteRecord(DnsMadeEasyDomain domain, String name) {
264 DnsMadeEasyDnsRecord record = getRecord(domain, name);
265 Assert.isTrue(record.getId() != null, "id is required");
266 deleteRecord(domain, record.getId());
267 }
268
269 protected void deleteCNAMERecord(DnsMadeEasyDomain domain, String name) {
270 DnsMadeEasyDnsRecord record = getRecord(domain, name);
271 Assert.isTrue(record.getId() != null, "id is required");
272 deleteRecord(domain, record.getId());
273 }
274
275 protected void deleteObject(String url) {
276 HttpMethod method = dnsme.getDeleteMethod(credentials, url);
277 HttpRequestResult result = http.executeMethod(method);
278 validateResult(result, HTTP_OK);
279 }
280
281 protected List<DnsMadeEasyDnsRecord> getCNAMERecords(DnsMadeEasyDomain domain) {
282 DnsMadeEasySearchCriteria search = new DnsMadeEasySearchCriteria();
283 search.setType(DnsRecordType.CNAME);
284 List<DnsMadeEasyDnsRecord> records = getRecords(domain, search);
285 Collections.sort(records, new DnsMadeEasyDnsRecordComparator());
286 return records;
287 }
288
289 protected void validateRecord(DnsMadeEasyDnsRecord record) {
290 StringBuilder sb = new StringBuilder();
291 if (record.getName() == null) {
292 sb.append("Name must not be null\n");
293 }
294 if (record.getData() == null) {
295 sb.append("Data must not be null\n");
296 }
297 if (record.getTtl() == null) {
298 sb.append("TTL must not be null\n");
299 }
300 if (record.getType() == null) {
301 sb.append("Type must not be null\n");
302 }
303 if (sb.length() > 0) {
304 throw new IllegalStateException(sb.toString());
305 }
306 }
307
308 protected <T> T addOrUpdateObject(String url, int statusCode, T object, EntityEnclosingMethod method) {
309 String json = gson.toJson(object);
310 dnsme.updateMethod(credentials, json, method);
311 String resultJson = getJson(url, method, statusCode);
312 @SuppressWarnings("unchecked")
313 T resultObject = (T) gson.fromJson(resultJson, object.getClass());
314 return resultObject;
315 }
316
317 protected List<DnsMadeEasyDnsRecord> getRecords(DnsMadeEasyDomain domain) {
318 return getRecords(domain, null);
319 }
320
321 protected String getJson(String url, HttpMethod method, int successCode) {
322 HttpRequestResult result = http.executeMethod(method);
323 validateResult(result, successCode);
324 return result.getResponseBody();
325 }
326
327 protected String getJson(String url, int successCode) {
328 HttpMethod method = dnsme.getGetMethod(credentials, url);
329 return getJson(url, method, successCode);
330 }
331
332 protected List<DnsMadeEasyDnsRecord> getRecords(String json) {
333 Type recordsListType = new TypeToken<List<DnsMadeEasyDnsRecord>>() {
334 }.getType();
335
336 @SuppressWarnings("unchecked")
337 List<DnsMadeEasyDnsRecord> records = (List<DnsMadeEasyDnsRecord>) gson.fromJson(json, recordsListType);
338 if (records == null) {
339 return new ArrayList<DnsMadeEasyDnsRecord>();
340 } else {
341 return records;
342 }
343 }
344
345 protected void validateResult(HttpRequestResult result, int statusCode) {
346 switch (result.getType()) {
347 case EXCEPTION:
348 throw new IllegalStateException(result.getException());
349 case TIMEOUT:
350 throw new IllegalStateException("Operation timed out");
351 case COMPLETED:
352 int code = result.getStatusCode();
353 if (statusCode == result.getStatusCode()) {
354 return;
355 } else {
356 String errorText = result.getResponseBody();
357 throw new IllegalStateException("Invalid http status '" + code + ":" + result.getStatusText() + "' Expected: '" + statusCode + "' Error:" + errorText);
358 }
359 default:
360 throw new IllegalStateException("Unknown result type: " + result.getType());
361 }
362 }
363
364 protected List<DnsMadeEasyDomain> getDomains(DnsMadeEasyDomainNames domainNames) {
365 if (domainNames.getList() == null) {
366 return new ArrayList<DnsMadeEasyDomain>();
367 }
368 List<String> names = domainNames.getList();
369 List<DnsMadeEasyDomain> domains = new ArrayList<DnsMadeEasyDomain>();
370 for (String name : names) {
371 DnsMadeEasyDomain domain = new DnsMadeEasyDomain(credentials, name);
372 domains.add(domain);
373 }
374 return domains;
375 }
376
377 @Override
378 public String getDomainName() {
379 return domainName;
380 }
381
382
383
384
385 protected String checkDomain(String fqdn, String domain) {
386 checkNotBlank(fqdn, "fqdn");
387 checkNotBlank(domain, "domain");
388 checkArgument(fqdn.endsWith(domain), "[%s] doesn't end with [%s]", fqdn, domain);
389 return fqdn;
390 }
391
392 protected String getRecordNameFromFQDN(String fqdn, String domain) {
393 checkNotBlank(fqdn, "fqdn");
394 checkNotBlank(domain, "domain");
395
396
397 checkDomain(fqdn, domain);
398
399
400 int start = 0;
401 int end = fqdn.length() - domain.length();
402 String fragment = fqdn.substring(start, end);
403
404
405 if (fragment.endsWith(".")) {
406 return fragment.substring(0, fragment.length() - 1);
407 } else {
408 return fragment;
409 }
410 }
411
412 @Override
413 public String getCNAMERecordValueFromFQDN(String fqdn) {
414
415 checkIpv4Fqdn(fqdn);
416 return fqdn + ".";
417 }
418
419 @Override
420 public SimpleDnsRecord createCNAMERecord(String aliasFQDN, String fqdn, int timeToLiveInSeconds) {
421
422
423 checkIpv4Fqdn(aliasFQDN);
424 checkIpv4Fqdn(fqdn);
425
426
427 checkDomain(aliasFQDN, getDomainName());
428
429
430 checkMin(timeToLiveInSeconds, 0, "timeToLiveInSeconds");
431
432
433
434 String recordValue = getCNAMERecordValueFromFQDN(fqdn);
435
436
437 String recordName = getRecordNameFromFQDN(aliasFQDN, getDomainName());
438
439
440 DnsMadeEasyDnsRecord record = new DnsMadeEasyDnsRecord();
441 record.setName(recordName);
442 record.setType(DnsRecordType.CNAME);
443 record.setData(recordValue);
444 record.setTtl(timeToLiveInSeconds);
445
446
447 DnsMadeEasyDnsRecord added = addRecord(domain, record);
448
449
450 SimpleDnsRecord.Builder builder = SimpleDnsRecord.builder();
451 builder.withName(added.getName());
452 builder.withType(added.getType());
453 builder.withValue(record.getData());
454 builder.withTtl(added.getTtl());
455 return builder.build();
456 }
457
458 @Override
459 public boolean isExistingCNAMERecord(String fqdn) {
460 return getCNAMERecord(fqdn).isPresent();
461 }
462
463 @Override
464 public void deleteCNAMERecord(String fqdn) {
465
466
467 checkIpv4Fqdn(fqdn);
468
469
470 checkDomain(fqdn, getDomainName());
471
472
473 Optional<SimpleDnsRecord> optional = getCNAMERecord(fqdn);
474
475
476 if (optional.isPresent()) {
477
478
479 SimpleDnsRecord record = optional.get();
480
481
482 deleteRecord(domain, record.getName(), record.getType());
483 }
484 }
485
486 @Override
487 public Optional<SimpleDnsRecord> getCNAMERecord(String fqdn) {
488
489
490 checkIpv4Fqdn(fqdn);
491
492
493 checkDomain(fqdn, getDomainName());
494
495
496 String recordName = getRecordNameFromFQDN(fqdn, getDomainName());
497
498
499 DnsMadeEasySearchCriteria search = getSearch(recordName, CNAME);
500
501
502 List<DnsMadeEasyDnsRecord> records = getRecords(domain, search);
503
504
505 Assert.isFalse(records.size() > 1, "Found " + records.size() + " records when expecting a max of 1");
506
507 if (records.size() == 0) {
508
509 return Optional.<SimpleDnsRecord> absent();
510 } else {
511
512 DnsMadeEasyDnsRecord dnsme = records.get(0);
513
514
515 SimpleDnsRecord.Builder builder = SimpleDnsRecord.builder();
516 builder.withName(dnsme.getName());
517 builder.withType(dnsme.getType());
518 builder.withValue(dnsme.getData());
519 builder.withTtl(dnsme.getTtl());
520 SimpleDnsRecord record = builder.build();
521
522
523 return Optional.of(record);
524 }
525 }
526
527 @Override
528 public List<SimpleDnsRecord> getRecords() {
529 List<DnsMadeEasyDnsRecord> records = getRecords(domain);
530 return getRecords(records);
531 }
532
533 @Override
534 public List<SimpleDnsRecord> getRecords(DnsRecordSearchCriteria searchCriteria) {
535 Assert.noNulls(searchCriteria);
536 DnsMadeEasySearchCriteria search = new DnsMadeEasySearchCriteria();
537 if (searchCriteria.getNameContains().isPresent()) {
538 search.setNameContains(searchCriteria.getNameContains().get());
539 }
540 if (searchCriteria.getValueContains().isPresent()) {
541 search.setValueContains(searchCriteria.getValueContains().get());
542 }
543 if (searchCriteria.getType().isPresent()) {
544 search.setType(searchCriteria.getType().get());
545 }
546 List<DnsMadeEasyDnsRecord> records = getRecords(domain, search);
547 return getRecords(records);
548 }
549
550 protected List<SimpleDnsRecord> getRecords(List<DnsMadeEasyDnsRecord> records) {
551 List<SimpleDnsRecord> list = newArrayList();
552 for (DnsMadeEasyDnsRecord record : records) {
553 String name = trimToNone(record.getName());
554 String value = trimToNone(record.getData());
555
556 SimpleDnsRecord.Builder builder = SimpleDnsRecord.builder();
557 builder.withName(name);
558 builder.withType(record.getType());
559 builder.withValue(value);
560 builder.withTtl(record.getTtl());
561 SimpleDnsRecord element = builder.build();
562 list.add(element);
563 }
564 return Ordering.natural().immutableSortedCopy(list);
565 }
566
567 public DNSMadeEasyServiceContext getContext() {
568 return context;
569 }
570
571 public String getRestApiUrl() {
572 return restApiUrl;
573 }
574
575 public DNSMadeEasyCredentials getCredentials() {
576 return credentials;
577 }
578
579 public DnsMadeEasyDomain getDomain() {
580 return domain;
581 }
582
583 }