001/* 002 * Copyright 2015 Aroma Tech. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package tech.aroma.client; 018 019import java.util.concurrent.ExecutorService; 020import java.util.concurrent.Executors; 021import tech.aroma.client.exceptions.BananaException; 022import tech.aroma.thrift.application.service.ApplicationServiceConstants; 023import tech.aroma.thrift.authentication.ApplicationToken; 024import tech.aroma.thrift.endpoint.Endpoint; 025import tech.aroma.thrift.endpoint.TcpEndpoint; 026import tech.sirwellington.alchemy.annotations.arguments.NonEmpty; 027import tech.sirwellington.alchemy.annotations.arguments.Optional; 028import tech.sirwellington.alchemy.annotations.arguments.Required; 029import tech.sirwellington.alchemy.annotations.concurrency.ThreadSafe; 030import tech.sirwellington.alchemy.annotations.designs.FluidAPIDesign; 031import tech.sirwellington.alchemy.annotations.designs.patterns.BuilderPattern; 032 033import static tech.sirwellington.alchemy.annotations.designs.patterns.BuilderPattern.Role.BUILDER; 034import static tech.sirwellington.alchemy.annotations.designs.patterns.BuilderPattern.Role.PRODUCT; 035import static tech.sirwellington.alchemy.arguments.Arguments.checkThat; 036import static tech.sirwellington.alchemy.arguments.assertions.Assertions.notNull; 037import static tech.sirwellington.alchemy.arguments.assertions.NetworkAssertions.validPort; 038import static tech.sirwellington.alchemy.arguments.assertions.StringAssertions.nonEmptyString; 039 040/** 041 * 042 * @author SirWellington 043 */ 044@ThreadSafe 045@BuilderPattern(role = PRODUCT) 046@FluidAPIDesign 047public interface Aroma 048{ 049 050 Request begin(); 051 052 interface Request 053 { 054 Request text(@Required String message, @Optional Object...args); 055 056 Request titled(@Required String title); 057 058 Request withUrgency(@Required Urgency level) throws IllegalArgumentException; 059 060 void send() throws IllegalArgumentException, BananaException; 061 } 062 063 static Aroma create() 064 { 065 return create("Aroma"); 066 } 067 068 static Aroma create(@NonEmpty String applicationToken) 069 { 070 checkThat(applicationToken) 071 .usingMessage("Application Token cannot be empty") 072 .is(nonEmptyString()); 073 074 return newBuilder() 075 .withAsyncExecutorService(Executors.newSingleThreadExecutor()) 076 .withApplicationToken(applicationToken) 077 .build(); 078 } 079 080 static Builder newBuilder() 081 { 082 return new Builder(); 083 } 084 085 @BuilderPattern(role = BUILDER) 086 static final class Builder 087 { 088 089 private String hostname = ApplicationServiceConstants.PRODUCTION_ENDPOINT.getHostname(); 090 private int port = ApplicationServiceConstants.PRODUCTION_ENDPOINT.getPort(); 091 private String applicationToken = ""; 092 private ExecutorService async; 093 094 Builder() 095 { 096 097 } 098 099 /** 100 * Set the Token ID created from the Aroma App. 101 * 102 * @param applicationToken 103 * @return 104 * 105 * @throws IllegalArgumentException 106 */ 107 public Builder withApplicationToken(@Required String applicationToken) throws IllegalArgumentException 108 { 109 checkThat(applicationToken) 110 .are(nonEmptyString()); 111 112 this.applicationToken = applicationToken; 113 114 return this; 115 } 116 117 public Builder withEndpoint(@NonEmpty String hostname, int port) throws IllegalArgumentException 118 { 119 checkThat(hostname) 120 .usingMessage("hostname cannot be empty") 121 .is(nonEmptyString()); 122 123 checkThat(port) 124 .is(validPort()); 125 126 this.hostname = hostname; 127 this.port = port; 128 129 return this; 130 } 131 132 public Builder withAsyncExecutorService(@Required ExecutorService executor) throws IllegalArgumentException 133 { 134 checkThat(executor) 135 .is(notNull()); 136 137 this.async = executor; 138 139 return this; 140 } 141 142 public Aroma build() throws IllegalStateException 143 { 144 checkThat(hostname) 145 .throwing(IllegalStateException.class) 146 .usingMessage("missing hostname") 147 .is(nonEmptyString()); 148 149 checkThat(applicationToken) 150 .throwing(IllegalStateException.class) 151 .usingMessage("missing Application Token") 152 .is(nonEmptyString()); 153 154 checkThat(port) 155 .throwing(IllegalStateException.class) 156 .is(validPort()); 157 158 if (async == null) 159 { 160 async = Executors.newSingleThreadExecutor(); 161 } 162 163 Endpoint endpoint = createEndpoint(); 164 165 ApplicationToken token = new ApplicationToken().setTokenId(applicationToken); 166 167 ThriftClientProvider clientProvider = new ThriftClientProvider(() -> endpoint); 168 AromaClient banana = new AromaClient(() -> clientProvider.get(), async, token); 169 return banana; 170 171 } 172 173 private Endpoint createEndpoint() 174 { 175 TcpEndpoint tcpEndpoint = new TcpEndpoint(hostname, port); 176 177 Endpoint endpoint = new Endpoint(); 178 endpoint.setTcp(tcpEndpoint); 179 return endpoint; 180 } 181 182 } 183 184}